Skip to content

Commit c97040a

Browse files
committed
first commit
1 parent d8489e1 commit c97040a

File tree

14 files changed

+1283
-2
lines changed

14 files changed

+1283
-2
lines changed

Classes/KYShutterButton.swift

+303
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
/*
2+
The MIT License (MIT)
3+
4+
Copyright (c) 2015 Kyohei Yamaguchi
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy
7+
of this software and associated documentation files (the "Software"), to deal
8+
in the Software without restriction, including without limitation the rights
9+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the Software is
11+
furnished to do so, subject to the following conditions:
12+
13+
The above copyright notice and this permission notice shall be included in all
14+
copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
SOFTWARE.
23+
*/
24+
25+
import UIKit
26+
27+
@IBDesignable
28+
public class KYShutterButton: UIButton {
29+
30+
public enum ShutterType: Int {
31+
case Normal, SlowMotion, TimeLapse
32+
}
33+
34+
public enum ButtonState: Int {
35+
case Normal, Recording
36+
}
37+
38+
private let _kstartAnimateDuration: CFTimeInterval = 0.5
39+
40+
/**************************************************************************/
41+
// MARK: - Properties
42+
/**************************************************************************/
43+
44+
@IBInspectable var typeRaw: Int = 0 {
45+
didSet {
46+
if let type = ShutterType(rawValue: typeRaw) {
47+
self.shutterType = type
48+
} else {
49+
self.shutterType = .Normal
50+
}
51+
}
52+
}
53+
54+
@IBInspectable public var buttonColor: UIColor = UIColor.redColor() {
55+
didSet {
56+
_circleLayer.backgroundColor = buttonColor.CGColor
57+
}
58+
}
59+
60+
@IBInspectable public var arcColor: UIColor = UIColor.whiteColor() {
61+
didSet {
62+
_arcLayer.strokeColor = arcColor.CGColor
63+
}
64+
}
65+
66+
@IBInspectable public var progressColor: UIColor = UIColor.whiteColor() {
67+
didSet {
68+
_progressLayer.strokeColor = progressColor.CGColor
69+
_rotateLayer.strokeColor = progressColor.CGColor
70+
}
71+
}
72+
73+
public var buttonState: ButtonState = .Normal {
74+
didSet {
75+
let animation = CABasicAnimation(keyPath: "path")
76+
animation.fromValue = _circleLayer.path
77+
animation.duration = 0.15
78+
79+
switch buttonState {
80+
case .Normal:
81+
if shutterType == .TimeLapse {
82+
_progressLayer.removeAllAnimations()
83+
_rotateLayer.removeAllAnimations()
84+
}
85+
animation.toValue = _circlePath.CGPath
86+
_circleLayer.addAnimation(animation, forKey: "path-anim")
87+
_circleLayer.path = _circlePath.CGPath
88+
case .Recording:
89+
animation.toValue = _roundRectPath.CGPath
90+
_circleLayer.addAnimation(animation, forKey: "path-anim")
91+
_circleLayer.path = _roundRectPath.CGPath
92+
if shutterType == .TimeLapse {
93+
_progressLayer.addAnimation(_startProgressAnimation, forKey: "start-anim")
94+
_rotateLayer.addAnimation(_startRotateAnimation, forKey: "rotate-anim")
95+
_progressLayer.addAnimation(_recordingAnimation, forKey: "recording-anim")
96+
_rotateLayer.addAnimation(_recordingRotateAnimation, forKey: "recordingRotate-anim")
97+
_progressLayer.path = p_arcPathWithProgress(1.0).CGPath
98+
}
99+
}
100+
}
101+
}
102+
103+
public var shutterType: ShutterType = .Normal {
104+
didSet {
105+
switch shutterType {
106+
case .Normal:
107+
_arcLayer.lineDashPattern = nil
108+
_progressLayer.hidden = true
109+
case .SlowMotion:
110+
_arcLayer.lineDashPattern = [1, 1]
111+
_progressLayer.hidden = true
112+
case .TimeLapse:
113+
let diameter = 2*CGFloat(M_PI)*(self.bounds.width/2 - self._arcWidth/2)
114+
_arcLayer.lineDashPattern = [1, diameter/10 - 1]
115+
_progressLayer.hidden = false
116+
}
117+
}
118+
}
119+
120+
private var _arcWidth: CGFloat {
121+
return bounds.width * 0.09090
122+
}
123+
124+
private var _arcMargin: CGFloat {
125+
return bounds.width * 0.03030
126+
}
127+
128+
lazy private var _circleLayer: CAShapeLayer = {
129+
let layer = CAShapeLayer()
130+
layer.path = self._circlePath.CGPath
131+
layer.fillColor = self.buttonColor.CGColor
132+
return layer
133+
}()
134+
135+
lazy private var _arcLayer: CAShapeLayer = {
136+
let layer = CAShapeLayer()
137+
let path = UIBezierPath(
138+
arcCenter: CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)),
139+
radius: self.bounds.width/2 - self._arcWidth/2,
140+
startAngle: -CGFloat(M_PI_2),
141+
endAngle: CGFloat(M_PI*2.0) - CGFloat(M_PI_2),
142+
clockwise: true
143+
)
144+
layer.path = path.CGPath
145+
layer.fillColor = UIColor.clearColor().CGColor
146+
layer.strokeColor = self.arcColor.CGColor
147+
layer.lineWidth = self._arcWidth
148+
return layer
149+
}()
150+
151+
lazy private var _progressLayer: CAShapeLayer = {
152+
let layer = CAShapeLayer()
153+
let path = self.p_arcPathWithProgress(1.0, clockwise: true)
154+
let diameter = 2*CGFloat(M_PI)*(self.bounds.width/2 - self._arcWidth/3)
155+
layer.lineDashPattern = [1, diameter/60 - 1]
156+
layer.path = path.CGPath
157+
layer.fillColor = UIColor.clearColor().CGColor
158+
layer.strokeColor = self.progressColor.CGColor
159+
layer.lineWidth = self._arcWidth/1.5
160+
return layer
161+
}()
162+
163+
lazy private var _rotateLayer: CAShapeLayer = {
164+
let layer = CAShapeLayer()
165+
let subPath = UIBezierPath()
166+
subPath.moveToPoint(CGPointMake(self.bounds.width/2, 0))
167+
subPath.addLineToPoint(CGPointMake(self.bounds.width/2, self._arcWidth))
168+
layer.strokeColor = self.progressColor.CGColor
169+
layer.lineWidth = 1
170+
layer.path = subPath.CGPath
171+
layer.frame = self.bounds
172+
return layer
173+
}()
174+
175+
private var _circlePath: UIBezierPath {
176+
let side = self.bounds.width - self._arcWidth*2 - self._arcMargin*2
177+
return UIBezierPath(
178+
roundedRect: CGRectMake(bounds.width/2 - side/2, bounds.width/2 - side/2, side, side),
179+
cornerRadius: side/2
180+
)
181+
}
182+
183+
private var _roundRectPath: UIBezierPath {
184+
let side = bounds.width * 0.4242
185+
return UIBezierPath(
186+
roundedRect: CGRectMake(bounds.width/2 - side/2, bounds.width/2 - side/2, side, side),
187+
cornerRadius: side * 0.107
188+
)
189+
}
190+
191+
private var _startProgressAnimation: CAKeyframeAnimation {
192+
let frameCount = 60
193+
var paths = [CGPath]()
194+
var times = [CGFloat]()
195+
for i in 1...frameCount {
196+
let animationProgress = 1/CGFloat(frameCount) * CGFloat(i) - 0.01
197+
paths.append(self.p_arcPathWithProgress(animationProgress, clockwise: false).CGPath)
198+
times.append(CGFloat(i)*0.1)
199+
}
200+
let animation = CAKeyframeAnimation(keyPath: "path")
201+
animation.duration = _kstartAnimateDuration
202+
animation.values = paths
203+
return animation
204+
}
205+
206+
private var _startRotateAnimation: CABasicAnimation {
207+
let animation = CABasicAnimation(keyPath: "transform.rotation.z")
208+
animation.fromValue = 0
209+
animation.toValue = CGFloat(M_PI*2.0)
210+
animation.duration = _kstartAnimateDuration
211+
return animation
212+
}
213+
214+
private var _recordingAnimation: CAKeyframeAnimation {
215+
let frameCount = 60
216+
var paths = [CGPath]()
217+
for i in 1...frameCount {
218+
let animationProgress = 1/CGFloat(frameCount) * CGFloat(i)
219+
paths.append(self.p_arcPathWithProgress(animationProgress).CGPath)
220+
}
221+
for i in 1...frameCount {
222+
let animationProgress = 1/CGFloat(frameCount) * CGFloat(i) - 0.01
223+
paths.append(self.p_arcPathWithProgress(animationProgress, clockwise: false).CGPath)
224+
}
225+
let animation = CAKeyframeAnimation(keyPath: "path")
226+
animation.duration = 10
227+
animation.values = paths
228+
animation.beginTime = CACurrentMediaTime() + _kstartAnimateDuration
229+
animation.repeatCount = Float.infinity
230+
animation.calculationMode = kCAAnimationDiscrete
231+
return animation
232+
}
233+
234+
private var _recordingRotateAnimation: CABasicAnimation {
235+
let animation = CABasicAnimation(keyPath: "transform.rotation")
236+
animation.fromValue = 0
237+
animation.toValue = CGFloat(M_PI*2.0)
238+
animation.duration = 5
239+
animation.repeatCount = Float.infinity
240+
animation.beginTime = CACurrentMediaTime() + _kstartAnimateDuration
241+
return animation
242+
}
243+
244+
245+
/**************************************************************************/
246+
// MARK: - initialize
247+
/**************************************************************************/
248+
249+
public convenience init(frame: CGRect, shutterType: ShutterType, buttonColor: UIColor) {
250+
self.init(frame: frame)
251+
self.shutterType = shutterType
252+
self.buttonColor = buttonColor
253+
}
254+
255+
/**************************************************************************/
256+
// MARK: - Override
257+
/**************************************************************************/
258+
259+
override public var highlighted: Bool {
260+
didSet {
261+
_circleLayer.opacity = highlighted ? 0.5 : 1.0
262+
}
263+
}
264+
265+
public override func layoutSubviews() {
266+
super.layoutSubviews()
267+
if _arcLayer.superlayer != layer {
268+
layer.addSublayer(_arcLayer)
269+
}
270+
if _progressLayer.superlayer != layer {
271+
layer.addSublayer(_progressLayer)
272+
}
273+
if _rotateLayer.superlayer != layer {
274+
layer.insertSublayer(_rotateLayer, atIndex: 0)
275+
}
276+
if _circleLayer.superlayer != layer {
277+
layer.addSublayer(_circleLayer)
278+
}
279+
}
280+
281+
/**************************************************************************/
282+
// MARK: - Method
283+
/**************************************************************************/
284+
285+
private func p_arcPathWithProgress(progress: CGFloat, clockwise: Bool = true) -> UIBezierPath {
286+
let diameter = 2*CGFloat(M_PI)*(self.bounds.width/2 - self._arcWidth/3)
287+
let startAngle = clockwise ?
288+
-CGFloat(M_PI_2) :
289+
-CGFloat(M_PI_2) + CGFloat(M_PI)*(540/diameter)/180
290+
let endAngle = clockwise ?
291+
CGFloat(M_PI*2.0)*progress - CGFloat(M_PI_2) :
292+
CGFloat(M_PI*2.0)*progress - CGFloat(M_PI_2) + CGFloat(M_PI)*(540/diameter)/180
293+
let path = UIBezierPath(
294+
arcCenter: CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)),
295+
radius: self.bounds.width/2 - self._arcWidth/3,
296+
startAngle: startAngle,
297+
endAngle: endAngle,
298+
clockwise: clockwise
299+
)
300+
return path
301+
}
302+
303+
}

0 commit comments

Comments
 (0)