Skip to content

Instantly share code, notes, and snippets.

@robertmryan
Last active June 28, 2022 12:07
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save robertmryan/67484c74297cede3926a3aed2fceedb9 to your computer and use it in GitHub Desktop.
Save robertmryan/67484c74297cede3926a3aed2fceedb9 to your computer and use it in GitHub Desktop.
class ViewController: UIViewController {
var bezier: QuadBezier!
let pathLayer: CAShapeLayer = {
let layer = CAShapeLayer()
layer.lineWidth = 5
layer.strokeColor = UIColor.blue.cgColor
layer.fillColor = UIColor.clear.cgColor
return layer
}()
var animation: CAKeyframeAnimation!
var shape: CircleView = {
let shape = CircleView()
shape.backgroundColor = .red
shape.frame = CGRect(origin: .zero, size: CGSize(width: 50, height: 50))
return shape
}()
override func viewDidLoad() {
super.viewDidLoad()
view.layer.addSublayer(pathLayer)
view.addSubview(shape)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
bezier = buildCurvedPath()
pathLayer.path = bezier.path.cgPath
shape.center = bezier.point(at: 0.5)
}
func buildCurvedPath() -> QuadBezier {
let bounds = view.bounds
let point1 = CGPoint(x: bounds.minX, y: bounds.midY)
let point2 = CGPoint(x: bounds.maxX, y: bounds.midY)
let controlPoint = CGPoint(x: bounds.midX, y: bounds.midY + 100)
let path = QuadBezier(point1: point1, point2: point2, controlPoint: controlPoint)
return path
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
updatePosition(for: touch)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
if let touch = event?.predictedTouches(for: touch)?.last {
updatePosition(for: touch)
} else {
updatePosition(for: touch)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
updatePosition(for: touch)
}
func updatePosition(for touch: UITouch) {
let location = touch.location(in: view)
let t = (location.x - view.bounds.minX) / view.bounds.width
shape.center = bezier.point(at: t)
}
}
struct QuadBezier {
var point1: CGPoint
var point2: CGPoint
var controlPoint: CGPoint
var path: UIBezierPath {
let path = UIBezierPath()
path.move(to: point1)
path.addQuadCurve(to: point2, controlPoint: controlPoint)
return path
}
func point(at t: CGFloat) -> CGPoint {
let t1 = 1 - t
return CGPoint(
x: t1 * t1 * point1.x + 2 * t * t1 * controlPoint.x + t * t * point2.x,
y: t1 * t1 * point1.y + 2 * t * t1 * controlPoint.y + t * t * point2.y
)
}
}
@IBDesignable
public class CircleView: UIView {
override public func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = min(bounds.width, bounds.height) / 2
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment