Last active
August 10, 2019 00:17
-
-
Save chriseidhof/fb2805e04d8eedcdcaf30e982563af37 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ContentView.swift | |
// SwiftTalkAnimationLoader | |
// | |
// Created by Chris Eidhof on 30.07.19. | |
// Copyright © 2019 Chris Eidhof. All rights reserved. | |
// | |
import SwiftUI | |
func +(lhs: CGPoint, rhs: CGPoint) -> CGPoint { | |
return CGPoint(x: lhs.x + rhs.x, y: lhs.y + rhs.y) | |
} | |
extension Path { | |
var currentPoint: CGPoint? { // temp fix for b4 | |
cgPath.currentPoint | |
} | |
mutating func addRelativeLine(to point: CGPoint) { | |
let position = currentPoint ?? .zero | |
addLine(to: position + point) | |
} | |
mutating func addVerticalLine(to: CGFloat) { | |
let position = currentPoint ?? .zero | |
addLine(to: CGPoint(x: position.x, y: to)) | |
} | |
mutating func addHorizontalLine(to: CGFloat) { | |
let position = currentPoint ?? .zero | |
addLine(to: CGPoint(x: to, y: position.y)) | |
} | |
mutating func addRelativeVerticalLine(to: CGFloat) { | |
let position = currentPoint ?? .zero | |
addLine(to: CGPoint(x: position.x, y: position.y + to)) | |
} | |
mutating func addRelativeHorizontalLine(to: CGFloat) { | |
let position = currentPoint ?? .zero | |
addLine(to: CGPoint(x: position.x + to, y: position.y)) | |
} | |
} | |
extension Path { | |
// M423 73l8-7-45.5-46L340 66l8 7 32-34v86h11V39l32 34z | |
static let objcioArrow = Path { p in | |
let startingPoint = CGPoint(x: 83, y: 53) | |
p.move(to: startingPoint) | |
p.addRelativeLine(to: CGPoint(x: 8, y: -7)) | |
p.addRelativeLine(to: CGPoint(x: -45.5, y: -46)) | |
p.addRelativeLine(to: CGPoint(x: -45.5, y: 46)) | |
p.addRelativeLine(to: CGPoint(x: 8, y: 7)) | |
p.addRelativeLine(to: CGPoint(x: 32, y: -34)) | |
p.addRelativeVerticalLine(to: 86) | |
p.addRelativeHorizontalLine(to: 11) | |
p.addRelativeVerticalLine(to: -86) | |
p.addRelativeLine(to: CGPoint(x: 32, y: 34)) | |
p.closeSubpath() | |
} | |
} | |
extension CGPoint { | |
var cgSize: CGSize { | |
return CGSize(width: x, height: y) | |
} | |
} | |
extension CGFloat { | |
func clamped(to: ClosedRange<CGFloat>) -> CGFloat { | |
return .minimum(.maximum(self, to.lowerBound), to.upperBound) | |
} | |
} | |
extension CGPoint { | |
func angle(to other: CGPoint) -> Angle { | |
let dX = Double(other.x - x) | |
let dY = Double(y - other.y) | |
return Angle(radians: atan2(dX, dY)) | |
} | |
} | |
struct PointOnPath: Animatable { | |
let path: Path | |
var offset: CGFloat // should be 0...1 | |
private let epsilon: CGFloat = 0.0001 | |
var animatableData: CGFloat { | |
get { offset } | |
set { offset = newValue.clamped(to: epsilon...1) } | |
} | |
var point: (CGPoint, direction: Angle) { | |
let point = path.trimmedPath(from: 0, to: offset).currentPoint! | |
let previousOffset = max(epsilon, offset - epsilon*50) | |
let previousPoint = path.trimmedPath(from: 0, to: previousOffset).currentPoint! | |
let direction = previousPoint.angle(to: point) | |
return (point, direction: direction) | |
} | |
} | |
struct OffsetOnPath<S: Shape>: Shape, Animatable { | |
var pointOnPath: PointOnPath | |
var shape: S | |
var animatableData: AnimatablePair<PointOnPath.AnimatableData, S.AnimatableData> { | |
get { | |
AnimatablePair(pointOnPath.animatableData, shape.animatableData) | |
} | |
set { | |
pointOnPath.animatableData = newValue.first | |
shape.animatableData = newValue.second | |
} | |
} | |
func path(in rect: CGRect) -> Path { | |
let (point, direction) = pointOnPath.point | |
return shape | |
.path(in: rect) | |
.applying(CGAffineTransform(rotationAngle: CGFloat(direction.radians))) | |
.offsetBy(dx: point.x, dy: point.y) | |
} | |
} | |
struct Arrow: View { | |
@Binding var animation: Bool | |
let theRect = Path(roundedRect: CGRect(origin: .zero, size: CGSize(width: 200, height: 300)), cornerRadius: 50) | |
@State var value: CGFloat = 0.0001 | |
var body: some View { | |
return VStack { | |
ZStack { | |
theRect.fill(Color.green) | |
OffsetOnPath(pointOnPath: PointOnPath(path: theRect, offset: animation ? 1 : 0), shape: Path.objcioArrow) | |
.fill(Color.black) | |
.animation(Animation.linear(duration: 10).repeatForever(autoreverses: false)) | |
} | |
Slider(value: $value, from: 0, through: 1) | |
} | |
} | |
} | |
struct ContentView: View { | |
@State var foo: Bool = false | |
var body: some View { | |
VStack { | |
Arrow(animation: $foo.self) | |
Button(action: { self.foo.toggle() }, label: { Text("Toggle") }) | |
} | |
} | |
} | |
#if DEBUG | |
struct ContentView_Previews: PreviewProvider { | |
static var previews: some View { | |
ContentView() | |
} | |
} | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment