Skip to content

Instantly share code, notes, and snippets.

@chriseidhof
Last active August 10, 2019 00:17
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chriseidhof/fb2805e04d8eedcdcaf30e982563af37 to your computer and use it in GitHub Desktop.
Save chriseidhof/fb2805e04d8eedcdcaf30e982563af37 to your computer and use it in GitHub Desktop.
//
// 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