-
Notifications
You must be signed in to change notification settings - Fork 111
/
IndeterminateLoadingView.swift
107 lines (83 loc) · 3.15 KB
/
IndeterminateLoadingView.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
//
// LoadingView.swift
// LoadingUI
//
// Created by Andrew R Madsen on 9/18/18.
// Copyright © 2018 Lambda School. All rights reserved.
//
import UIKit
class IndeterminateLoadingView: UIView, CAAnimationDelegate {
override init(frame: CGRect) {
super.init(frame: frame)
setupShapeLayer()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupShapeLayer()
}
func startAnimating() {
guard !isAnimating else { return }
defer { isAnimating = true }
startAnimation()
}
func stopAnimating() {
guard isAnimating else { return }
shouldStopAnimationOnNextCycle = true
}
// MARK: - Private
private func setupShapeLayer() {
let thickness: CGFloat = 10.0
shapeLayer.frame = layer.bounds
shapeLayer.strokeColor = UIColor.black.cgColor
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.lineWidth = thickness
shapeLayer.strokeStart = 0.0
shapeLayer.strokeEnd = 0.0
layer.addSublayer(shapeLayer)
let radius = min(bounds.width, bounds.height) / 2.0 - thickness/2.0
let rect = CGRect(x: bounds.midX - radius/2.0, y: bounds.midY - radius/2.0, width: radius, height: radius)
let path = UIBezierPath(ovalIn: rect)
shapeLayer.path = path.cgPath
}
private func startAnimation() {
shouldStopAnimationOnNextCycle = false
shapeLayer.strokeStart = 0.0
shapeLayer.strokeEnd = 0.0
startAnimation(for: "strokeEnd", timing: .easeIn)
}
private func startAnimation(for keyPath: String, timing: CAMediaTimingFunctionName) {
let animation = CABasicAnimation(keyPath: keyPath)
animation.fromValue = 0.0
animation.toValue = 1.0
animation.duration = duration
animation.delegate = self
animation.isRemovedOnCompletion = false
animation.timingFunction = CAMediaTimingFunction(name: timing)
shapeLayer.add(animation, forKey: keyPath)
}
// MARK: - CAAnimationDelegate
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
guard !shouldStopAnimationOnNextCycle else {
shouldStopAnimationOnNextCycle = false
isAnimating = false
return
}
if let anim = anim as? CABasicAnimation, anim.keyPath == "strokeEnd" {
shapeLayer.strokeStart = 0.0
shapeLayer.strokeEnd = 1.0
shapeLayer.removeAllAnimations()
startAnimation(for: "strokeStart", timing: .easeOut)
}
if let anim = anim as? CABasicAnimation, anim.keyPath == "strokeStart" {
shapeLayer.strokeStart = 0.0
shapeLayer.strokeEnd = 0.0
shapeLayer.removeAllAnimations()
startAnimation(for: "strokeEnd", timing: .easeIn)
}
}
// MARK: - Properties
private(set) var isAnimating = false
private let shapeLayer = CAShapeLayer()
private let duration = 1.0
private var shouldStopAnimationOnNextCycle = false
}