Skip to content

Instantly share code, notes, and snippets.

@HarryGoodwin
Last active May 17, 2022 10:31
Show Gist options
  • Save HarryGoodwin/01c9a4ad0733d425be79129e839f13a9 to your computer and use it in GitHub Desktop.
Save HarryGoodwin/01c9a4ad0733d425be79129e839f13a9 to your computer and use it in GitHub Desktop.
Draws an animated spiral
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