├── sample.gif
├── playground.png
├── sample.playground
├── contents.xcplayground
├── playground.xcworkspace
│ └── contents.xcworkspacedata
└── Contents.swift
├── README.md
└── LICENSE
/sample.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tueno/MaterialLoadingIndicator/HEAD/sample.gif
--------------------------------------------------------------------------------
/playground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tueno/MaterialLoadingIndicator/HEAD/playground.png
--------------------------------------------------------------------------------
/sample.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/sample.playground/playground.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MaterialLoadingIndicator
2 | Materialish progress indicator sample code for iOS.
3 |
4 | * Written by Swift3.0 (Thanks @pawankmrai)
5 | * You can copy classes from sample.playground for use in your project.
6 |
7 | 
8 | 
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Tueno
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/sample.playground/Contents.swift:
--------------------------------------------------------------------------------
1 | //: Playground - noun: a place where people can play
2 | //
3 | // Created by Tueno on 2016/07/04.
4 | //
5 | import UIKit
6 | import PlaygroundSupport
7 |
8 | // MARK: -
9 | class MaterialLoadingIndicator: UIView {
10 |
11 | let MinStrokeLength: CGFloat = 0.05
12 | let MaxStrokeLength: CGFloat = 0.7
13 | let circleShapeLayer = CAShapeLayer()
14 |
15 | override init(frame: CGRect) {
16 | super.init(frame: frame)
17 | backgroundColor = UIColor.clear
18 | initShapeLayer()
19 | }
20 |
21 | required init?(coder aDecoder: NSCoder) {
22 | fatalError("init(coder:) has not been implemented")
23 | }
24 |
25 | func initShapeLayer() {
26 | circleShapeLayer.actions = ["strokeEnd" : NSNull(),
27 | "strokeStart" : NSNull(),
28 | "transform" : NSNull(),
29 | "strokeColor" : NSNull()]
30 | circleShapeLayer.backgroundColor = UIColor.clear.cgColor
31 | circleShapeLayer.strokeColor = UIColor.blue.cgColor
32 | circleShapeLayer.fillColor = UIColor.clear.cgColor
33 | circleShapeLayer.lineWidth = 5
34 | circleShapeLayer.lineCap = kCALineCapRound
35 | circleShapeLayer.strokeStart = 0
36 | circleShapeLayer.strokeEnd = MinStrokeLength
37 | let center = CGPoint(x: bounds.width*0.5, y: bounds.height*0.5)
38 | circleShapeLayer.frame = bounds
39 | circleShapeLayer.path = UIBezierPath(arcCenter: center,
40 | radius: center.x,
41 | startAngle: 0,
42 | endAngle: CGFloat(Double.pi*2),
43 | clockwise: true).cgPath
44 | layer.addSublayer(circleShapeLayer)
45 | }
46 |
47 | func startAnimating() {
48 | if layer.animation(forKey: "rotation") == nil {
49 | startColorAnimation()
50 | startStrokeAnimation()
51 | startRotatingAnimation()
52 | }
53 | }
54 |
55 | private func startColorAnimation() {
56 | let color = CAKeyframeAnimation(keyPath: "strokeColor")
57 | color.duration = 10.0
58 | color.values = [UIColor(hex: 0x4285F4, alpha: 1.0).cgColor,
59 | UIColor(hex: 0xDE3E35, alpha: 1.0).cgColor,
60 | UIColor(hex: 0xF7C223, alpha: 1.0).cgColor,
61 | UIColor(hex: 0x1B9A59, alpha: 1.0).cgColor,
62 | UIColor(hex: 0x4285F4, alpha: 1.0).cgColor]
63 | color.calculationMode = kCAAnimationPaced
64 | color.repeatCount = Float.infinity
65 | circleShapeLayer.add(color, forKey: "color")
66 | }
67 |
68 | private func startRotatingAnimation() {
69 | let rotation =
70 | CABasicAnimation(keyPath: "transform.rotation.z")
71 | rotation.toValue = Double.pi*2
72 | rotation.duration = 2.2
73 | rotation.isCumulative = true
74 | rotation.isAdditive = true
75 | rotation.repeatCount = Float.infinity
76 | layer.add(rotation, forKey: "rotation")
77 | }
78 |
79 | private func startStrokeAnimation() {
80 | let easeInOutSineTimingFunc = CAMediaTimingFunction(controlPoints: 0.39, 0.575, 0.565, 1.0)
81 | let progress: CGFloat = MaxStrokeLength
82 | let endFromValue: CGFloat = circleShapeLayer.strokeEnd
83 | let endToValue: CGFloat = endFromValue + progress
84 | let strokeEnd = CABasicAnimation(keyPath: "strokeEnd")
85 | strokeEnd.fromValue = endFromValue
86 | strokeEnd.toValue = endToValue
87 | strokeEnd.duration = 0.5
88 | strokeEnd.fillMode = kCAFillModeForwards
89 | strokeEnd.timingFunction = easeInOutSineTimingFunc
90 | strokeEnd.beginTime = 0.1
91 | strokeEnd.isRemovedOnCompletion = false
92 | let startFromValue: CGFloat = circleShapeLayer.strokeStart
93 | let startToValue: CGFloat = fabs(endToValue - MinStrokeLength)
94 | let strokeStart = CABasicAnimation(keyPath: "strokeStart")
95 | strokeStart.fromValue = startFromValue
96 | strokeStart.toValue = startToValue
97 | strokeStart.duration = 0.4
98 | strokeStart.fillMode = kCAFillModeForwards
99 | strokeStart.timingFunction = easeInOutSineTimingFunc
100 | strokeStart.beginTime = strokeEnd.beginTime + strokeEnd.duration + 0.2
101 | strokeStart.isRemovedOnCompletion = false
102 | let pathAnim = CAAnimationGroup()
103 | pathAnim.animations = [strokeEnd, strokeStart]
104 | pathAnim.duration = strokeStart.beginTime + strokeStart.duration
105 | pathAnim.fillMode = kCAFillModeForwards
106 | pathAnim.isRemovedOnCompletion = false
107 | CATransaction.begin()
108 | CATransaction.setCompletionBlock {
109 | if self.circleShapeLayer.animation(forKey: "stroke") != nil {
110 | self.circleShapeLayer.transform = CATransform3DRotate(self.circleShapeLayer.transform, CGFloat(Double.pi*2) * progress, 0, 0, 1)
111 | self.circleShapeLayer.removeAnimation(forKey: "stroke")
112 | self.startStrokeAnimation()
113 | }
114 | }
115 | circleShapeLayer.add(pathAnim, forKey: "stroke")
116 | CATransaction.commit()
117 | }
118 |
119 | func stopAnimating() {
120 | circleShapeLayer.removeAllAnimations()
121 | layer.removeAllAnimations()
122 | circleShapeLayer.transform = CATransform3DIdentity
123 | layer.transform = CATransform3DIdentity
124 | }
125 |
126 | }
127 |
128 | extension UIColor {
129 |
130 | convenience init(hex: UInt, alpha: CGFloat) {
131 | self.init(
132 | red: CGFloat((hex & 0xFF0000) >> 16) / 255.0,
133 | green: CGFloat((hex & 0x00FF00) >> 8) / 255.0,
134 | blue: CGFloat(hex & 0x0000FF) / 255.0,
135 | alpha: CGFloat(alpha)
136 | )
137 | }
138 |
139 | }
140 |
141 | // MARK: -
142 | let view = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 568))
143 | let indicator = MaterialLoadingIndicator(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
144 | indicator.center = CGPoint(x: 320*0.5, y: 568*0.5)
145 | view.addSubview(indicator)
146 | PlaygroundPage.current.liveView = view
147 | indicator.startAnimating()
148 |
--------------------------------------------------------------------------------