-
Notifications
You must be signed in to change notification settings - Fork 34
/
RecordButton.swift
223 lines (182 loc) · 8.38 KB
/
RecordButton.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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
//
// RecordButton.swift
// Instant
//
// Created by Samuel Beek on 21/06/15.
// Updated by digitallysavvy on (22/03/18), (08/08/19)
// Copyright (c) 2015 Samuel Beek. All rights reserved.
//
import Foundation
import UIKit
@objc public enum RecordButtonState : Int {
case recording, idle, hidden;
}
open class RecordButton : UIButton {
open var buttonColor: UIColor! = .blue {
didSet {
circleLayer.backgroundColor = buttonColor.cgColor
circleBorder.borderColor = buttonColor.cgColor
}
}
open var progressColor: UIColor! = .red {
didSet {
gradientMaskLayer.colors = [progressColor.cgColor, progressColor.cgColor]
}
}
/// Closes the circle and hides when the RecordButton is finished
open var closeWhenFinished: Bool = false
open var buttonState : RecordButtonState = .idle {
didSet {
switch buttonState {
case .idle:
self.alpha = 1.0
currentProgress = 0
setProgress(0)
setRecording(false)
case .recording:
self.alpha = 1.0
setRecording(true)
case .hidden:
self.alpha = 0
}
}
}
fileprivate var circleLayer: CALayer!
fileprivate var circleBorder: CALayer!
fileprivate var progressLayer: CAShapeLayer!
fileprivate var gradientMaskLayer: CAGradientLayer!
fileprivate var currentProgress: CGFloat! = 0
override public init(frame: CGRect) {
super.init(frame: frame)
self.addTarget(self, action: #selector(RecordButton.didTouchDown), for: .touchDown)
self.addTarget(self, action: #selector(RecordButton.didTouchUp), for: .touchUpInside)
self.addTarget(self, action: #selector(RecordButton.didTouchUp), for: .touchUpOutside)
self.drawButton()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.addTarget(self, action: #selector(RecordButton.didTouchDown), for: .touchDown)
self.addTarget(self, action: #selector(RecordButton.didTouchUp), for: .touchUpInside)
self.addTarget(self, action: #selector(RecordButton.didTouchUp), for: .touchUpOutside)
self.drawButton()
}
fileprivate func drawButton() {
self.backgroundColor = UIColor.clear
let layer = self.layer
circleLayer = CALayer()
circleLayer.backgroundColor = buttonColor.cgColor
let size: CGFloat = self.frame.size.width / 1.5
circleLayer.bounds = CGRect(x: 0, y: 0, width: size, height: size)
circleLayer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
circleLayer.position = CGPoint(x: self.bounds.midX,y: self.bounds.midY)
circleLayer.cornerRadius = size / 2
layer.insertSublayer(circleLayer, at: 0)
circleBorder = CALayer()
circleBorder.backgroundColor = UIColor.clear.cgColor
circleBorder.borderWidth = 1
circleBorder.borderColor = buttonColor.cgColor
circleBorder.bounds = CGRect(x: 0, y: 0, width: self.bounds.size.width - 1.5, height: self.bounds.size.height - 1.5)
circleBorder.anchorPoint = CGPoint(x: 0.5, y: 0.5)
circleBorder.position = CGPoint(x: self.bounds.midX,y: self.bounds.midY)
circleBorder.cornerRadius = self.frame.size.width / 2
layer.insertSublayer(circleBorder, at: 0)
let startAngle: CGFloat = CGFloat(Double.pi) + CGFloat(Double.pi/2)
let endAngle: CGFloat = CGFloat(Double.pi) * 3 + CGFloat(Double.pi/2)
let centerPoint: CGPoint = CGPoint(x: self.frame.size.width / 2, y: self.frame.size.height / 2)
gradientMaskLayer = self.gradientMask()
progressLayer = CAShapeLayer()
progressLayer.path = UIBezierPath(arcCenter: centerPoint, radius: self.frame.size.width / 2 - 2, startAngle: startAngle, endAngle: endAngle, clockwise: true).cgPath
progressLayer.backgroundColor = UIColor.clear.cgColor
progressLayer.fillColor = nil
progressLayer.strokeColor = UIColor.black.cgColor
progressLayer.lineWidth = 4.0
progressLayer.strokeStart = 0.0
progressLayer.strokeEnd = 0.0
gradientMaskLayer.mask = progressLayer
layer.insertSublayer(gradientMaskLayer, at: 0)
}
fileprivate func setRecording(_ recording: Bool) {
let duration: TimeInterval = 0.15
circleLayer.contentsGravity = CALayerContentsGravity(rawValue: "center")
let scale = CABasicAnimation(keyPath: "transform.scale")
scale.fromValue = recording ? 1.0 : 0.88
scale.toValue = recording ? 0.88 : 1
scale.duration = duration
scale.fillMode = CAMediaTimingFillMode.forwards
scale.isRemovedOnCompletion = false
let color = CABasicAnimation(keyPath: "backgroundColor")
color.duration = duration
color.fillMode = CAMediaTimingFillMode.forwards
color.isRemovedOnCompletion = false
color.toValue = recording ? progressColor.cgColor : buttonColor.cgColor
let circleAnimations = CAAnimationGroup()
circleAnimations.isRemovedOnCompletion = false
circleAnimations.fillMode = CAMediaTimingFillMode.forwards
circleAnimations.duration = duration
circleAnimations.animations = [scale, color]
let borderColor: CABasicAnimation = CABasicAnimation(keyPath: "borderColor")
borderColor.duration = duration
borderColor.fillMode = CAMediaTimingFillMode.forwards
borderColor.isRemovedOnCompletion = false
borderColor.toValue = recording ? UIColor(red: 0.83, green: 0.86, blue: 0.89, alpha: 1).cgColor : buttonColor
let borderScale = CABasicAnimation(keyPath: "transform.scale")
borderScale.fromValue = recording ? 1.0 : 0.88
borderScale.toValue = recording ? 0.88 : 1.0
borderScale.duration = duration
borderScale.fillMode = CAMediaTimingFillMode.forwards
borderScale.isRemovedOnCompletion = false
let borderAnimations = CAAnimationGroup()
borderAnimations.isRemovedOnCompletion = false
borderAnimations.fillMode = CAMediaTimingFillMode.forwards
borderAnimations.duration = duration
borderAnimations.animations = [borderColor, borderScale]
let fade = CABasicAnimation(keyPath: "opacity")
fade.fromValue = recording ? 0.0 : 1.0
fade.toValue = recording ? 1.0 : 0.0
fade.duration = duration
fade.fillMode = CAMediaTimingFillMode.forwards
fade.isRemovedOnCompletion = false
circleLayer.add(circleAnimations, forKey: "circleAnimations")
progressLayer.add(fade, forKey: "fade")
circleBorder.add(borderAnimations, forKey: "borderAnimations")
}
fileprivate func gradientMask() -> CAGradientLayer {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = self.bounds
gradientLayer.locations = [0.0, 1.0]
let topColor = progressColor
let bottomColor = progressColor
gradientLayer.colors = [topColor?.cgColor as Any, bottomColor?.cgColor as Any]
return gradientLayer
}
override open func layoutSubviews() {
circleLayer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
circleLayer.position = CGPoint(x: self.bounds.midX,y: self.bounds.midY)
circleBorder.anchorPoint = CGPoint(x: 0.5, y: 0.5)
circleBorder.position = CGPoint(x: self.bounds.midX,y: self.bounds.midY)
super.layoutSubviews()
}
@objc open func didTouchDown(){
self.buttonState = .recording
}
@objc open func didTouchUp() {
if(closeWhenFinished) {
self.setProgress(1)
UIView.animate(withDuration: 0.3, animations: {
self.buttonState = .hidden
}, completion: { completion in
self.setProgress(0)
self.currentProgress = 0
})
} else {
self.buttonState = .idle
}
}
/**
Set the relative length of the circle border to the specified progress
- parameter newProgress: the relative lenght, a percentage as float.
*/
open func setProgress(_ newProgress: CGFloat) {
progressLayer.strokeEnd = newProgress
}
}