├── libTest
├── Assets.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Info.plist
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
├── ViewController.swift
├── AppDelegate.swift
└── activityIndicator.swift
├── libTestTests
├── Info.plist
└── libTestTests.swift
├── libTestUITests
├── Info.plist
└── libTestUITests.swift
├── README.md
└── License.md
/libTest/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | }
33 | ],
34 | "info" : {
35 | "version" : 1,
36 | "author" : "xcode"
37 | }
38 | }
--------------------------------------------------------------------------------
/libTestTests/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 |
--------------------------------------------------------------------------------
/libTestUITests/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Requirements
6 |
7 | iOS 8.0+ /
8 |
9 | Xcode 7.2+
10 |
11 | A simple & stylish background activity Indicator which is easily integrated with just some line of code...
12 |
13 | how to implement ...
14 |
15 | Import the class file to your Project.
16 |
17 | Drag a view from object library to the view where you want to display the activity indicator.
18 |
19 | Click the view and set the class as "activityIndicator" and link to the view ie..@IBOutlet.
20 |
21 | Congrats you have successfully imported the cool activity indicator library.
22 |
23 | enjoy the lib..
24 |
25 | Controlling function
26 |
27 | activityView.startLoading()
28 |
29 | activityView.completeLoading(true)
30 |
31 | activityView.completeLoading(false)
32 |
33 | activityView.strokeColor = UIColor.redColor()
34 |
35 |
36 | For reference see the example
37 |
--------------------------------------------------------------------------------
/License.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 abdul
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 |
--------------------------------------------------------------------------------
/libTestTests/libTestTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // libTestTests.swift
3 | // libTestTests
4 | //
5 | // Created by Rohan Pawar on 25/12/15.
6 | // Copyright © 2015 dhlabs. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import libTest
11 |
12 | class libTestTests: 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 | // Use XCTAssert and related functions to verify your tests produce the correct results.
27 | }
28 |
29 | func testPerformanceExample() {
30 | // This is an example of a performance test case.
31 | self.measureBlock {
32 | // Put the code you want to measure the time of here.
33 | }
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/libTest/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 | 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 |
--------------------------------------------------------------------------------
/libTestUITests/libTestUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // libTestUITests.swift
3 | // libTestUITests
4 | //
5 | // Created by Rohan Pawar on 25/12/15.
6 | // Copyright © 2015 dhlabs. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class libTestUITests: XCTestCase {
12 |
13 | override func setUp() {
14 | super.setUp()
15 |
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 |
18 | // In UI tests it is usually best to stop immediately when a failure occurs.
19 | continueAfterFailure = false
20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
21 | XCUIApplication().launch()
22 |
23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
24 | }
25 |
26 | override func tearDown() {
27 | // Put teardown code here. This method is called after the invocation of each test method in the class.
28 | super.tearDown()
29 | }
30 |
31 | func testExample() {
32 | // Use recording to get started writing UI tests.
33 | // Use XCTAssert and related functions to verify your tests produce the correct results.
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/libTest/Base.lproj/LaunchScreen.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 |
--------------------------------------------------------------------------------
/libTest/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // libTest
4 | //
5 | // Created by abdul karim on 25/12/15.
6 | // Copyright © 2015 dhlabs. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ViewController: UIViewController {
12 |
13 | var tapCount = 0
14 |
15 | @IBOutlet weak var activityView: activityIndicator!
16 |
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 | // Do any additional setup after loading the view, typically from a nib.
20 |
21 | // activityView.lineWidth = 2
22 | }
23 |
24 | override func didReceiveMemoryWarning() {
25 | super.didReceiveMemoryWarning()
26 | // Dispose of any resources that can be recreated.
27 | }
28 |
29 | @IBAction func startAction(sender: AnyObject) {
30 | activityView.startLoading()
31 |
32 | }
33 |
34 | @IBAction func progressAction(sender: AnyObject) {
35 | let progress: Float = activityView.progress + 0.1043
36 | activityView.progress = progress
37 | }
38 |
39 | @IBAction func successAction(sender: AnyObject) {
40 | activityView.startLoading()
41 | activityView.completeLoading(true)
42 | }
43 |
44 | @IBAction func unsucessAct(sender: AnyObject) {
45 | activityView.startLoading()
46 | activityView.strokeColor = UIColor.redColor()
47 | activityView.completeLoading(false)
48 | }
49 | @IBAction func changeColorAct(sender: AnyObject) {
50 | tapCount++
51 |
52 | if (tapCount == 1){
53 | activityView.strokeColor = UIColor.redColor()
54 | }
55 | else
56 | if (tapCount == 2) {
57 | activityView.strokeColor = UIColor.blackColor()
58 | }
59 | else
60 | if (tapCount == 3) {
61 | tapCount = 0
62 | activityView.strokeColor = UIColor.purpleColor()
63 |
64 | }
65 |
66 |
67 |
68 |
69 | }
70 |
71 | }
72 |
73 |
--------------------------------------------------------------------------------
/libTest/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // libTest
4 | //
5 | // Created by abdul karim on 25/12/15.
6 | // Copyright © 2015 dhlabs. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
18 | // Override point for customization after application launch.
19 | return true
20 | }
21 |
22 | func applicationWillResignActive(application: UIApplication) {
23 | // 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.
24 | // 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.
25 | }
26 |
27 | func applicationDidEnterBackground(application: UIApplication) {
28 | // 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.
29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
30 | }
31 |
32 | func applicationWillEnterForeground(application: UIApplication) {
33 | // 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.
34 | }
35 |
36 | func applicationDidBecomeActive(application: UIApplication) {
37 | // 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.
38 | }
39 |
40 | func applicationWillTerminate(application: UIApplication) {
41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
42 | }
43 |
44 |
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/libTest/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 |
28 |
35 |
42 |
49 |
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 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
--------------------------------------------------------------------------------
/libTest/activityIndicator.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | let dhRingStorkeAnimationKey = "IDLoading.stroke"
4 | let dhRingRotationAnimationKey = "IDLoading.rotation"
5 | let dhCompletionAnimationDuration: TimeInterval = 0.3
6 | let dhHidesWhenCompletedDelay: TimeInterval = 0.5
7 |
8 | public typealias Block = () -> Void
9 |
10 |
11 | public class activityIndicator: UIView, CAAnimationDelegate {
12 | public enum ProgressStatus: Int {
13 | case Unknown, Loading, Progress, Completion
14 | }
15 |
16 | @IBInspectable public var lineWidth: CGFloat = 2.0 {
17 | didSet {
18 | progressLayer.lineWidth = lineWidth
19 | shapeLayer.lineWidth = lineWidth
20 |
21 | setProgressLayerPath()
22 | }
23 | }
24 |
25 | @IBInspectable public var strokeColor: UIColor = UIColor(red: 0.0, green: 122.0/255.0, blue: 1.0, alpha: 1.0){
26 | didSet{
27 | progressLayer.strokeColor = strokeColor.cgColor
28 | shapeLayer.strokeColor = strokeColor.cgColor
29 | progressLabel.textColor = strokeColor
30 | }
31 | }
32 |
33 | @IBInspectable public var fontSize: Float = 30 {
34 | didSet{
35 | progressLabel.font = UIFont.systemFont(ofSize: CGFloat(fontSize))
36 | }
37 | }
38 |
39 | public var hidesWhenCompleted: Bool = false
40 | public var hideAfterTime: TimeInterval = dhHidesWhenCompletedDelay
41 | public private(set) var status: ProgressStatus = .Unknown
42 |
43 | private var _progress: Float = 0.0
44 | public var progress: Float {
45 | get {
46 | return _progress
47 | }
48 | set(newProgress) {
49 | //Avoid calling excessively
50 | if (newProgress - _progress >= 0.01 || newProgress >= 100.0) {
51 | _progress = min(max(0, newProgress), 1)
52 | progressLayer.strokeEnd = CGFloat(_progress)
53 |
54 | if status == .Loading {
55 | progressLayer.removeAllAnimations()
56 | } else if(status == .Completion) {
57 | shapeLayer.strokeStart = 0
58 | shapeLayer.strokeEnd = 0
59 | shapeLayer.removeAllAnimations()
60 | }
61 |
62 | status = .Progress
63 |
64 | progressLabel.isHidden = false
65 | let progressInt: Int = Int(_progress * 100)
66 | progressLabel.text = "\(progressInt)"
67 | }
68 | }
69 | }
70 |
71 | private let progressLayer: CAShapeLayer! = CAShapeLayer()
72 | private let shapeLayer: CAShapeLayer! = CAShapeLayer()
73 | private let progressLabel: UILabel! = UILabel()
74 |
75 | private var completionBlock: Block?
76 | public override init(frame: CGRect) {
77 | super.init(frame: frame)
78 | initialize()
79 | }
80 |
81 | required public init?(coder aDecoder: NSCoder) {
82 | super.init(coder: aDecoder)
83 | initialize()
84 | }
85 |
86 | deinit{
87 | NotificationCenter.default.removeObserver(self)
88 | }
89 |
90 | override public func layoutSubviews() {
91 | super.layoutSubviews()
92 |
93 | let width = self.bounds.width
94 | let height = self.bounds.height
95 | let square = min(width, height)
96 |
97 | let bounds = CGRect(origin: CGPoint(x:0, y:0), size: CGSize(width:square, height:square))
98 |
99 | progressLayer.frame = CGRect(origin:CGPoint(x:0, y:0), size:CGSize(width:width, height:height))
100 | setProgressLayerPath()
101 |
102 | shapeLayer.bounds = bounds
103 | shapeLayer.position = CGPoint(x:self.bounds.midX, y:self.bounds.midY)
104 |
105 | let labelSquare = sqrt(2) / 2.0 * square
106 | progressLabel.bounds = CGRect(origin:CGPoint(x:0, y:0), size:CGSize(width:labelSquare, height:labelSquare))
107 | progressLabel.center = CGPoint(x:self.bounds.midX, y:self.bounds.midY)
108 | }
109 |
110 | public func startLoading() {
111 | if status == .Loading {
112 | return
113 | }
114 |
115 | status = .Loading
116 |
117 | progressLabel.isHidden = true
118 | progressLabel.text = "0"
119 | _progress = 0
120 |
121 | shapeLayer.strokeStart = 0
122 | shapeLayer.strokeEnd = 0
123 | shapeLayer.removeAllAnimations()
124 |
125 | self.isHidden = false
126 | progressLayer.strokeEnd = 0.0
127 | progressLayer.removeAllAnimations()
128 |
129 | let animation = CABasicAnimation(keyPath: "transform.rotation")
130 | animation.duration = 4.0
131 | animation.fromValue = 0.0
132 | animation.toValue = 2 * M_PI
133 | animation.repeatCount = Float.infinity
134 | progressLayer.add(animation, forKey: dhRingRotationAnimationKey)
135 |
136 | let totalDuration = 1.0
137 | let firstDuration = 2.0 * totalDuration / 3.0
138 | let secondDuration = totalDuration / 3.0
139 |
140 | let headAnimation = CABasicAnimation(keyPath: "strokeStart")
141 | headAnimation.duration = firstDuration
142 | headAnimation.fromValue = 0.0
143 | headAnimation.toValue = 0.25
144 |
145 | let tailAnimation = CABasicAnimation(keyPath: "strokeEnd")
146 | tailAnimation.duration = firstDuration
147 | tailAnimation.fromValue = 0.0
148 | tailAnimation.toValue = 1.0
149 |
150 | let endHeadAnimation = CABasicAnimation(keyPath: "strokeStart")
151 | endHeadAnimation.beginTime = firstDuration
152 | endHeadAnimation.duration = secondDuration
153 | endHeadAnimation.fromValue = 0.25
154 | endHeadAnimation.toValue = 1.0
155 |
156 | let endTailAnimation = CABasicAnimation(keyPath: "strokeEnd")
157 | endTailAnimation.beginTime = firstDuration
158 | endTailAnimation.duration = secondDuration
159 | endTailAnimation.fromValue = 1.0
160 | endTailAnimation.toValue = 1.0
161 |
162 | let animations = CAAnimationGroup()
163 | animations.duration = firstDuration + secondDuration
164 | animations.repeatCount = Float.infinity
165 | animations.animations = [headAnimation, tailAnimation, endHeadAnimation, endTailAnimation]
166 | progressLayer.add(animations, forKey: dhRingRotationAnimationKey)
167 | }
168 |
169 | public func completeLoading(success: Bool, completion: Block? = nil) {
170 | if status == .Completion {
171 | return
172 | }
173 |
174 | completionBlock = completion
175 |
176 | progressLabel.isHidden = true
177 | progressLayer.strokeEnd = 1.0
178 | progressLayer.removeAllAnimations()
179 |
180 | if success {
181 | setStrokeSuccessShapePath()
182 | self.strokeColor = UIColor.green
183 | } else {
184 | setStrokeFailureShapePath()
185 | self.strokeColor = UIColor.red
186 | }
187 |
188 | var strokeStart :CGFloat = 0.25
189 | var strokeEnd :CGFloat = 0.8
190 | var phase1Duration = 0.7 * dhCompletionAnimationDuration
191 | var phase2Duration = 0.3 * dhCompletionAnimationDuration
192 | var phase3Duration = 0.0
193 |
194 | if !success {
195 | let square = min(self.bounds.width, self.bounds.height)
196 | let point = errorJoinPoint()
197 | let increase = 1.0/3 * square - point.x
198 | let sum = 2.0/3 * square
199 | strokeStart = increase / (sum + increase)
200 | strokeEnd = (increase + sum/2) / (sum + increase)
201 |
202 | phase1Duration = 0.5 * dhCompletionAnimationDuration
203 | phase2Duration = 0.2 * dhCompletionAnimationDuration
204 | phase3Duration = 0.3 * dhCompletionAnimationDuration
205 | }
206 |
207 | shapeLayer.strokeEnd = 1.0
208 | shapeLayer.strokeStart = strokeStart
209 | let timeFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
210 |
211 | let headStartAnimation = CABasicAnimation(keyPath: "strokeStart")
212 | headStartAnimation.fromValue = 0.0
213 | headStartAnimation.toValue = 0.0
214 | headStartAnimation.duration = phase1Duration
215 | headStartAnimation.timingFunction = timeFunction
216 |
217 | let headEndAnimation = CABasicAnimation(keyPath: "strokeEnd")
218 | headEndAnimation.fromValue = 0.0
219 | headEndAnimation.toValue = strokeEnd
220 | headEndAnimation.duration = phase1Duration
221 | headEndAnimation.timingFunction = timeFunction
222 |
223 | let tailStartAnimation = CABasicAnimation(keyPath: "strokeStart")
224 | tailStartAnimation.fromValue = 0.0
225 | tailStartAnimation.toValue = strokeStart
226 | tailStartAnimation.beginTime = phase1Duration
227 | tailStartAnimation.duration = phase2Duration
228 | tailStartAnimation.timingFunction = timeFunction
229 |
230 | let tailEndAnimation = CABasicAnimation(keyPath: "strokeEnd")
231 | tailEndAnimation.fromValue = strokeEnd
232 | tailEndAnimation.toValue = success ? 1.0 : strokeEnd
233 | tailEndAnimation.beginTime = phase1Duration
234 | tailEndAnimation.duration = phase2Duration
235 | tailEndAnimation.timingFunction = timeFunction
236 |
237 | let extraAnimation = CABasicAnimation(keyPath: "strokeEnd")
238 | extraAnimation.fromValue = strokeEnd
239 | extraAnimation.toValue = 1.0
240 | extraAnimation.beginTime = phase1Duration + phase2Duration
241 | extraAnimation.duration = phase3Duration
242 | extraAnimation.timingFunction = timeFunction
243 |
244 | let groupAnimation = CAAnimationGroup()
245 | groupAnimation.animations = [headEndAnimation, headStartAnimation, tailStartAnimation, tailEndAnimation]
246 | if !success {
247 | groupAnimation.animations?.append(extraAnimation)
248 | }
249 | groupAnimation.duration = phase1Duration + phase2Duration + phase3Duration
250 | groupAnimation.delegate = self
251 | shapeLayer.add(groupAnimation, forKey: nil)
252 | }
253 |
254 | public func animationDidStop(_: CAAnimation, finished flag: Bool) {
255 | if hidesWhenCompleted {
256 | Timer.scheduledTimer(timeInterval: dhHidesWhenCompletedDelay, target: self, selector: #selector(DSActivityIndicator.hiddenLoadingView), userInfo: nil, repeats: false)
257 | } else {
258 | status = .Completion
259 | if completionBlock != nil {
260 | completionBlock!()
261 | }
262 | }
263 | }
264 |
265 | //MARK: - Private
266 | private func initialize() {
267 | //progressLabel
268 | progressLabel.font = UIFont.systemFont(ofSize: CGFloat(fontSize))
269 | progressLabel.textColor = strokeColor
270 | progressLabel.textAlignment = .center
271 | progressLabel.adjustsFontSizeToFitWidth = true
272 | progressLabel.isHidden = true
273 | self.addSubview(progressLabel)
274 |
275 | //progressLayer
276 | progressLayer.strokeColor = strokeColor.cgColor
277 | progressLayer.fillColor = nil
278 | progressLayer.lineWidth = lineWidth
279 | self.layer.addSublayer(progressLayer)
280 |
281 | //shapeLayer
282 | shapeLayer.strokeColor = strokeColor.cgColor
283 | shapeLayer.fillColor = nil
284 | shapeLayer.lineWidth = lineWidth
285 | shapeLayer.lineCap = kCALineCapRound
286 | shapeLayer.lineJoin = kCALineJoinRound
287 | shapeLayer.strokeStart = 0.0
288 | shapeLayer.strokeEnd = 0.0
289 | self.layer.addSublayer(shapeLayer)
290 |
291 | NotificationCenter.default.addObserver(self, selector:#selector(DSActivityIndicator.resetAnimations), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
292 | }
293 |
294 | private func setProgressLayerPath() {
295 | let center = CGPoint(x:self.bounds.midX, y:self.bounds.midY)
296 | let radius = (min(self.bounds.width, self.bounds.height) - progressLayer.lineWidth) / 2
297 | let path = UIBezierPath(arcCenter: center, radius: radius, startAngle: CGFloat(0.0), endAngle: CGFloat(2 * M_PI), clockwise: true)
298 | progressLayer.path = path.cgPath
299 | progressLayer.strokeStart = 0.0
300 | progressLayer.strokeEnd = 0.0
301 | }
302 |
303 | private func setStrokeSuccessShapePath() {
304 | let width = self.bounds.width
305 | let height = self.bounds.height
306 | let square = min(width, height)
307 | let b = square/2
308 | let oneTenth = square/10
309 | let xOffset = oneTenth
310 | let yOffset = 1.5 * oneTenth
311 | let ySpace = 3.2 * oneTenth
312 | let point = correctJoinPoint()
313 |
314 |
315 | let path = CGMutablePath()
316 | path.move(to: CGPoint(x:point.x, y:point.y))
317 | path.addLine(to: CGPoint(x:point.x, y:point.y))
318 | path.addLine(to: CGPoint(x: b - xOffset, y: b + yOffset))
319 | path.addLine(to: CGPoint(x: 2 * b - xOffset + yOffset - ySpace, y:ySpace ))
320 |
321 | shapeLayer.path = path
322 | shapeLayer.cornerRadius = square/2
323 | shapeLayer.masksToBounds = true
324 | shapeLayer.strokeStart = 0.0
325 | shapeLayer.strokeEnd = 0.0
326 | }
327 |
328 | private func setStrokeFailureShapePath() {
329 | let width = self.bounds.width
330 | let height = self.bounds.height
331 | let square = min(width, height)
332 | let b = square/2
333 | let space = square/3
334 | let point = errorJoinPoint()
335 |
336 |
337 | let path = CGMutablePath()
338 | path.move(to: CGPoint(x:point.x, y:point.y))
339 | path.addLine(to: CGPoint(x:2 * b - space, y: 2 * b - space))
340 | path.move(to: CGPoint(x:2 * b - space, y: space))
341 | path.addLine(to: CGPoint(x:space, y:2 * b - space))
342 |
343 | shapeLayer.path = path
344 | shapeLayer.cornerRadius = square/2
345 | shapeLayer.masksToBounds = true
346 | shapeLayer.strokeStart = 0
347 | shapeLayer.strokeEnd = 0.0
348 | }
349 |
350 | private func correctJoinPoint() -> CGPoint {
351 | let r = min(self.bounds.width, self.bounds.height)/2
352 | let m = r/2
353 | let k = lineWidth/2
354 |
355 | let a: CGFloat = 2.0
356 | let b = -4 * r + 2 * m
357 | let c = (r - m) * (r - m) + 2 * r * k - k * k
358 | let x = (-b - sqrt(b * b - 4 * a * c))/(2 * a)
359 | let y = x + m
360 |
361 | return CGPoint(x:x, y:y)
362 | }
363 |
364 | private func errorJoinPoint() -> CGPoint {
365 | let r = min(self.bounds.width, self.bounds.height)/2
366 | let k = lineWidth/2
367 |
368 | let a: CGFloat = 2.0
369 | let b = -4 * r
370 | let c = r * r + 2 * r * k - k * k
371 | let x = (-b - sqrt(b * b - 4 * a * c))/(2 * a)
372 |
373 | return CGPoint(x:x, y:x)
374 | }
375 |
376 | @objc private func resetAnimations() {
377 | if status == .Loading {
378 | status = .Unknown
379 | progressLayer.removeAnimation(forKey: dhRingRotationAnimationKey)
380 | progressLayer.removeAnimation(forKey: dhRingStorkeAnimationKey)
381 |
382 | startLoading()
383 | }
384 | }
385 |
386 | @objc private func hiddenLoadingView() {
387 | status = .Completion
388 | self.isHidden = true
389 |
390 | if completionBlock != nil {
391 | completionBlock!()
392 | }
393 | }
394 | }
395 |
--------------------------------------------------------------------------------