-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathConnectButton+CheckmarkView.swift
146 lines (120 loc) · 5.66 KB
/
ConnectButton+CheckmarkView.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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
//
// ConnectButton+CheckmarkView.swift
// IFTTT SDK
//
// Copyright © 2019 IFTTT. All rights reserved.
//
import UIKit
extension ConnectButton {
/// A `UIView` subclass that is used to draw a checkmark when completing activation.
final class CheckmarkView: UIView {
/// The `UIView` that represents the circular outline of the `CheckmarkView`.
let outline = UIView()
private let indicator = UIView()
private let checkmarkShape = CAShapeLayer()
private let indicatorAnimationPath = UIBezierPath()
private func reset() {
CATransaction.begin()
CATransaction.setDisableActions(true)
checkmarkShape.strokeEnd = 0
CATransaction.commit()
}
/// Creates a `CheckmarkView`.
init() {
super.init(frame: .zero)
constrain.square(length: Layout.height)
let lineWidth: CGFloat = 3
addSubview(outline)
outline.layer.borderWidth = lineWidth
outline.layer.borderColor = Color.border.cgColor
outline.constrain.center(in: self)
outline.constrain.square(length: Layout.checkmarkDiameter)
addSubview(indicator)
indicator.translatesAutoresizingMaskIntoConstraints = false
indicator.frame = CGRect(x: 0, y: 0, width: lineWidth, height: lineWidth)
layer.addSublayer(checkmarkShape)
indicator.backgroundColor = .white
checkmarkShape.fillColor = UIColor.clear.cgColor
checkmarkShape.strokeColor = UIColor.white.cgColor
#if swift(>=4.2)
checkmarkShape.lineCap = .round
checkmarkShape.lineJoin = .round
#else
checkmarkShape.lineCap = kCALineCapRound
checkmarkShape.lineJoin = kCALineJoinRound
#endif
checkmarkShape.lineWidth = lineWidth
// Offset by line width (this visually centered the checkmark)
let center = CGPoint(x: 0.5 * Layout.height - lineWidth, y: 0.5 * Layout.height)
// The projection of the checkmarks longest arm to the X and Y axes (Since its a 45 / 45 triangle)
let armProjection = Layout.checkmarkLength / sqrt(2)
let checkmarkStartPoint = CGPoint(x: center.x - 0.5 * armProjection,
y: center.y)
let path = UIBezierPath()
path.move(to: checkmarkStartPoint)
path.addLine(to: CGPoint(x: center.x,
y: center.y + 0.5 * armProjection))
path.addLine(to: CGPoint(x: center.x + armProjection,
y: center.y - 0.5 * armProjection))
checkmarkShape.path = path.cgPath
checkmarkShape.strokeEnd = 0
indicatorAnimationPath.move(to: center)
indicatorAnimationPath.addQuadCurve(to: CGPoint(x: 0,
y: center.y),
controlPoint: CGPoint(x: 0,
y: Layout.height))
indicatorAnimationPath.addQuadCurve(to: checkmarkStartPoint,
controlPoint: .zero)
}
@available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
outline.layer.cornerRadius = 0.5 * outline.bounds.height
indicator.layer.cornerRadius = 0.5 * indicator.bounds.height
checkmarkShape.frame = bounds
}
}
}
extension ConnectButton.CheckmarkView {
/// Draws the checkmark.
///
/// - Parameter duration: The amount of time the checkmark should be drawn in.
func drawCheckmark(duration: TimeInterval) {
UIView.performWithoutAnimation {
self.indicator.alpha = 1
}
reset()
let indicatorAnimation = CAKeyframeAnimation(keyPath: "position")
indicatorAnimation.path = indicatorAnimationPath.cgPath
indicatorAnimation.duration = 0.6 * duration
#if swift(>=4.2)
indicatorAnimation.timingFunction = CAMediaTimingFunction(name: .easeIn)
#else
indicatorAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
#endif
indicatorAnimation.delegate = self
indicator.layer.add(indicatorAnimation, forKey: "position along path")
indicator.layer.position = indicatorAnimationPath.currentPoint
}
}
extension ConnectButton.CheckmarkView: CAAnimationDelegate {
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
if anim is CAKeyframeAnimation {
indicator.alpha = 0
checkmarkShape.strokeEnd = 1
let checkmarkAnimation = CABasicAnimation(keyPath: "strokeEnd")
checkmarkAnimation.fromValue = 0
checkmarkAnimation.toValue = 1
checkmarkAnimation.duration = 0.4 * anim.duration
#if swift(>=4.2)
checkmarkAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut)
#else
checkmarkAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
#endif
self.checkmarkShape.add(checkmarkAnimation, forKey: "draw line")
}
}
}