├── 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 | ![preview](https://github.com/Tueno/MaterialLoadingIndicator/blob/master/sample.gif?raw=true) 8 | ![preview](https://github.com/Tueno/MaterialLoadingIndicator/blob/master/playground.png?raw=true) 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 | --------------------------------------------------------------------------------