├── Classes └── MAActivityIndicatorView.swift ├── LICENCE.md ├── MAActivityIndicator-Demo ├── MAActivityIndicator-Demo.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── MAActivityIndicator-Demo.xccheckout │ │ └── xcuserdata │ │ │ ├── mazevedo.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ │ │ └── micazeve.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ ├── mazevedo.xcuserdatad │ │ ├── xcdebugger │ │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ │ ├── MAActivityIndicator-Demo.xcscheme │ │ │ └── xcschememanagement.plist │ │ └── micazeve.xcuserdatad │ │ └── xcschemes │ │ ├── MAActivityIndicator-Demo.xcscheme │ │ └── xcschememanagement.plist ├── MAActivityIndicator-Demo │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── 29@2x.png │ │ │ ├── 29@3x.png │ │ │ ├── 40@2x.png │ │ │ ├── 40@3x.png │ │ │ ├── 60@2x.png │ │ │ ├── 60@3x.png │ │ │ └── Contents.json │ ├── Info.plist │ └── ViewController.swift └── MAActivityIndicator-DemoTests │ ├── Info.plist │ └── MAActivityIndicator_DemoTests.swift ├── MAActivityIndicator.podspec ├── README.md └── Screenshots ├── Screenshot1.png ├── Screenshot2.png └── dots.png /Classes/MAActivityIndicatorView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MAActivityIndicatorView.swift 3 | // actIndicator 4 | // 5 | // Created by Michaël Azevedo on 09/02/2015. 6 | // Copyright (c) 2015 Michaël Azevedo. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | @objc public protocol MAActivityIndicatorViewDelegate 13 | { 14 | func activityIndicatorView(activityIndicatorView: MAActivityIndicatorView, 15 | circleBackgroundColorAtIndex index: NSInteger) -> UIColor 16 | 17 | } 18 | 19 | public class MAActivityIndicatorView: UIView { 20 | 21 | 22 | // MARK: - Private properties 23 | 24 | 25 | /// The number of circle indicators. 26 | private var _numberOfCircles = 5 27 | 28 | /// The base animation delay of each circle. 29 | private var delay = 0.2 30 | 31 | /// The base animation duration of each circle 32 | private var duration = 0.8 33 | 34 | /// Total animation duration 35 | private var _animationDuration:Double = 2 36 | 37 | /// The spacing between circles. 38 | private var _internalSpacing:CGFloat = 5 39 | 40 | /// The maximum radius of each circle. 41 | private var _maxRadius:CGFloat = 10 42 | 43 | // The minimum radius of each circle 44 | private let minRadius:CGFloat = 2 45 | 46 | /// Default color of each circle 47 | private var _defaultColor = UIColor.lightGrayColor() 48 | 49 | /// An indicator whether the activity indicator view is animating or not. 50 | private(set) public var isAnimating = false 51 | 52 | 53 | // MARK: - Public properties 54 | 55 | /// Delegate, used to chose the color of each circle. 56 | public var delegate:MAActivityIndicatorViewDelegate? 57 | 58 | //MARK: - Public computed properties 59 | 60 | /// The number of circle indicators. 61 | public var numberOfCircles:Int { 62 | get { 63 | return _numberOfCircles 64 | } 65 | set { 66 | _numberOfCircles = newValue 67 | delay = 2*duration/Double(numberOfCircles) 68 | updateCircles() 69 | } 70 | } 71 | 72 | /// Default color of each circle 73 | public var defaultColor:UIColor { 74 | get { 75 | return _defaultColor 76 | } 77 | set { 78 | _defaultColor = newValue 79 | updateCircles() 80 | } 81 | } 82 | 83 | /// Total animation duration 84 | public var animationDuration:Double { 85 | get { 86 | return _animationDuration 87 | } 88 | set { 89 | _animationDuration = newValue 90 | duration = _animationDuration/2 91 | delay = 2*duration/Double(numberOfCircles) 92 | updateCirclesanimations() 93 | } 94 | 95 | } 96 | 97 | /// The maximum radius of each circle. 98 | public var maxRadius:CGFloat { 99 | get { 100 | return _maxRadius 101 | } 102 | set { 103 | _maxRadius = newValue 104 | if _maxRadius < minRadius { 105 | _maxRadius = minRadius 106 | } 107 | updateCircles() 108 | } 109 | } 110 | 111 | /// The spacing between circles. 112 | public var internalSpacing:CGFloat { 113 | get { 114 | return _internalSpacing 115 | } 116 | set { 117 | _internalSpacing = newValue 118 | if (_internalSpacing * CGFloat(numberOfCircles-1) > CGRectGetWidth(self.frame)) { 119 | _internalSpacing = (CGRectGetWidth(self.frame) - CGFloat(numberOfCircles) * minRadius) / CGFloat(numberOfCircles-1) 120 | } 121 | updateCircles() 122 | } 123 | } 124 | 125 | 126 | // MARK: - override 127 | 128 | public convenience init () { 129 | self.init(frame:CGRectMake(0, 0, 100, 50)) 130 | } 131 | 132 | public override init(frame: CGRect) { 133 | super.init(frame: frame) 134 | setupDefaults() 135 | } 136 | 137 | 138 | public required init(coder aDecoder: NSCoder) { 139 | super.init(coder: aDecoder) 140 | setupDefaults() 141 | } 142 | 143 | public override func translatesAutoresizingMaskIntoConstraints() -> Bool { 144 | return false 145 | } 146 | 147 | // MARK: - private methods 148 | 149 | // Sets up defaults values 150 | private func setupDefaults() { 151 | numberOfCircles = 5 152 | internalSpacing = 5 153 | maxRadius = 10 154 | animationDuration = 2 155 | defaultColor = UIColor.lightGrayColor() 156 | 157 | } 158 | 159 | /// Creates the circle view. 160 | /// 161 | /// :param: radius The radius of the circle. 162 | /// :param: color The background color of the circle. 163 | /// :param: positionX The x-position of the circle in the contentView. 164 | /// :param: posX The x-position of the circle in the contentView. 165 | /// :param: posY The y-position of the circle in the contentView. 166 | /// 167 | /// :returns: The circle view 168 | private func createCircleWithRadius(radius:CGFloat, color:UIColor, posX:CGFloat, posY:CGFloat) -> UIView { 169 | let circle = UIView(frame: CGRect(x: posX, y: posY, width: radius*2, height: radius*2)) 170 | circle.backgroundColor = color 171 | circle.layer.cornerRadius = radius 172 | circle.setTranslatesAutoresizingMaskIntoConstraints(false) 173 | return circle 174 | } 175 | 176 | /// Creates the animation of the circle. 177 | /// 178 | /// :param: duration The duration of the animation. 179 | /// :param: delay The delay of the animation 180 | /// 181 | /// :returns: The animation of the circle. 182 | private func createAnimationWithDuration(duration:Double, delay:Double) -> CABasicAnimation { 183 | let animation = CABasicAnimation(keyPath:"transform.scale") 184 | animation.delegate = self 185 | animation.fromValue = 0 186 | animation.toValue = 1 187 | animation.autoreverses = true 188 | animation.duration = duration 189 | animation.removedOnCompletion = false 190 | animation.beginTime = CACurrentMediaTime() + delay 191 | animation.repeatCount = MAXFLOAT 192 | animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) 193 | return animation 194 | } 195 | 196 | /// Add the circles 197 | private func addCircles () { 198 | var color = defaultColor 199 | 200 | var radiusForCircle = (CGRectGetWidth(self.frame) - CGFloat(numberOfCircles-1)*internalSpacing)/CGFloat(2*numberOfCircles) 201 | if radiusForCircle > CGRectGetHeight(self.frame)/2 { 202 | radiusForCircle = CGRectGetHeight(self.frame)/2 203 | } 204 | 205 | if radiusForCircle > maxRadius { 206 | radiusForCircle = maxRadius 207 | } 208 | 209 | var widthUsed = 2*radiusForCircle * CGFloat(numberOfCircles) + CGFloat(numberOfCircles-1)*internalSpacing 210 | if widthUsed > CGRectGetWidth(self.frame) { 211 | widthUsed = CGRectGetWidth(self.frame) 212 | } 213 | 214 | let offsetX = (CGRectGetWidth(self.frame) - widthUsed)/2 215 | 216 | let posY = (CGRectGetHeight(self.frame) - 2*radiusForCircle)/2 217 | 218 | for i in 0..