Last active
May 17, 2022 10:31
-
-
Save HarryGoodwin/01c9a4ad0733d425be79129e839f13a9 to your computer and use it in GitHub Desktop.
Draws an animated spiral
This file contains hidden or 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
import UIKit | |
class SpiralViewController : UIViewController { | |
var spiralView: SpiralView! | |
override func loadView() { | |
let view = SpiralView() | |
spiralView = view | |
view.backgroundColor = .white | |
self.view = view | |
} | |
override func viewDidLayoutSubviews() { | |
spiralView.drawSpiral() | |
} | |
} | |
class SpiralView: UIView { | |
func drawSpiral() { | |
let rect = self.bounds | |
let height = rect.size.height | |
let width = rect.size.width | |
let longestSideLength: CGFloat = width - 10 // stops outer line being too close to edge | |
let center = CGPoint(x: width / 2, y: height / 2) | |
let origin = CGPoint(x: center.x - longestSideLength / 2, | |
y: center.y - longestSideLength / 2) | |
let points = calculatePoints(points: [origin], lineLength: longestSideLength) | |
guard points.count > 1 else { return } | |
let path = makePath(with: points) | |
let shapeLayer = makeShapeLayer(with: path) | |
addAnimation(to: shapeLayer) | |
self.layer.addSublayer(shapeLayer) | |
} | |
private func makePath(with points: [CGPoint]) -> UIBezierPath { | |
let path = UIBezierPath() | |
path.move(to: points.first!) | |
for point in points { | |
path.addLine(to: point) | |
} | |
return path | |
} | |
private func makeShapeLayer(with path: UIBezierPath) -> CAShapeLayer { | |
let shapeLayer = CAShapeLayer() | |
shapeLayer.path = path.cgPath | |
shapeLayer.lineWidth = 2 | |
shapeLayer.fillColor = UIColor.clear.cgColor | |
shapeLayer.strokeColor = UIColor.red.cgColor | |
return shapeLayer | |
} | |
private func addAnimation(to shapeLayer: CAShapeLayer) { | |
let animation = CABasicAnimation(keyPath: "strokeEnd") | |
animation.fromValue = 0 | |
animation.toValue = 1 | |
animation.duration = 5 | |
animation.autoreverses = false | |
animation.repeatCount = .infinity | |
shapeLayer.add(animation, forKey: "line") | |
} | |
private func calculatePoints(points: [CGPoint], | |
lineLength: CGFloat) -> [CGPoint] { | |
let newLineLength = lineLength - 5 | |
guard newLineLength > 0 else { return points } | |
var points = points | |
if points.count == 1 { | |
let firstPoint = points.first! | |
let nextPoint = CGPoint(x: firstPoint.x + lineLength, y: firstPoint.y) | |
points.append(nextPoint) | |
} | |
let currentPoint = points.last! | |
let lastPoint = points[points.count - 2] | |
var x: CGFloat | |
var y: CGFloat | |
if lastPoint.y == currentPoint.y { | |
// we just moved horizontally so change y | |
x = currentPoint.x | |
if currentPoint.x > lastPoint.x { | |
y = currentPoint.y + newLineLength | |
} else { | |
y = currentPoint.y - newLineLength | |
} | |
} else { | |
// otherwise change x | |
y = currentPoint.y | |
if currentPoint.y > lastPoint.y { | |
x = currentPoint.x - newLineLength | |
} else { | |
x = currentPoint.x + newLineLength | |
} | |
} | |
points.append(CGPoint(x: x, y: y)) | |
return calculatePoints(points: points, lineLength: newLineLength) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment