├── .swift-version ├── Classes ├── MAAnimatedMultiGearView.swift ├── MAGear.swift ├── MAGearRefreshControl.swift ├── MAMultiGearView.swift └── MASingleGearView.swift ├── LICENSE.md ├── MAGearRefreshControl-Demo ├── MAGearRefreshControl-Demo.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── MAGearRefreshControl-Demo.xccheckout │ │ └── xcuserdata │ │ │ ├── mazevedo.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ │ │ └── micazeve.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ ├── mazevedo.xcuserdatad │ │ ├── xcdebugger │ │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ │ ├── MAGearRefreshControl-Demo.xcscheme │ │ │ └── xcschememanagement.plist │ │ └── micazeve.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ ├── MAGearRefreshControl-Demo.xcscheme │ │ └── xcschememanagement.plist ├── MAGearRefreshControl-Demo │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard │ ├── DemoTableViewController.swift │ ├── DemoViewController.swift │ ├── 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 │ └── UIColor + RGBInit.swift └── MAGearRefreshControl-DemoTests │ ├── Info.plist │ └── MAGearRefreshControl_DemoTests.swift ├── MAGearRefreshControl.podspec ├── README.md └── Screenshots ├── Anim.gif └── BranchedGear.png /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 2 | -------------------------------------------------------------------------------- /Classes/MAAnimatedMultiGearView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MAAnimatedMultiGearView.swift 3 | // MAGearRefreshControl-Demo 4 | // 5 | // Created by Michaël Azevedo on 20/02/2017. 6 | // Copyright © 2017 micazeve. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | //MARK: - MAAnimatedMultiGearView Class 12 | 13 | /// This class is used to draw and animate multiple gears 14 | 15 | public class MAAnimatedMultiGearView: MAMultiGearView { 16 | 17 | //MARK: Instance properties 18 | 19 | 20 | /// Enum representing the animation style 21 | internal enum MAGearRefreshAnimationStyle: UInt8 { 22 | case singleGear // Only the main gear is rotating when the data is refreshing 23 | case keepGears // All the gear are still visible during the refresh and disappear only when its finished 24 | } 25 | 26 | /// Animation style of the refresh control 27 | internal var style = MAGearRefreshAnimationStyle.keepGears 28 | 29 | /// Array of rotational angle for the refresh 30 | fileprivate var arrayOfRotationAngle:[CGFloat] = [180] 31 | 32 | /// Workaround for the issue with the CGAffineTransformRotate (when angle > Double.pi its rotate clockwise beacause it's shorter) 33 | fileprivate var divisionFactor: CGFloat = 1 34 | 35 | /// Variable used to rotate or no the gear 36 | var stopRotation = true 37 | 38 | /// Boolean used to know if the view is already animated 39 | var isRotating = false 40 | 41 | //MARK: Various methods 42 | 43 | /// Override of the `addLinkedGear` method in order to update the array of rotational angle when a gear is added 44 | override public func addLinkedGear(_ gearLinked: Int, nbTeeth:UInt, color:UIColor, angleInDegree:Double, gearStyle:MASingleGearView.MAGearStyle = .Normal, nbBranches:UInt = 5) -> Bool { 45 | 46 | if !super.addLinkedGear(gearLinked, nbTeeth: nbTeeth, color: color, angleInDegree: angleInDegree, gearStyle: gearStyle, nbBranches: nbBranches) { 47 | return false 48 | } 49 | 50 | let ratio = CGFloat(arrayViews[gearLinked].gear.nbTeeth) / CGFloat(arrayViews[arrayViews.count - 1].gear.nbTeeth) 51 | let newAngle = -1 * arrayOfRotationAngle[gearLinked] * ratio 52 | /* 53 | NSLog("addLinkedGear \(gearLinked) , \(nbTeeth) , \(angleInDegree)") 54 | 55 | NSLog(" angleOtherGear : \(arrayOfRotationAngle[gearLinked])") 56 | NSLog(" ratio : \(ratio)") 57 | NSLog(" newAngle : \(newAngle)") 58 | */ 59 | 60 | arrayOfRotationAngle.append(newAngle) 61 | 62 | let angleScaled = 1+floor(abs(newAngle)/180) 63 | 64 | if angleScaled > divisionFactor { 65 | divisionFactor = angleScaled 66 | } 67 | 68 | return true 69 | } 70 | 71 | 72 | /// Method called to rotate the main gear by 360 degree 73 | internal func rotate() { 74 | 75 | if !stopRotation && !isRotating { 76 | isRotating = true 77 | 78 | let duration = TimeInterval(1/divisionFactor) 79 | /* 80 | NSLog("rotation 0 \(self.arrayOfRotationAngle[0] / 180 * CGFloat(Double.pi) / self.divisionFactor)" ) 81 | NSLog(" -> duration : \(duration)") 82 | */ 83 | UIView.animate(withDuration: duration, delay: 0, options: .curveLinear, animations: { () -> Void in 84 | 85 | switch self.style { 86 | case .singleGear: 87 | self.arrayViews[0].transform = self.arrayViews[0].transform.rotated(by: self.arrayOfRotationAngle[0] / 180 * CGFloat(Double.pi)) 88 | case .keepGears: 89 | for i in 0.. Void in 97 | // NSLog(" -> completion \(finished)") 98 | self.isRotating = false 99 | self.rotate() 100 | }) 101 | } 102 | } 103 | 104 | /// Public method to start rotating 105 | public func startRotating() { 106 | stopRotation = false 107 | rotate() 108 | } 109 | 110 | 111 | /// Public method to start rotating 112 | public func stopRotating() { 113 | stopRotation = true 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Classes/MAGear.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MAGear.swift 3 | // MAGearRefreshControl-Demo 4 | // 5 | // Created by Michaël Azevedo on 20/02/2017. 6 | // Copyright © 2017 micazeve. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | //MARK: - MAGear Class 12 | 13 | /// This class represents a gear in the most abstract way, without any graphical code related. 14 | public class MAGear { 15 | 16 | //MARK: Instance properties 17 | 18 | /// The circle on which two gears effectively mesh, about halfway through the tooth. 19 | let pitchDiameter:CGFloat 20 | 21 | /// Diameter of the gear, measured from the tops of the teeth. 22 | let outsideDiameter:CGFloat 23 | 24 | /// Diameter of the gear, measured at the base of the teeth. 25 | let insideDiameter:CGFloat 26 | 27 | /// The number of teeth per inch of the circumference of the pitch diameter. The diametral pitch of all meshing gears must be the same. 28 | let diametralPitch:CGFloat 29 | 30 | /// Number of teeth of the gear. 31 | let nbTeeth:UInt 32 | 33 | 34 | //MARK: Init method 35 | 36 | /// Init method. 37 | /// 38 | /// - parameter radius: of the gear 39 | /// - parameter nbTeeth: Number of teeth of the gear. Must be greater than 2. 40 | public init (radius:CGFloat, nbTeeth:UInt) { 41 | 42 | assert(nbTeeth > 2) 43 | 44 | self.pitchDiameter = 2*radius 45 | self.diametralPitch = CGFloat(nbTeeth)/pitchDiameter 46 | self.outsideDiameter = CGFloat((nbTeeth+2))/diametralPitch 47 | self.insideDiameter = CGFloat((nbTeeth-2))/diametralPitch 48 | self.nbTeeth = nbTeeth 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Classes/MAGearRefreshControl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MAGearRefreshControl.swift 3 | // 4 | // Created by Michaël Azevedo on 20/05/2015. 5 | // Copyright (c) 2015 micazeve. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | 10 | 11 | ///MARK: - MAGearRefreshDelegate protocol 12 | 13 | 14 | /// Protocol between the MAGearRefreshControl and its delegate (mostly UITableViewController). 15 | @objc public protocol MAGearRefreshDelegate { 16 | 17 | /// Method called when the pull to refresh move was triggered. 18 | /// 19 | /// - parameter view: The MAGearRefreshControl object. 20 | func MAGearRefreshTableHeaderDidTriggerRefresh(_ view:MAGearRefreshControl) 21 | 22 | /// Method called to know if the data source is loading or no 23 | /// 24 | /// - parameter view: The MAGearRefreshControl object. 25 | /// 26 | /// - returns: true if the datasource is loading, false otherwise 27 | func MAGearRefreshTableHeaderDataSourceIsLoading(_ view:MAGearRefreshControl) -> Bool 28 | } 29 | 30 | 31 | 32 | 33 | //MARK: - MAGearRefreshControl Class 34 | 35 | /// This class is used to draw an animated group of gears and offers the same interactions as an UIRefreshControl 36 | public class MAGearRefreshControl: MAAnimatedMultiGearView { 37 | 38 | //MARK: Instance properties 39 | 40 | /// Enum representing the different state of the refresh control 41 | public enum MAGearRefreshState: UInt8 { 42 | case normal // The user is pulling but hasn't reach the activation threshold yet 43 | case pulling // The user is still pulling and has passed the activation threshold 44 | case loading // The refresh control is animating 45 | } 46 | 47 | /// State of the refresh control 48 | fileprivate var state = MAGearRefreshState.normal 49 | 50 | /// Delegate conforming to the MAGearRefreshDelegate protocol. Most of time it's an UITableViewController 51 | public var delegate:MAGearRefreshDelegate? 52 | 53 | /// Content offset of the tableview 54 | fileprivate var contentOffset:CGFloat = 0 55 | 56 | /// Variable used to allow the end of the refresh 57 | /// We must wait for the end of the animation of the contentInset before allowing the refresh 58 | fileprivate var endRefreshAllowed = false 59 | 60 | /// Variable used to know if the end of the refresh has been asked 61 | fileprivate var endRefreshAsked = false 62 | 63 | 64 | 65 | 66 | //MARK: Various methods 67 | 68 | /// Set the state of the refresh control. 69 | /// 70 | /// - parameter aState: New state of the refresh control. 71 | fileprivate func setState(_ aState:MAGearRefreshState) { 72 | NSLog("setState : \(aState.rawValue)") 73 | switch aState { 74 | 75 | case .loading: 76 | self.rotate() 77 | if style == .singleGear { 78 | 79 | UIView.animate(withDuration: 0.5, animations: { () -> Void in 80 | for i in 1.. -65 && scrollView.contentOffset.y < 0 && !loading { 119 | setState(.normal) 120 | } else if state == .normal && scrollView.contentOffset.y < -65 && !loading { 121 | setState(.pulling) 122 | } 123 | 124 | 125 | if (scrollView.contentInset.top != 0) { 126 | scrollView.contentInset = UIEdgeInsets.zero; 127 | } 128 | } 129 | 130 | let phase = -Double(scrollView.contentOffset.y/20) 131 | 132 | if stopRotation { 133 | setMainGearPhase(phase) 134 | } 135 | } 136 | } 137 | 138 | /// Method to call when the scrollview ended dragging 139 | /// 140 | /// - parameter scrollView: The scrollview. 141 | public func MAGearRefreshScrollViewDidEndDragging(_ scrollView:UIScrollView) { 142 | 143 | NSLog("MAGearRefreshScrollViewDidEndDragging") 144 | /*if state == .Loading { 145 | NSLog("return") 146 | return 147 | }*/ 148 | 149 | var loading = false 150 | 151 | if let load = delegate?.MAGearRefreshTableHeaderDataSourceIsLoading(self) { 152 | loading = load 153 | } 154 | 155 | if scrollView.contentOffset.y <= -65.0 && !loading { 156 | 157 | self.stopRotation = false 158 | delegate?.MAGearRefreshTableHeaderDidTriggerRefresh(self) 159 | 160 | setState(.loading) 161 | 162 | let contentOffset = scrollView.contentOffset 163 | 164 | UIView.animate(withDuration: 0.2, animations: { () -> Void in 165 | 166 | scrollView.contentInset = UIEdgeInsetsMake(60, 0, 0, 0) 167 | scrollView.contentOffset = contentOffset; // Workaround for smooth transition on iOS8 168 | }, completion: { (completed) -> Void in 169 | NSLog("completed") 170 | let dispatchTime: DispatchTime = DispatchTime.now() + Double(Int64(0.6 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC) 171 | DispatchQueue.main.asyncAfter(deadline: dispatchTime, execute: { 172 | // your function here 173 | self.endRefreshAllowed = true 174 | if self.endRefreshAsked { 175 | NSLog("self.endRefreshAsked") 176 | self.endRefreshAsked = false 177 | self.MAGearRefreshScrollViewDataSourceDidFinishedLoading(scrollView) 178 | } 179 | }) 180 | 181 | 182 | }) 183 | 184 | } 185 | } 186 | 187 | /// Method to call when the datasource finished loading 188 | /// 189 | /// - parameter scrollView: The scrollview. 190 | public func MAGearRefreshScrollViewDataSourceDidFinishedLoading(_ scrollView:UIScrollView) { 191 | 192 | NSLog("MAGearRefreshScrollViewDataSourceDidFinishedLoading") 193 | 194 | if !endRefreshAllowed { 195 | endRefreshAsked = true 196 | return 197 | } 198 | endRefreshAllowed = false 199 | self.setState(.normal) 200 | 201 | scrollView.isUserInteractionEnabled = false 202 | 203 | UIView.animate(withDuration: 0.1, animations: { () -> Void in 204 | self.arrayViews[0].transform = CGAffineTransform(scaleX: 1.2, y: 1.2) 205 | }, completion: { (finished) -> Void in 206 | 207 | UIView.animate(withDuration: 0.3, animations: { () -> Void in 208 | 209 | if self.style == .keepGears { 210 | for i in 1.. Void in 219 | scrollView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0) 220 | scrollView.contentOffset = CGPoint(x: 0, y: 0); // Workaround for smooth transition on iOS8 221 | }, completion: { (finished) -> Void in 222 | self.stopRotation = true 223 | scrollView.isUserInteractionEnabled = true 224 | for view in self.arrayViews { 225 | view.alpha = 1 226 | view.transform = CGAffineTransform.identity 227 | 228 | } 229 | }) 230 | }) 231 | } 232 | 233 | 234 | //MARK: View configuration 235 | 236 | /// Method to configure the view with an Y offset of the scrollview 237 | /// 238 | /// - parameter offset: Offset of the scrollView 239 | fileprivate func configureWithContentOffsetY(_ offset:CGFloat) 240 | { 241 | contentOffset = offset 242 | configureView() 243 | } 244 | 245 | /// Override of configureView(). The override is needed since we don't want the first gear to be centered within the view. 246 | /// Instead, we want it to be centered within the visible part of the view 247 | override internal func configureView() { 248 | if arrayViews.count == 0 { 249 | return 250 | } 251 | 252 | arrayViews[0].center.x = frame.size.width/2 253 | arrayViews[0].center.y = frame.height - contentOffset/2 254 | 255 | 256 | for i in 1.. the 3rd gear is linked to the 2nd one. 54 | internal var arrayRelations:[Int] = [0] 55 | 56 | /// Angles between the gears, in degree, according to the unit circle 57 | /// Ex. arrayAngles[3] -> the angle between the 3rd gear and its linked one 58 | internal var arrayAngles:[Double] = [0] 59 | 60 | 61 | //MARK: Init methods 62 | 63 | /// Default initializer 64 | override public init(frame: CGRect) { 65 | super.init(frame: frame) 66 | 67 | clipsToBounds = true 68 | 69 | leftBorderView = UIView(frame:CGRect(x: barMargin, y: 0, width: barWidth, height: frame.height)) 70 | leftBorderView.backgroundColor = barColor 71 | 72 | rightBorderView = UIView(frame:CGRect(x: frame.width - barMargin - barWidth, y: 0, width: barWidth, height: frame.height)) 73 | rightBorderView.backgroundColor = barColor 74 | 75 | 76 | addSubview(leftBorderView) 77 | addSubview(rightBorderView) 78 | } 79 | 80 | /// Required initializer 81 | required public init(coder aDecoder: NSCoder) { 82 | fatalError("init(coder:) has not been implemented") 83 | } 84 | 85 | 86 | //MARK: Method to add gears 87 | 88 | /// Add the initial gear to the view. It is always centered in the view. 89 | /// 90 | /// - parameter nbTeeth: Number of teeth of the gear. 91 | /// - parameter color: Color of the gear. 92 | /// - parameter radius: Radius in pixel of the gear 93 | /// 94 | /// - returns: true if the gear was succesfully created, false otherwise (if at least one gear exists). 95 | public func addInitialGear(nbTeeth:UInt, color: UIColor, radius:CGFloat) -> Bool { 96 | 97 | if arrayViews.count > 0 { 98 | return false 99 | } 100 | 101 | diametralPitch = CGFloat(nbTeeth)/(2*radius) 102 | 103 | let gear = MAGear(radius: radius, nbTeeth: nbTeeth) 104 | 105 | let view = MASingleGearView(gear: gear, gearColor:color) 106 | view.phase = 0 107 | 108 | view.center = CGPoint(x: self.bounds.size.width/2, y: self.bounds.size.height/2) 109 | 110 | arrayViews.append(view) 111 | self.insertSubview(view, belowSubview: leftBorderView) 112 | 113 | return true 114 | } 115 | /// Add another gear to the view and link it to another already existing gear 116 | /// 117 | /// - parameter gearLinked: Index of the previously created gear 118 | /// - parameter nbTeeth: Number of teeth of the gear. 119 | /// - parameter color: Color of the gear. 120 | /// - parameter angleInDegree: Angle (in degree) between the gear to create and the previous gear, according to the unit circle. 121 | /// - parameter style: Style of the gear 122 | /// - parameter nbBranches: Number of branches if the gear style is 'WithBranches' 123 | /// 124 | /// - returns: true if the gear was succesfully created, false otherwise (if the gearLinked index is incorrect). 125 | public func addLinkedGear(_ gearLinked: Int, nbTeeth:UInt, color:UIColor, angleInDegree:Double, gearStyle:MASingleGearView.MAGearStyle = .Normal, nbBranches:UInt = 5) -> Bool { 126 | 127 | if gearLinked >= arrayViews.count || gearLinked < 0 { 128 | return false 129 | } 130 | 131 | let linkedGearView = arrayViews[gearLinked] 132 | let linkedGear = linkedGearView.gear 133 | 134 | let newRadius = CGFloat(nbTeeth)/(2*diametralPitch) 135 | 136 | let gear = MAGear(radius:newRadius, nbTeeth: nbTeeth) 137 | 138 | let dist = Double(gear.pitchDiameter + (linkedGear?.pitchDiameter)!)/2 139 | 140 | let xValue = CGFloat(dist*cos(angleInDegree*Double.pi/180)) 141 | let yValue = CGFloat(-dist*sin(angleInDegree*Double.pi/180)) 142 | 143 | 144 | let angleBetweenMainTeethsInDegree = 360/Double((linkedGear?.nbTeeth)!) 145 | 146 | let nbDentsPassees = angleInDegree / angleBetweenMainTeethsInDegree 147 | let phaseForAngle = nbDentsPassees - Double(Int(nbDentsPassees)) 148 | 149 | 150 | var phaseNewGearForAngle = 0.5 + phaseForAngle - linkedGearView.phase 151 | if gear.nbTeeth%2 == 1 { 152 | phaseNewGearForAngle += 0.5 153 | } 154 | phaseNewGearForAngle = phaseNewGearForAngle - trunc(phaseNewGearForAngle) 155 | 156 | let angleBetweenNewTeethsInDegree = 360/Double(gear.nbTeeth) 157 | let nbNewDentsPassees = angleInDegree / angleBetweenNewTeethsInDegree 158 | let phaseForNewAngle = 1-(nbNewDentsPassees - Double(Int(nbNewDentsPassees))) 159 | 160 | 161 | let view = MASingleGearView(gear: gear, gearColor:color, style:gearStyle, nbBranches: nbBranches) 162 | view.center = CGPoint(x: linkedGearView.center.x + xValue, y: linkedGearView.center.y + yValue) 163 | 164 | arrayRelations.append(gearLinked) 165 | arrayAngles.append(angleInDegree) 166 | view.phase = phaseNewGearForAngle - phaseForNewAngle 167 | 168 | arrayViews.append(view) 169 | self.insertSubview(view, belowSubview: leftBorderView) 170 | return true 171 | } 172 | 173 | 174 | /// Set the phase for the first gear and calculate it for all the linked gears 175 | /// 176 | /// - parameter phase: Each incrementation of the phase means the gear rotated from one tooth 177 | public func setMainGearPhase(_ phase:Double) { 178 | if arrayViews.count == 0 { 179 | return 180 | } 181 | 182 | 183 | let newPhase = phase 184 | 185 | arrayViews[0].phase = newPhase 186 | 187 | for i in 1.. 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MAGearRefreshControl-Demo/MAGearRefreshControl-Demo.xcodeproj/project.xcworkspace/xcshareddata/MAGearRefreshControl-Demo.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | B7563A8C-C522-4D2E-BB08-E96704E9C0C8 9 | IDESourceControlProjectName 10 | MAGearRefreshControl-Demo 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | 63C4C0D16683D2B74A0A2D02EEE7DD92EE15A282 14 | https://github.com/micazeve/MAGearRefreshControl.git 15 | 16 | IDESourceControlProjectPath 17 | MAGearRefreshControl-Demo/MAGearRefreshControl-Demo.xcodeproj 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | 63C4C0D16683D2B74A0A2D02EEE7DD92EE15A282 21 | ../../.. 22 | 23 | IDESourceControlProjectURL 24 | https://github.com/micazeve/MAGearRefreshControl.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | 63C4C0D16683D2B74A0A2D02EEE7DD92EE15A282 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | 63C4C0D16683D2B74A0A2D02EEE7DD92EE15A282 36 | IDESourceControlWCCName 37 | MAGearRefreshControl 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /MAGearRefreshControl-Demo/MAGearRefreshControl-Demo.xcodeproj/project.xcworkspace/xcuserdata/mazevedo.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micazeve/MAGearRefreshControl/e30acacf57a43fb0c20fcbbda7f761e49c1ef3c7/MAGearRefreshControl-Demo/MAGearRefreshControl-Demo.xcodeproj/project.xcworkspace/xcuserdata/mazevedo.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /MAGearRefreshControl-Demo/MAGearRefreshControl-Demo.xcodeproj/project.xcworkspace/xcuserdata/micazeve.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micazeve/MAGearRefreshControl/e30acacf57a43fb0c20fcbbda7f761e49c1ef3c7/MAGearRefreshControl-Demo/MAGearRefreshControl-Demo.xcodeproj/project.xcworkspace/xcuserdata/micazeve.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /MAGearRefreshControl-Demo/MAGearRefreshControl-Demo.xcodeproj/xcuserdata/mazevedo.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /MAGearRefreshControl-Demo/MAGearRefreshControl-Demo.xcodeproj/xcuserdata/mazevedo.xcuserdatad/xcschemes/MAGearRefreshControl-Demo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /MAGearRefreshControl-Demo/MAGearRefreshControl-Demo.xcodeproj/xcuserdata/mazevedo.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | MAGearRefreshControl-Demo.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 83D9CA1F1B0CC44D00ECA1F5 16 | 17 | primary 18 | 19 | 20 | 83D9CA341B0CC44D00ECA1F5 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /MAGearRefreshControl-Demo/MAGearRefreshControl-Demo.xcodeproj/xcuserdata/micazeve.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /MAGearRefreshControl-Demo/MAGearRefreshControl-Demo.xcodeproj/xcuserdata/micazeve.xcuserdatad/xcschemes/MAGearRefreshControl-Demo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 75 | 77 | 83 | 84 | 85 | 86 | 87 | 88 | 94 | 96 | 102 | 103 | 104 | 105 | 107 | 108 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /MAGearRefreshControl-Demo/MAGearRefreshControl-Demo.xcodeproj/xcuserdata/micazeve.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | MAGearRefreshControl-Demo.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 83D9CA1F1B0CC44D00ECA1F5 16 | 17 | primary 18 | 19 | 20 | 83D9CA341B0CC44D00ECA1F5 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /MAGearRefreshControl-Demo/MAGearRefreshControl-Demo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // MAGearRefreshControl-Demo 4 | // 5 | // Created by Michaël Azevedo on 20/05/2015. 6 | // Copyright (c) 2015 micazeve. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | 15 | 16 | var window: UIWindow? 17 | 18 | 19 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 20 | // Override point for customization after application launch. 21 | 22 | return true 23 | } 24 | 25 | func applicationWillResignActive(_ application: UIApplication) { 26 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 27 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 28 | } 29 | 30 | func applicationDidEnterBackground(_ application: UIApplication) { 31 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | func applicationWillEnterForeground(_ application: UIApplication) { 36 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 37 | } 38 | 39 | func applicationDidBecomeActive(_ application: UIApplication) { 40 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 41 | } 42 | 43 | func applicationWillTerminate(_ application: UIApplication) { 44 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 45 | } 46 | 47 | 48 | } 49 | 50 | -------------------------------------------------------------------------------- /MAGearRefreshControl-Demo/MAGearRefreshControl-Demo/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /MAGearRefreshControl-Demo/MAGearRefreshControl-Demo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 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 | 105 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /MAGearRefreshControl-Demo/MAGearRefreshControl-Demo/DemoTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoTableViewController.swift 3 | // MAGearRefreshControl-Demo 4 | // 5 | // Created by Michaël Azevedo on 20/05/2015. 6 | // Copyright (c) 2015 micazeve. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DemoTableViewController: UITableViewController, MAGearRefreshDelegate { 12 | 13 | //MARK: - Instance properties 14 | var refreshControlView : MAGearRefreshControl! 15 | var isLoading = false 16 | 17 | //MARK: - Init methods 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | 22 | self.navigationController?.navigationBar.isTranslucent = false 23 | 24 | 25 | refreshControlView = MAGearRefreshControl(frame: CGRect(x: 0, y: -self.tableView.bounds.height, width: self.view.frame.width, height: self.tableView.bounds.height)) 26 | refreshControlView.backgroundColor = UIColor.initRGB(34, g: 75, b: 150) 27 | 28 | _ = refreshControlView.addInitialGear(nbTeeth:12, color: UIColor.initRGB(92, g: 133, b: 236), radius:16) 29 | _ = refreshControlView.addLinkedGear(0, nbTeeth:16, color: UIColor.initRGB(92, g: 133, b: 236).withAlphaComponent(0.8), angleInDegree: 30) 30 | _ = refreshControlView.addLinkedGear(0, nbTeeth:32, color: UIColor.initRGB(92, g: 133, b: 236).withAlphaComponent(0.4), angleInDegree: 190, gearStyle: .WithBranchs) 31 | _ = refreshControlView.addLinkedGear(1, nbTeeth:40, color: UIColor.initRGB(92, g: 133, b: 236).withAlphaComponent(0.4), angleInDegree: -30, gearStyle: .WithBranchs, nbBranches:12) 32 | _ = refreshControlView.addLinkedGear(2, nbTeeth:24, color: UIColor.initRGB(92, g: 133, b: 236).withAlphaComponent(0.8), angleInDegree: -190) 33 | _ = refreshControlView.addLinkedGear(3, nbTeeth:10, color: UIColor.initRGB(92, g: 133, b: 236), angleInDegree: 40) 34 | refreshControlView.setMainGearPhase(0) 35 | refreshControlView.delegate = self 36 | refreshControlView.barColor = UIColor.initRGB(92, g: 133, b: 236) 37 | self.tableView.addSubview(refreshControlView) 38 | 39 | } 40 | 41 | //MARK: - Orientation methods 42 | 43 | override func didRotate(from fromInterfaceOrientation: UIInterfaceOrientation) { 44 | refreshControlView.frame = CGRect(x: 0, y: -self.tableView.bounds.height, width: self.view.frame.size.width, height: self.tableView.bounds.size.height) 45 | } 46 | 47 | 48 | //MARK: - Various methods 49 | 50 | 51 | 52 | func refresh(){ 53 | NSLog("refresh") 54 | isLoading = true 55 | 56 | // -- DO SOMETHING AWESOME (... or just wait 3 seconds) -- 57 | // This is where you'll make requests to an API, reload data, or process information 58 | let delayInSeconds = 1.0 59 | let popTime = DispatchTime.now() + Double(Int64(delayInSeconds * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC) 60 | DispatchQueue.main.asyncAfter(deadline: popTime) { () -> Void in 61 | // When done requesting/reloading/processing invoke endRefreshing, to close the control 62 | self.isLoading = false 63 | self.refreshControlView.MAGearRefreshScrollViewDataSourceDidFinishedLoading(self.tableView) 64 | 65 | } 66 | // -- FINISHED SOMETHING AWESOME, WOO! -- 67 | } 68 | 69 | 70 | 71 | //MARK: - UIScrollViewDelegate protocol conformance 72 | 73 | override func scrollViewDidScroll(_ scrollView: UIScrollView) { 74 | refreshControlView.MAGearRefreshScrollViewDidScroll(scrollView) 75 | } 76 | 77 | override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { 78 | refreshControlView.MAGearRefreshScrollViewDidEndDragging(scrollView) 79 | } 80 | 81 | 82 | //MARK: - MAGearRefreshDelegate protocol conformance 83 | 84 | func MAGearRefreshTableHeaderDataSourceIsLoading(_ view: MAGearRefreshControl) -> Bool { 85 | return isLoading 86 | } 87 | 88 | func MAGearRefreshTableHeaderDidTriggerRefresh(_ view: MAGearRefreshControl) { 89 | refresh() 90 | } 91 | 92 | 93 | // MARK: - UITableViewDataSource protocol conformance 94 | 95 | 96 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 97 | return 20 98 | } 99 | 100 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 101 | let CellIdentifier = "Cell"; 102 | 103 | var cell: AnyObject? = tableView.dequeueReusableCell(withIdentifier: CellIdentifier) 104 | 105 | if (cell == nil) { 106 | cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: CellIdentifier) 107 | } 108 | 109 | // Configure the cell... 110 | cell!.textLabel!!.text = "Row \(indexPath.row)" 111 | 112 | return cell! as! UITableViewCell 113 | } 114 | 115 | // MARK: - UITableViewDelegate protocol conformance 116 | 117 | 118 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 119 | tableView.deselectRow(at: indexPath, animated: true) 120 | } 121 | 122 | 123 | 124 | } 125 | -------------------------------------------------------------------------------- /MAGearRefreshControl-Demo/MAGearRefreshControl-Demo/DemoViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoViewController.swift 3 | // MAGearRefreshControl-Demo 4 | // 5 | // Created by Michaël Azevedo on 31/08/2015. 6 | // Copyright (c) 2015 micazeve. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DemoViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, MAGearRefreshDelegate { 12 | 13 | 14 | //MARK: - Instance properties 15 | var refreshControlView : MAGearRefreshControl! 16 | var isLoading = false 17 | @IBOutlet var myTableView: UITableView! 18 | 19 | 20 | //MARK: - Init methods 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | 25 | myTableView.delegate = self 26 | myTableView.dataSource = self 27 | 28 | self.navigationController?.navigationBar.isTranslucent = false 29 | 30 | 31 | refreshControlView = MAGearRefreshControl(frame: CGRect(x: 0, y: -self.myTableView.bounds.height, width: self.view.frame.width, height: self.myTableView.bounds.height)) 32 | refreshControlView.backgroundColor = UIColor.initRGB(34, g: 75, b: 150) 33 | 34 | _ = refreshControlView.addInitialGear(nbTeeth:12, color: UIColor.initRGB(92, g: 133, b: 236), radius:16) 35 | _ = refreshControlView.addLinkedGear(0, nbTeeth:16, color: UIColor.initRGB(92, g: 133, b: 236).withAlphaComponent(0.8), angleInDegree: 30) 36 | _ = refreshControlView.addLinkedGear(0, nbTeeth:32, color: UIColor.initRGB(92, g: 133, b: 236).withAlphaComponent(0.4), angleInDegree: 190, gearStyle: .WithBranchs) 37 | _ = refreshControlView.addLinkedGear(1, nbTeeth:40, color: UIColor.initRGB(92, g: 133, b: 236).withAlphaComponent(0.4), angleInDegree: -30, gearStyle: .WithBranchs, nbBranches:12) 38 | _ = refreshControlView.addLinkedGear(2, nbTeeth:24, color: UIColor.initRGB(92, g: 133, b: 236).withAlphaComponent(0.8), angleInDegree: -190) 39 | _ = refreshControlView.addLinkedGear(3, nbTeeth:10, color: UIColor.initRGB(92, g: 133, b: 236), angleInDegree: 40) 40 | refreshControlView.setMainGearPhase(0) 41 | refreshControlView.delegate = self 42 | refreshControlView.barColor = UIColor.initRGB(92, g: 133, b: 236) 43 | self.myTableView.addSubview(refreshControlView) 44 | 45 | } 46 | 47 | //MARK: - Orientation methods 48 | 49 | override func didRotate(from fromInterfaceOrientation: UIInterfaceOrientation) { 50 | refreshControlView.frame = CGRect(x: 0, y: -self.myTableView.bounds.height, width: self.view.frame.size.width, height: self.myTableView.bounds.size.height) 51 | } 52 | 53 | 54 | //MARK: - Various methods 55 | 56 | 57 | 58 | func refresh(){ 59 | isLoading = true 60 | 61 | // -- DO SOMETHING AWESOME (... or just wait 3 seconds) -- 62 | // This is where you'll make requests to an API, reload data, or process information 63 | let delayInSeconds = 0.6 64 | let popTime = DispatchTime.now() + Double(Int64(delayInSeconds * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC) 65 | DispatchQueue.main.asyncAfter(deadline: popTime) { () -> Void in 66 | // When done requesting/reloading/processing invoke endRefreshing, to close the control 67 | self.isLoading = false 68 | self.refreshControlView.MAGearRefreshScrollViewDataSourceDidFinishedLoading(self.myTableView) 69 | 70 | } 71 | // -- FINISHED SOMETHING AWESOME, WOO! -- 72 | } 73 | 74 | 75 | 76 | //MARK: - UIScrollViewDelegate protocol conformance 77 | 78 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 79 | refreshControlView.MAGearRefreshScrollViewDidScroll(scrollView) 80 | } 81 | 82 | func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { 83 | refreshControlView.MAGearRefreshScrollViewDidEndDragging(scrollView) 84 | } 85 | 86 | 87 | //MARK: - MAGearRefreshDelegate protocol conformance 88 | 89 | func MAGearRefreshTableHeaderDataSourceIsLoading(_ view: MAGearRefreshControl) -> Bool { 90 | return isLoading 91 | } 92 | 93 | func MAGearRefreshTableHeaderDidTriggerRefresh(_ view: MAGearRefreshControl) { 94 | refresh() 95 | } 96 | 97 | 98 | // MARK: - UITableViewDataSource protocol conformance 99 | 100 | 101 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 102 | return 20 103 | } 104 | 105 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 106 | let CellIdentifier = "Cell"; 107 | 108 | var cell: AnyObject? = tableView.dequeueReusableCell(withIdentifier: CellIdentifier) 109 | 110 | if (cell == nil) { 111 | cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: CellIdentifier) 112 | } 113 | 114 | // Configure the cell... 115 | cell!.textLabel!!.text = "Row \(indexPath.row)" 116 | 117 | return cell as! UITableViewCell! 118 | } 119 | 120 | // MARK: - UITableViewDelegate protocol conformance 121 | 122 | 123 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 124 | tableView.deselectRow(at: indexPath, animated: true) 125 | } 126 | 127 | 128 | 129 | } 130 | -------------------------------------------------------------------------------- /MAGearRefreshControl-Demo/MAGearRefreshControl-Demo/Images.xcassets/AppIcon.appiconset/29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micazeve/MAGearRefreshControl/e30acacf57a43fb0c20fcbbda7f761e49c1ef3c7/MAGearRefreshControl-Demo/MAGearRefreshControl-Demo/Images.xcassets/AppIcon.appiconset/29@2x.png -------------------------------------------------------------------------------- /MAGearRefreshControl-Demo/MAGearRefreshControl-Demo/Images.xcassets/AppIcon.appiconset/29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micazeve/MAGearRefreshControl/e30acacf57a43fb0c20fcbbda7f761e49c1ef3c7/MAGearRefreshControl-Demo/MAGearRefreshControl-Demo/Images.xcassets/AppIcon.appiconset/29@3x.png -------------------------------------------------------------------------------- /MAGearRefreshControl-Demo/MAGearRefreshControl-Demo/Images.xcassets/AppIcon.appiconset/40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micazeve/MAGearRefreshControl/e30acacf57a43fb0c20fcbbda7f761e49c1ef3c7/MAGearRefreshControl-Demo/MAGearRefreshControl-Demo/Images.xcassets/AppIcon.appiconset/40@2x.png -------------------------------------------------------------------------------- /MAGearRefreshControl-Demo/MAGearRefreshControl-Demo/Images.xcassets/AppIcon.appiconset/40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micazeve/MAGearRefreshControl/e30acacf57a43fb0c20fcbbda7f761e49c1ef3c7/MAGearRefreshControl-Demo/MAGearRefreshControl-Demo/Images.xcassets/AppIcon.appiconset/40@3x.png -------------------------------------------------------------------------------- /MAGearRefreshControl-Demo/MAGearRefreshControl-Demo/Images.xcassets/AppIcon.appiconset/60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micazeve/MAGearRefreshControl/e30acacf57a43fb0c20fcbbda7f761e49c1ef3c7/MAGearRefreshControl-Demo/MAGearRefreshControl-Demo/Images.xcassets/AppIcon.appiconset/60@2x.png -------------------------------------------------------------------------------- /MAGearRefreshControl-Demo/MAGearRefreshControl-Demo/Images.xcassets/AppIcon.appiconset/60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micazeve/MAGearRefreshControl/e30acacf57a43fb0c20fcbbda7f761e49c1ef3c7/MAGearRefreshControl-Demo/MAGearRefreshControl-Demo/Images.xcassets/AppIcon.appiconset/60@3x.png -------------------------------------------------------------------------------- /MAGearRefreshControl-Demo/MAGearRefreshControl-Demo/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "size" : "29x29", 15 | "idiom" : "iphone", 16 | "filename" : "29@2x.png", 17 | "scale" : "2x" 18 | }, 19 | { 20 | "size" : "29x29", 21 | "idiom" : "iphone", 22 | "filename" : "29@3x.png", 23 | "scale" : "3x" 24 | }, 25 | { 26 | "size" : "40x40", 27 | "idiom" : "iphone", 28 | "filename" : "40@2x.png", 29 | "scale" : "2x" 30 | }, 31 | { 32 | "size" : "40x40", 33 | "idiom" : "iphone", 34 | "filename" : "40@3x.png", 35 | "scale" : "3x" 36 | }, 37 | { 38 | "size" : "60x60", 39 | "idiom" : "iphone", 40 | "filename" : "60@2x.png", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "size" : "60x60", 45 | "idiom" : "iphone", 46 | "filename" : "60@3x.png", 47 | "scale" : "3x" 48 | } 49 | ], 50 | "info" : { 51 | "version" : 1, 52 | "author" : "xcode" 53 | } 54 | } -------------------------------------------------------------------------------- /MAGearRefreshControl-Demo/MAGearRefreshControl-Demo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | Gear Demo 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /MAGearRefreshControl-Demo/MAGearRefreshControl-Demo/UIColor + RGBInit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage + RGBInit.swift 3 | // 4 | // Created by Michaël Azevedo on 16/10/2014. 5 | // 6 | 7 | import Foundation 8 | import UIKit 9 | 10 | extension UIColor{ 11 | 12 | class func initRGBA(_ r:CGFloat, g:CGFloat, b:CGFloat, a:CGFloat) ->UIColor 13 | { 14 | return UIColor(red:r/255, green: g/255, blue: b/255, alpha: a) 15 | } 16 | 17 | class func initRGB(_ r:CGFloat, g:CGFloat, b:CGFloat) ->UIColor 18 | { 19 | return UIColor.initRGBA(r, g:g, b:b, a:1) 20 | } 21 | 22 | class func initRGBGRAY(_ gray:CGFloat) ->UIColor 23 | { 24 | return UIColor.initRGBA(gray, g:gray, b:gray, a:1) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /MAGearRefreshControl-Demo/MAGearRefreshControl-DemoTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /MAGearRefreshControl-Demo/MAGearRefreshControl-DemoTests/MAGearRefreshControl_DemoTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MAGearRefreshControl_DemoTests.swift 3 | // MAGearRefreshControl-DemoTests 4 | // 5 | // Created by Michaël Azevedo on 20/05/2015. 6 | // Copyright (c) 2015 micazeve. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import XCTest 11 | 12 | class MAGearRefreshControl_DemoTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | XCTAssert(true, "Pass") 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure() { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /MAGearRefreshControl.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "MAGearRefreshControl" 3 | s.version = "1.0.1" 4 | s.summary = "Refresh control with gear animation." 5 | 6 | s.description = <<-DESC 7 | MAGearRefreshControl is a fully customizable iOS refresh control with gear animation for tableview refresh, writen in Swift. 8 | DESC 9 | s.homepage = "https://github.com/micazeve/MAGearRefreshControl" 10 | 11 | s.license = { :type => "MIT", :file => "LICENSE.md" } 12 | s.author = { "Michaël Azevedo" => "micazeve@gmail.com" } 13 | s.social_media_url = "https://twitter.com/micazeve" 14 | s.platforms = { :ios => "8.0" } 15 | 16 | s.source = { :git => "https://github.com/micazeve/MAGearRefreshControl.git", :branch => "master", :tag => '1.0.1'} 17 | s.source_files = "Classes/**/*.swift" 18 | 19 | s.ios.deployment_target = '8.0' 20 | 21 | s.framework = "Foundation" 22 | s.requires_arc = true 23 | end 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # README 2 | [![Version](https://img.shields.io/cocoapods/v/MAGearRefreshControl.svg?)](http://cocoapods.org/pods/MAGearRefreshControl) 3 | [![License](https://img.shields.io/cocoapods/l/MAGearRefreshControl.svg)](http://cocoapods.org/pods/MAGearRefreshControl) 4 | [![Platform](https://img.shields.io/cocoapods/p/MAGearRefreshControl.svg)](http://cocoapods.org/pods/MAGearRefreshControl) 5 | 6 | MAGearRefreshControl is a fully customizable iOS refresh control with gear animation for tableview refresh, writen in Swift. 7 | 8 | ![MAGearRefreshControl](Screenshots/Anim.gif "MAGearRefreshControl") 9 | 10 | ## **Edit from 20/02/17:** 11 | 12 | This component is now compatible with Swift 3.0. The `swift-2.0` branch is still available. 13 | 14 | ## **Edit from 16/11/15:** 15 | 16 | A new enum, `MAGearStyle`, was added in order to custom single gears within the gear group. 17 | 18 | * `.Normal` : Full gear, the one you knew from the beginning :) 19 | * `.Branched`: Gear with branchs inside of it. Since it's difficult to describe, a picture will help : 20 | 21 | ![BranchedGear](Screenshots/BranchedGear.png "BranchedGear") 22 | 23 | Now you can easily add your own gear styles and I will gladly merge them. 24 | 25 | ## **Edit from 23/06/15:** 26 | 27 | An intermediate class, `MAAnimatedMultiGearView`, was added in order to animate rotation without having to use an 28 | `MAGearRefreshControl` object. 29 | 30 | ## **Credits:** 31 | 32 | This project is inspired by this [dribble post](https://dribbble.com/shots/1974767-gear-powered-pull-to-refresh-animation). 33 | The main structure of the refresh Control is based on [EGOTableViewPullRefresh](https://github.com/enormego/EGOTableViewPullRefresh). 34 | 35 | ## **How to use:** 36 | 37 | ### Contents: 38 | MAGearRefreshControl is made of five base classes you can use as you wish : 39 | 40 | * **MAGear** : This class represents a gear in the most abstract way, without any graphical code related. 41 | * **MASingleGearView** : This `UIView` subclass is used to draw a gear. 42 | * **MAMultiGearView** : This `UIView` subclass is used to draw multiples gears. 43 | * **MAAnimatedMultiGearView** : This `MAMultiGearView` subclass is used to draw and animate multiple gears 44 | * **MAGearRefreshControl** : This `MAAnimatedMultiGearView` subclass is used to draw multiples gears and offers the same interactions as an `UIRefreshControl`. 45 | 46 | ### Refresh control 47 | 48 | `MAGearRefreshControl` must be used from an `UITableViewController` subclass or `UIViewController` subclass with an `UITableView`. Examples are provided for both cases. 49 | 50 | ```objective-c 51 | refreshControl = MAGearRefreshControl(frame: CGRect(x: 0, y: -self.myTableView.bounds.height, width: self.view.frame.width, height: self.myTableView.bounds.height)) 52 | refreshControl.backgroundColor = UIColor.initRGB(34, g: 75, b: 150) 53 | _ = refreshControl.addInitialGear(nbTeeth:12, color: UIColor.initRGB(92, g: 133, b: 236), radius:16) 54 | refreshControl.delegate = self 55 | self.tableView.addSubview(refreshControl) 56 | ``` 57 | 58 | You can add new gears easily with a single method : 59 | ```objective-c 60 | _ = refreshControl.addLinkedGear(0, nbTeeth:16, color: UIColor.blue, angleInDegree: 30) 61 | _ = refreshControl.addLinkedGear(0, nbTeeth:32, color: UIColor.red, angleInDegree: 190) 62 | 63 | // Gear with branch style : 64 | refreshControl.addLinkedGear(1, nbTeeth:14, color: yellowColor(), angleInDegree: 20, gearStyle: .WithBranchs) 65 | 66 | // Gear with branch style and custom number of branchs: 67 |    refreshControl.addLinkedGear(1, nbTeeth:45, color: greenColor(), angleInDegree: -50, gearStyle: .WithBranchs, nbBranches:12)) 68 | ``` 69 | 70 | Now you have to respect the `MAGearRefreshDelegate` protocol : 71 | 72 | ```objective-c 73 | // Method called to know if the data source is loading or no 74 | func MAGearRefreshTableHeaderDataSourceIsLoading(_ view: MAGearRefreshControl) -> Bool { 75 | return isLoading 76 | } 77 | 78 | // Method called when the pull to refresh move was triggered. 79 | func MAGearRefreshTableHeaderDidTriggerRefresh(_ view: MAGearRefreshControl) { 80 | refresh() 81 | } 82 | ``` 83 | 84 | The refresh control must be notified of scrolling events and when the data is loaded using `MAGearRefreshScrollViewDidEndDragging`, `MAGearRefreshScrollViewDidScroll`and `MAGearRefreshScrollViewDataSourceDidFinishedLoading` methods. The sample project illustrate when to call these methods. 85 | 86 | 87 | 88 | ## **Licence:** 89 | 90 | MAGearRefreshControl is under MIT Licence so you can use/modify it as you wish. Any feedback will be appreciated. 91 | 92 | 93 | ## **Contact:** 94 | 95 | [@micazeve](https://twitter.com/micazeve) 96 | micazeve@gmail.com 97 | -------------------------------------------------------------------------------- /Screenshots/Anim.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micazeve/MAGearRefreshControl/e30acacf57a43fb0c20fcbbda7f761e49c1ef3c7/Screenshots/Anim.gif -------------------------------------------------------------------------------- /Screenshots/BranchedGear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micazeve/MAGearRefreshControl/e30acacf57a43fb0c20fcbbda7f761e49c1ef3c7/Screenshots/BranchedGear.png --------------------------------------------------------------------------------