├── Dance.swift ├── DanceExample.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcuserdata │ └── saoud.xcuserdatad │ └── xcschemes │ ├── DanceExample.xcscheme │ └── xcschememanagement.plist ├── DanceExample ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist └── ViewController.swift ├── LICENSE └── README.md /Dance.swift: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2017 Saoud Rizwan 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 13 | // all 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 21 | // THE SOFTWARE. 22 | 23 | import UIKit 24 | 25 | // MARK: - Dance Animation State 26 | 27 | @available(iOS 10.0, *) 28 | public enum DanceAnimationState { 29 | case inactive // animation hasn't started yet, or no animation associated with view 30 | case active // animation exists and has started 31 | } 32 | 33 | // MARK: - Dance Implementation 34 | 35 | fileprivate class DanceFactory { 36 | 37 | static let instance = DanceFactory() 38 | 39 | // MARK: UIViewPropertyAnimator Wrapper 40 | 41 | var tagCount: Int = 0 42 | var animators = [Int: UIViewPropertyAnimator]() // [dance.tag: UIViewPropertyAnimator()] 43 | 44 | /// Initialize a UIViewPropertyAnimator with timing parameters. 45 | func createNewAnimator(tag: Int, duration: TimeInterval, timingParameters: UITimingCurveProvider, animations: @escaping (() -> Void)) { 46 | let newAnimator = UIViewPropertyAnimator(duration: duration, timingParameters: timingParameters) 47 | newAnimator.addAnimations(animations) 48 | newAnimator.addCompletion { (_) in 49 | self.animators.removeValue(forKey: tag) 50 | } 51 | animators[tag] = newAnimator 52 | } 53 | 54 | /// Initialize a UIViewPropertyAnimator with an animation curve. 55 | func createNewAnimator(tag: Int, duration: TimeInterval, curve: UIViewAnimationCurve, animations: (() -> Void)?) { 56 | let newAnimator = UIViewPropertyAnimator(duration: duration, curve: curve, animations: animations) 57 | newAnimator.addCompletion { (_) in 58 | self.animators.removeValue(forKey: tag) 59 | } 60 | animators[tag] = newAnimator 61 | } 62 | 63 | /// Initialize a UIViewPropertyAnimator with custom control points. 64 | func createNewAnimator(tag: Int, duration: TimeInterval, controlPoint1 point1: CGPoint, controlPoint2 point2: CGPoint, animations: (() -> Void)?) { 65 | let newAnimator = UIViewPropertyAnimator(duration: duration, controlPoint1: point1, controlPoint2: point2, animations: animations) 66 | newAnimator.addCompletion { (_) in 67 | self.animators.removeValue(forKey: tag) 68 | } 69 | animators[tag] = newAnimator 70 | } 71 | 72 | /// Initialize a UIViewPropertyAnimator with a damping ratio. 73 | func createNewAnimator(tag: Int, duration: TimeInterval, dampingRatio: CGFloat, animations: (() -> Void)?) { 74 | let newAnimator = UIViewPropertyAnimator(duration: duration, dampingRatio: dampingRatio, animations: animations) 75 | newAnimator.addCompletion { (_) in 76 | self.animators.removeValue(forKey: tag) 77 | } 78 | animators[tag] = newAnimator 79 | } 80 | 81 | /// Add a completion block to the current UIViewPropertyAnimator for the UIView. 82 | func addCompletion(tag: Int, completion: @escaping (UIViewAnimatingPosition) -> Void) { 83 | if let animator = animators[tag] { 84 | animator.addCompletion(completion) 85 | } else { 86 | handle(error: .noAnimation, forViewWithTag: tag) 87 | } 88 | } 89 | 90 | /// Start the UIViewPropertyAnimator animation for the UIView immediately. 91 | func startAnimation(tag: Int) { 92 | if let animator = animators[tag] { 93 | animator.startAnimation() 94 | } else { 95 | handle(error: .noAnimation, forViewWithTag: tag) 96 | } 97 | } 98 | 99 | /// Start the UIViewPropertyAnimator animation for the UIView after a delay (in seconds). 100 | func startAnimation(tag: Int, afterDelay delay: TimeInterval) { 101 | if let animator = animators[tag] { 102 | animator.startAnimation(afterDelay: delay) 103 | } else { 104 | handle(error: .noAnimation, forViewWithTag: tag) 105 | } 106 | } 107 | 108 | /// Pause the UIViewPropertyAnimator animation for the UIView immediately. 109 | func pauseAnimation(tag: Int) { 110 | if let animator = animators[tag] { 111 | animator.pauseAnimation() 112 | } else { 113 | handle(error: .noAnimation, forViewWithTag: tag) 114 | } 115 | } 116 | 117 | /// Pause the UIViewPropertyAnimator animation for the UIView after a delay (in seconds). 118 | func pauseAnimation(tag: Int, afterDelay delay: TimeInterval) { 119 | DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: { 120 | if let animator = self.animators[tag] { 121 | animator.pauseAnimation() 122 | } else { 123 | self.handle(error: .noAnimation, forViewWithTag: tag) 124 | } 125 | }) 126 | } 127 | 128 | /// Finish the UIViewPropertyAnimator animation. 129 | /// Only stopped animations can be finished, and UIViewPropertyAnimator completion blocks are called only once an animation finishes. 130 | func finishAnimation(tag: Int, at finalPosition: UIViewAnimatingPosition) { 131 | if let animator = animators[tag] { 132 | // You can't finish an animation that hasn't been started. So in case it hasn't been started - start it. 133 | if !animator.isRunning { 134 | animator.startAnimation() 135 | } 136 | animator.stopAnimation(false) 137 | animator.finishAnimation(at: finalPosition) 138 | } else { 139 | handle(error: .noAnimation, forViewWithTag: tag) 140 | } 141 | 142 | } 143 | 144 | /// Fraction of completion of a UIViewPropertyAnimator animation. 145 | func getFractionComplete(tag: Int) -> CGFloat { 146 | if let animator = animators[tag] { 147 | return animator.fractionComplete 148 | } else { 149 | handle(error: .noAnimation, forViewWithTag: tag) 150 | return CGFloat(0) 151 | } 152 | 153 | } 154 | 155 | /// Updates the UIViewPropertyAnimator's .fractionComplete. 156 | func setFractionComplete(tag: Int, newFractionComplete: CGFloat) { 157 | if let animator = animators[tag] { 158 | // trigger the animator if it exists but hasn't started 159 | if !animator.isRunning { 160 | animator.startAnimation() 161 | animator.pauseAnimation() 162 | } 163 | animator.fractionComplete = newFractionComplete 164 | } else { 165 | handle(error: .noAnimation, forViewWithTag: tag) 166 | } 167 | 168 | } 169 | 170 | /// Returns the view's UIViewPropertyAnimator current state (inactive, active, stopped). 171 | func getState(tag: Int) -> DanceAnimationState { 172 | if let animator = animators[tag] { 173 | switch animator.state { 174 | case .inactive: 175 | return DanceAnimationState.inactive 176 | case .active: 177 | return DanceAnimationState.active 178 | case .stopped: 179 | return DanceAnimationState.inactive 180 | } 181 | } else { 182 | handle(error: .noAnimation, forViewWithTag: tag) 183 | return .inactive 184 | } 185 | } 186 | 187 | /// Returns a boolean value indicating whether the UIView's animation is running (active, started and not paused) or paused/inactive. 188 | func getIsRunning(tag: Int) -> Bool { 189 | if let animator = animators[tag] { 190 | return animator.isRunning 191 | } else { 192 | handle(error: .noAnimation, forViewWithTag: tag) 193 | return false 194 | } 195 | } 196 | 197 | /// Returns a boolean value indicating whether the UIView's UIViewPropertyAnimator is reversed or not. 198 | func getIsReversed(tag: Int) -> Bool { 199 | if let animator = animators[tag] { 200 | return animator.isReversed 201 | } else { 202 | handle(error: .noAnimation, forViewWithTag: tag) 203 | return false 204 | } 205 | } 206 | 207 | /// Updates the UIView's UIViewPropertyAnimator's .isReversed property, dynamically reversing the animation in progress. 208 | func setIsReversed(tag: Int, isReversed: Bool) { 209 | if let animator = animators[tag] { 210 | animator.isReversed = isReversed 211 | } else { 212 | handle(error: .noAnimation, forViewWithTag: tag) 213 | } 214 | } 215 | 216 | /// Returns a boolean value inidicating whether the UIView has an active UIViewPropertyAnimator attached to it through Dance. 217 | func getHasAnimation(tag: Int) -> Bool { 218 | if let _ = animators[tag] { 219 | return true 220 | } else { 221 | return false 222 | } 223 | } 224 | 225 | // MARK: Error Handling 226 | 227 | enum DanceError { 228 | case noAnimation 229 | } 230 | 231 | func handle(error: DanceError, forViewWithTag tag: Int) { 232 | switch error { 233 | case .noAnimation: 234 | print("** Dance Error: view with dance.tag = \(tag) does not have an active animation! **") 235 | } 236 | } 237 | 238 | } 239 | 240 | 241 | // MARK: - Dance 242 | 243 | // This class should only be accessed through the 'dance' variable declared in the UIView extension below. 244 | // (Dance needs to have a public modifier in order to be accessed globally through the UIView Extension.) 245 | 246 | @available(iOS 10.0, *) 247 | public class Dance { 248 | 249 | fileprivate weak var dancingView: UIView! 250 | public var tag: Int = 0 251 | 252 | fileprivate init(dancingView: UIView) { 253 | self.dancingView = dancingView 254 | } 255 | 256 | // MARK: Dance - UIViewPropertyAnimator Properties 257 | 258 | /// GET: Returns a boolean value of true if the view has an animation attached to it, and false otherwise. 259 | public var hasAnimation: Bool { 260 | get { 261 | return DanceFactory.instance.getHasAnimation(tag: self.tag) 262 | } 263 | } 264 | 265 | /// GET: Returns a CGFloat value between 0 and 1 that inidicates the fraction value of the completion of the view's animation. 266 | /// SET: Sets the view's animation's fraction complete. If this is set in the middle of a running animation, the view will jump to it's new fraction complete value seamlessly. 267 | public var progress: CGFloat { 268 | get { 269 | return DanceFactory.instance.getFractionComplete(tag: self.tag) 270 | } 271 | set { 272 | DanceFactory.instance.setFractionComplete(tag: self.tag, newFractionComplete: newValue) 273 | } 274 | } 275 | 276 | /// GET: Returns the view's current animation state (inactive or active.) If a view has a dance animation associated with it and has not been started, then state will return .inactive. Once the animation has been started, even if it is then paused, state will return .active until the animation finishes. 277 | public var state: DanceAnimationState { 278 | get { 279 | return DanceFactory.instance.getState(tag: self.tag) 280 | } 281 | } 282 | 283 | /// GET: Returns a boolean value indicating whether the view's animation is currently running (active and started) or not. 284 | public var isRunning: Bool { 285 | get { 286 | return DanceFactory.instance.getIsRunning(tag: self.tag) 287 | } 288 | } 289 | 290 | /// GET: Returns a boolean value indicating whether the view's animation is currently reversed or not. 291 | /// SET: Sets the view's animation to a reversed state. NOTE: you may also call .reverse() on any view to reverse or de-reverse its animation. 292 | public var isReversed: Bool { 293 | get { 294 | return DanceFactory.instance.getIsReversed(tag: self.tag) 295 | } 296 | set { 297 | DanceFactory.instance.setIsReversed(tag: self.tag, isReversed: newValue) 298 | } 299 | } 300 | 301 | // MARK: Dance - UIViewPropertyAnimator Methods 302 | 303 | public typealias make = UIView 304 | 305 | /// Initializes a UIViewPropertyAnimator object with a built-in UIKit timing curve for the view. 306 | /// 307 | /// - Parameters: 308 | /// - duration: The total duration of the animations, measured in seconds. If you specify a negative value or 0, the changes are made without animating them. 309 | /// - curve: The UIKit timing curve to apply to the animation. 310 | /// * easeInOut (slow at beginning and end) 311 | /// * easeIn (slow at beginning) 312 | /// * easeOut (slow at end) 313 | /// * linear 314 | /// - animation: Any changes to commit to the view during the animation (can be any property defined in Table 4-1 in https://developer.apple.com/library/content/documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/AnimatingViews/AnimatingViews.html) 315 | @discardableResult public func animate(duration: TimeInterval, curve: UIViewAnimationCurve, _ animation: @escaping (make) -> Void) -> Dance { 316 | self.tag = DanceFactory.instance.tagCount 317 | DanceFactory.instance.tagCount += 1 318 | 319 | if self.hasAnimation { 320 | DanceFactory.instance.finishAnimation(tag: self.tag, at: .current) 321 | } 322 | 323 | DanceFactory.instance.createNewAnimator(tag: self.tag, duration: duration, curve: curve) { 324 | animation(self.dancingView) 325 | } 326 | 327 | return dancingView.dance 328 | } 329 | 330 | /// Initializes a UIViewPropertyAnimator object with a custom timing curve object for the view. See UITimingCurveProvider https://developer.apple.com/reference/uikit/uitimingcurveprovider 331 | /// 332 | /// - Parameters: 333 | /// - duration: The total duration of the animations, measured in seconds. If you specify a negative value or 0, the changes are made without animating them. 334 | /// - timingParameters: The object providing the timing information. This object must adopt the UITimingCurveProvider protocol https://developer.apple.com/reference/uikit/uitimingcurveprovider 335 | /// - animation: Any changes to commit to the view during the animation (can be any property defined in Table 4-1 in https://developer.apple.com/library/content/documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/AnimatingViews/AnimatingViews.html) 336 | @discardableResult public func animate(duration: TimeInterval, timingParameters: UITimingCurveProvider, _ animation: @escaping (make) -> Void) -> Dance { 337 | self.tag = DanceFactory.instance.tagCount 338 | DanceFactory.instance.tagCount += 1 339 | 340 | if self.hasAnimation { 341 | DanceFactory.instance.finishAnimation(tag: self.tag, at: .current) 342 | } 343 | 344 | DanceFactory.instance.createNewAnimator(tag: self.tag, duration: duration, timingParameters: timingParameters) { 345 | animation(self.dancingView) 346 | } 347 | 348 | return dancingView.dance 349 | } 350 | 351 | /// Initializes a UIViewPropertyAnimator object with a cubic Bézier timing curve for the view. 352 | /// 353 | /// - Parameters: 354 | /// - duration: The total duration of the animations, measured in seconds. If you specify a negative value or 0, the changes are made without animating them. 355 | /// - point1: The first control point for the cubic Bézier timing curve. 356 | /// - point2: The second control point for the cubic Bézier timing curve. 357 | /// - animation: Any changes to commit to the view during the animation (can be any property defined in Table 4-1 in https://developer.apple.com/library/content/documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/AnimatingViews/AnimatingViews.html) 358 | @discardableResult public func animate(duration: TimeInterval, controlPoint1 point1: CGPoint, controlPoint2 point2: CGPoint, _ animation: @escaping (make) -> Void) -> Dance { 359 | self.tag = DanceFactory.instance.tagCount 360 | DanceFactory.instance.tagCount += 1 361 | 362 | if self.hasAnimation { 363 | DanceFactory.instance.finishAnimation(tag: self.tag, at: .current) 364 | } 365 | 366 | DanceFactory.instance.createNewAnimator(tag: self.tag, duration: duration, controlPoint1: point1, controlPoint2: point2) { 367 | animation(self.dancingView) 368 | } 369 | 370 | return dancingView.dance 371 | } 372 | 373 | /// Initializes a UIViewPropertyAnimator object with spring-based timing information for the view. 374 | /// 375 | /// - Parameters: 376 | /// - duration: The total duration of the animations, measured in seconds. If you specify a negative value or 0, the changes are made without animating them. 377 | /// - dampingRatio: The damping ratio for the spring animation as it approaches its quiescent state. To smoothly decelerate the animation without oscillation, use a value of 1. Employ a damping ratio closer to zero to increase oscillation. 378 | /// - animation: Any changes to commit to the view during the animation (can be any property defined in Table 4-1 in https://developer.apple.com/library/content/documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/AnimatingViews/AnimatingViews.html) 379 | @discardableResult public func animate(duration: TimeInterval, dampingRatio: CGFloat, _ animation: @escaping (make) -> Void) -> Dance { 380 | self.tag = DanceFactory.instance.tagCount 381 | DanceFactory.instance.tagCount += 1 382 | 383 | if self.hasAnimation { 384 | DanceFactory.instance.finishAnimation(tag: self.tag, at: .current) 385 | } 386 | 387 | DanceFactory.instance.createNewAnimator(tag: self.tag, duration: duration, dampingRatio: dampingRatio) { 388 | animation(self.dancingView) 389 | } 390 | 391 | return dancingView.dance 392 | } 393 | 394 | /// Adds a completion block to the view's current animation. 395 | /// 396 | /// - Parameter completion: closure with a UIViewAnimatingPosition parameter that tells you what position the view's animation is currently at (end, start, current). NOTE: If you reverse an animation, the end position will be the initial starting position before you reversed the animation (and vice versa.) 397 | @discardableResult public func addCompletion(_ completion: @escaping (UIViewAnimatingPosition) -> Void) -> Dance { 398 | DanceFactory.instance.addCompletion(tag: self.tag, completion: completion) 399 | return dancingView.dance 400 | } 401 | 402 | /// Starts the animation for the view (must be called after declaring an animation block.) 403 | @discardableResult public func start() -> Dance { 404 | DanceFactory.instance.startAnimation(tag: self.tag) 405 | return dancingView.dance 406 | } 407 | 408 | /// Starts the animation for the view after a delay (must be called after declaring an animation block.) 409 | /// 410 | /// - Parameter delay: The amount of time (measured in seconds) to wait before beginning the animations. 411 | @discardableResult public func start(after delay: TimeInterval) -> Dance { 412 | DanceFactory.instance.startAnimation(tag: self.tag, afterDelay: delay) 413 | return dancingView.dance 414 | } 415 | 416 | /// Pauses the view's animation. The view is not rendered in a paused state, so make sure to call .finish(at:) on the view in order to render it at a desired position. 417 | @discardableResult public func pause() -> Dance { 418 | DanceFactory.instance.pauseAnimation(tag: self.tag) 419 | return dancingView.dance 420 | } 421 | 422 | /// Pauses the view's animation after a delay. The view is not rendered in a paused state, so make sure to call .finish(at:) on the view in order to render it at a desired position. 423 | /// 424 | /// - Parameter delay: The amount of time (measured in seconds) to wait before pausing the animations. 425 | @discardableResult public func pause(after delay: TimeInterval) -> Dance { 426 | DanceFactory.instance.pauseAnimation(tag: self.tag, afterDelay: delay) 427 | return dancingView.dance 428 | } 429 | 430 | /// Finished the view's current animation. Triggers the animation's completion blocks to take action immediately. 431 | /// 432 | /// - Parameter position: the position (current, start, end) to end the animation at. In other words, the position to render the view at after the animation. 433 | @discardableResult public func finish(at position: UIViewAnimatingPosition) -> Dance { 434 | DanceFactory.instance.finishAnimation(tag: self.tag, at: position) 435 | return dancingView.dance 436 | } 437 | 438 | /// Reverses the animation. Calling .reverse() on an already reversed view animation will make it animate in the initial direction. 439 | /// Alternative to setting the .isReversed variable 440 | @discardableResult public func reverse() -> Dance { 441 | let reversedState = DanceFactory.instance.getIsReversed(tag: self.tag) // leave this here in order to print debug error 442 | if self.hasAnimation { 443 | DanceFactory.instance.setIsReversed(tag: self.tag, isReversed: !reversedState) 444 | } 445 | return dancingView.dance 446 | } 447 | 448 | /// Sets the view's animation's fraction complete. If this is set in the middle of a running animation, the view will jump to it's new fraction complete value seamlessly. 449 | /// Alternative to setting the .progress value 450 | @discardableResult public func setProgress(to newProgress: T) -> Dance { 451 | if let value = newProgress as? Float { 452 | progress = CGFloat(value) 453 | } else if let value = newProgress as? Double { 454 | progress = CGFloat(value) 455 | } else if let value = newProgress as? CGFloat { 456 | progress = value 457 | } 458 | return dancingView.dance 459 | } 460 | 461 | } 462 | 463 | 464 | // MARK: - UIView Extension for Dance 465 | 466 | @available(iOS 10.0, *) 467 | extension UIView { 468 | 469 | fileprivate struct DanceAssociatedKey { 470 | static var dance = "dance_key" 471 | } 472 | 473 | public var dance: Dance { 474 | get { 475 | if let danceInstance = objc_getAssociatedObject(self, &DanceAssociatedKey.dance) as? Dance { 476 | return danceInstance 477 | } else { 478 | let newDanceInstance = Dance(dancingView: self) 479 | objc_setAssociatedObject(self, &DanceAssociatedKey.dance, newDanceInstance, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) 480 | return newDanceInstance 481 | } 482 | } 483 | } 484 | 485 | } 486 | -------------------------------------------------------------------------------- /DanceExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1019DBEC1E51060F00C7D6F1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1019DBEB1E51060F00C7D6F1 /* AppDelegate.swift */; }; 11 | 1019DBEE1E51060F00C7D6F1 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1019DBED1E51060F00C7D6F1 /* ViewController.swift */; }; 12 | 1019DBF11E51060F00C7D6F1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1019DBEF1E51060F00C7D6F1 /* Main.storyboard */; }; 13 | 1019DBF31E51060F00C7D6F1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1019DBF21E51060F00C7D6F1 /* Assets.xcassets */; }; 14 | 1019DBF61E51060F00C7D6F1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1019DBF41E51060F00C7D6F1 /* LaunchScreen.storyboard */; }; 15 | 1019DBFE1E51063D00C7D6F1 /* Dance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1019DBFD1E51063D00C7D6F1 /* Dance.swift */; }; 16 | 1045F3801E5169A20021062D /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 1045F37F1E5169A20021062D /* LICENSE */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | 1019DBE81E51060F00C7D6F1 /* DanceExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DanceExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 21 | 1019DBEB1E51060F00C7D6F1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 22 | 1019DBED1E51060F00C7D6F1 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 23 | 1019DBF01E51060F00C7D6F1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 24 | 1019DBF21E51060F00C7D6F1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 25 | 1019DBF51E51060F00C7D6F1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 26 | 1019DBF71E51060F00C7D6F1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 27 | 1019DBFD1E51063D00C7D6F1 /* Dance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Dance.swift; sourceTree = ""; }; 28 | 1045F37F1E5169A20021062D /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; 29 | /* End PBXFileReference section */ 30 | 31 | /* Begin PBXFrameworksBuildPhase section */ 32 | 1019DBE51E51060F00C7D6F1 /* Frameworks */ = { 33 | isa = PBXFrameworksBuildPhase; 34 | buildActionMask = 2147483647; 35 | files = ( 36 | ); 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXFrameworksBuildPhase section */ 40 | 41 | /* Begin PBXGroup section */ 42 | 1019DBDF1E51060F00C7D6F1 = { 43 | isa = PBXGroup; 44 | children = ( 45 | 1045F37F1E5169A20021062D /* LICENSE */, 46 | 1019DBFF1E51064E00C7D6F1 /* Dance */, 47 | 1019DBEA1E51060F00C7D6F1 /* DanceExample */, 48 | 1019DBE91E51060F00C7D6F1 /* Products */, 49 | ); 50 | sourceTree = ""; 51 | }; 52 | 1019DBE91E51060F00C7D6F1 /* Products */ = { 53 | isa = PBXGroup; 54 | children = ( 55 | 1019DBE81E51060F00C7D6F1 /* DanceExample.app */, 56 | ); 57 | name = Products; 58 | sourceTree = ""; 59 | }; 60 | 1019DBEA1E51060F00C7D6F1 /* DanceExample */ = { 61 | isa = PBXGroup; 62 | children = ( 63 | 1019DBED1E51060F00C7D6F1 /* ViewController.swift */, 64 | 1019DBEF1E51060F00C7D6F1 /* Main.storyboard */, 65 | 1019DC001E51093700C7D6F1 /* Junk */, 66 | ); 67 | path = DanceExample; 68 | sourceTree = ""; 69 | }; 70 | 1019DBFF1E51064E00C7D6F1 /* Dance */ = { 71 | isa = PBXGroup; 72 | children = ( 73 | 1019DBFD1E51063D00C7D6F1 /* Dance.swift */, 74 | ); 75 | name = Dance; 76 | sourceTree = ""; 77 | }; 78 | 1019DC001E51093700C7D6F1 /* Junk */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 1019DBEB1E51060F00C7D6F1 /* AppDelegate.swift */, 82 | 1019DBF21E51060F00C7D6F1 /* Assets.xcassets */, 83 | 1019DBF41E51060F00C7D6F1 /* LaunchScreen.storyboard */, 84 | 1019DBF71E51060F00C7D6F1 /* Info.plist */, 85 | ); 86 | name = Junk; 87 | sourceTree = ""; 88 | }; 89 | /* End PBXGroup section */ 90 | 91 | /* Begin PBXNativeTarget section */ 92 | 1019DBE71E51060F00C7D6F1 /* DanceExample */ = { 93 | isa = PBXNativeTarget; 94 | buildConfigurationList = 1019DBFA1E51060F00C7D6F1 /* Build configuration list for PBXNativeTarget "DanceExample" */; 95 | buildPhases = ( 96 | 1019DBE41E51060F00C7D6F1 /* Sources */, 97 | 1019DBE51E51060F00C7D6F1 /* Frameworks */, 98 | 1019DBE61E51060F00C7D6F1 /* Resources */, 99 | ); 100 | buildRules = ( 101 | ); 102 | dependencies = ( 103 | ); 104 | name = DanceExample; 105 | productName = DanceExample; 106 | productReference = 1019DBE81E51060F00C7D6F1 /* DanceExample.app */; 107 | productType = "com.apple.product-type.application"; 108 | }; 109 | /* End PBXNativeTarget section */ 110 | 111 | /* Begin PBXProject section */ 112 | 1019DBE01E51060F00C7D6F1 /* Project object */ = { 113 | isa = PBXProject; 114 | attributes = { 115 | LastSwiftUpdateCheck = 0820; 116 | LastUpgradeCheck = 0820; 117 | ORGANIZATIONNAME = "Saoud Rizwan"; 118 | TargetAttributes = { 119 | 1019DBE71E51060F00C7D6F1 = { 120 | CreatedOnToolsVersion = 8.2.1; 121 | ProvisioningStyle = Automatic; 122 | }; 123 | }; 124 | }; 125 | buildConfigurationList = 1019DBE31E51060F00C7D6F1 /* Build configuration list for PBXProject "DanceExample" */; 126 | compatibilityVersion = "Xcode 3.2"; 127 | developmentRegion = English; 128 | hasScannedForEncodings = 0; 129 | knownRegions = ( 130 | en, 131 | Base, 132 | ); 133 | mainGroup = 1019DBDF1E51060F00C7D6F1; 134 | productRefGroup = 1019DBE91E51060F00C7D6F1 /* Products */; 135 | projectDirPath = ""; 136 | projectRoot = ""; 137 | targets = ( 138 | 1019DBE71E51060F00C7D6F1 /* DanceExample */, 139 | ); 140 | }; 141 | /* End PBXProject section */ 142 | 143 | /* Begin PBXResourcesBuildPhase section */ 144 | 1019DBE61E51060F00C7D6F1 /* Resources */ = { 145 | isa = PBXResourcesBuildPhase; 146 | buildActionMask = 2147483647; 147 | files = ( 148 | 1019DBF61E51060F00C7D6F1 /* LaunchScreen.storyboard in Resources */, 149 | 1019DBF31E51060F00C7D6F1 /* Assets.xcassets in Resources */, 150 | 1045F3801E5169A20021062D /* LICENSE in Resources */, 151 | 1019DBF11E51060F00C7D6F1 /* Main.storyboard in Resources */, 152 | ); 153 | runOnlyForDeploymentPostprocessing = 0; 154 | }; 155 | /* End PBXResourcesBuildPhase section */ 156 | 157 | /* Begin PBXSourcesBuildPhase section */ 158 | 1019DBE41E51060F00C7D6F1 /* Sources */ = { 159 | isa = PBXSourcesBuildPhase; 160 | buildActionMask = 2147483647; 161 | files = ( 162 | 1019DBEE1E51060F00C7D6F1 /* ViewController.swift in Sources */, 163 | 1019DBEC1E51060F00C7D6F1 /* AppDelegate.swift in Sources */, 164 | 1019DBFE1E51063D00C7D6F1 /* Dance.swift in Sources */, 165 | ); 166 | runOnlyForDeploymentPostprocessing = 0; 167 | }; 168 | /* End PBXSourcesBuildPhase section */ 169 | 170 | /* Begin PBXVariantGroup section */ 171 | 1019DBEF1E51060F00C7D6F1 /* Main.storyboard */ = { 172 | isa = PBXVariantGroup; 173 | children = ( 174 | 1019DBF01E51060F00C7D6F1 /* Base */, 175 | ); 176 | name = Main.storyboard; 177 | sourceTree = ""; 178 | }; 179 | 1019DBF41E51060F00C7D6F1 /* LaunchScreen.storyboard */ = { 180 | isa = PBXVariantGroup; 181 | children = ( 182 | 1019DBF51E51060F00C7D6F1 /* Base */, 183 | ); 184 | name = LaunchScreen.storyboard; 185 | sourceTree = ""; 186 | }; 187 | /* End PBXVariantGroup section */ 188 | 189 | /* Begin XCBuildConfiguration section */ 190 | 1019DBF81E51060F00C7D6F1 /* Debug */ = { 191 | isa = XCBuildConfiguration; 192 | buildSettings = { 193 | ALWAYS_SEARCH_USER_PATHS = NO; 194 | CLANG_ANALYZER_NONNULL = YES; 195 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 196 | CLANG_CXX_LIBRARY = "libc++"; 197 | CLANG_ENABLE_MODULES = YES; 198 | CLANG_ENABLE_OBJC_ARC = YES; 199 | CLANG_WARN_BOOL_CONVERSION = YES; 200 | CLANG_WARN_CONSTANT_CONVERSION = YES; 201 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 202 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 203 | CLANG_WARN_EMPTY_BODY = YES; 204 | CLANG_WARN_ENUM_CONVERSION = YES; 205 | CLANG_WARN_INFINITE_RECURSION = YES; 206 | CLANG_WARN_INT_CONVERSION = YES; 207 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 208 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 209 | CLANG_WARN_UNREACHABLE_CODE = YES; 210 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 211 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 212 | COPY_PHASE_STRIP = NO; 213 | DEBUG_INFORMATION_FORMAT = dwarf; 214 | ENABLE_STRICT_OBJC_MSGSEND = YES; 215 | ENABLE_TESTABILITY = YES; 216 | GCC_C_LANGUAGE_STANDARD = gnu99; 217 | GCC_DYNAMIC_NO_PIC = NO; 218 | GCC_NO_COMMON_BLOCKS = YES; 219 | GCC_OPTIMIZATION_LEVEL = 0; 220 | GCC_PREPROCESSOR_DEFINITIONS = ( 221 | "DEBUG=1", 222 | "$(inherited)", 223 | ); 224 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 225 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 226 | GCC_WARN_UNDECLARED_SELECTOR = YES; 227 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 228 | GCC_WARN_UNUSED_FUNCTION = YES; 229 | GCC_WARN_UNUSED_VARIABLE = YES; 230 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 231 | MTL_ENABLE_DEBUG_INFO = YES; 232 | ONLY_ACTIVE_ARCH = YES; 233 | SDKROOT = iphoneos; 234 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 235 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 236 | TARGETED_DEVICE_FAMILY = "1,2"; 237 | }; 238 | name = Debug; 239 | }; 240 | 1019DBF91E51060F00C7D6F1 /* Release */ = { 241 | isa = XCBuildConfiguration; 242 | buildSettings = { 243 | ALWAYS_SEARCH_USER_PATHS = NO; 244 | CLANG_ANALYZER_NONNULL = YES; 245 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 246 | CLANG_CXX_LIBRARY = "libc++"; 247 | CLANG_ENABLE_MODULES = YES; 248 | CLANG_ENABLE_OBJC_ARC = YES; 249 | CLANG_WARN_BOOL_CONVERSION = YES; 250 | CLANG_WARN_CONSTANT_CONVERSION = YES; 251 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 252 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 253 | CLANG_WARN_EMPTY_BODY = YES; 254 | CLANG_WARN_ENUM_CONVERSION = YES; 255 | CLANG_WARN_INFINITE_RECURSION = YES; 256 | CLANG_WARN_INT_CONVERSION = YES; 257 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 258 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 259 | CLANG_WARN_UNREACHABLE_CODE = YES; 260 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 261 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 262 | COPY_PHASE_STRIP = NO; 263 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 264 | ENABLE_NS_ASSERTIONS = NO; 265 | ENABLE_STRICT_OBJC_MSGSEND = YES; 266 | GCC_C_LANGUAGE_STANDARD = gnu99; 267 | GCC_NO_COMMON_BLOCKS = YES; 268 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 269 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 270 | GCC_WARN_UNDECLARED_SELECTOR = YES; 271 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 272 | GCC_WARN_UNUSED_FUNCTION = YES; 273 | GCC_WARN_UNUSED_VARIABLE = YES; 274 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 275 | MTL_ENABLE_DEBUG_INFO = NO; 276 | SDKROOT = iphoneos; 277 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 278 | TARGETED_DEVICE_FAMILY = "1,2"; 279 | VALIDATE_PRODUCT = YES; 280 | }; 281 | name = Release; 282 | }; 283 | 1019DBFB1E51060F00C7D6F1 /* Debug */ = { 284 | isa = XCBuildConfiguration; 285 | buildSettings = { 286 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 287 | INFOPLIST_FILE = DanceExample/Info.plist; 288 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 289 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 290 | PRODUCT_BUNDLE_IDENTIFIER = saoudrizwan.DanceExample; 291 | PRODUCT_NAME = "$(TARGET_NAME)"; 292 | SWIFT_VERSION = 3.0; 293 | }; 294 | name = Debug; 295 | }; 296 | 1019DBFC1E51060F00C7D6F1 /* Release */ = { 297 | isa = XCBuildConfiguration; 298 | buildSettings = { 299 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 300 | INFOPLIST_FILE = DanceExample/Info.plist; 301 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 302 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 303 | PRODUCT_BUNDLE_IDENTIFIER = saoudrizwan.DanceExample; 304 | PRODUCT_NAME = "$(TARGET_NAME)"; 305 | SWIFT_VERSION = 3.0; 306 | }; 307 | name = Release; 308 | }; 309 | /* End XCBuildConfiguration section */ 310 | 311 | /* Begin XCConfigurationList section */ 312 | 1019DBE31E51060F00C7D6F1 /* Build configuration list for PBXProject "DanceExample" */ = { 313 | isa = XCConfigurationList; 314 | buildConfigurations = ( 315 | 1019DBF81E51060F00C7D6F1 /* Debug */, 316 | 1019DBF91E51060F00C7D6F1 /* Release */, 317 | ); 318 | defaultConfigurationIsVisible = 0; 319 | defaultConfigurationName = Release; 320 | }; 321 | 1019DBFA1E51060F00C7D6F1 /* Build configuration list for PBXNativeTarget "DanceExample" */ = { 322 | isa = XCConfigurationList; 323 | buildConfigurations = ( 324 | 1019DBFB1E51060F00C7D6F1 /* Debug */, 325 | 1019DBFC1E51060F00C7D6F1 /* Release */, 326 | ); 327 | defaultConfigurationIsVisible = 0; 328 | defaultConfigurationName = Release; 329 | }; 330 | /* End XCConfigurationList section */ 331 | }; 332 | rootObject = 1019DBE01E51060F00C7D6F1 /* Project object */; 333 | } 334 | -------------------------------------------------------------------------------- /DanceExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /DanceExample.xcodeproj/xcuserdata/saoud.xcuserdatad/xcschemes/DanceExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /DanceExample.xcodeproj/xcuserdata/saoud.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | DanceExample.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 1019DBE71E51060F00C7D6F1 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /DanceExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // DanceExample 4 | // 5 | // Created by Saoud Rizwan on 2/12/17. 6 | // Copyright © 2017 Saoud Rizwan. 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: [UIApplicationLaunchOptionsKey: Any]?) -> 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 invalidate graphics rendering callbacks. 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 active 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 | -------------------------------------------------------------------------------- /DanceExample/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 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /DanceExample/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 | -------------------------------------------------------------------------------- /DanceExample/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 | 31 | 40 | 49 | 58 | 67 | 73 | 86 | 99 | 110 | 123 | 136 | 137 | 138 | 139 | 140 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | -------------------------------------------------------------------------------- /DanceExample/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 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UIStatusBarHidden 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /DanceExample/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // DanceExample 4 | // 5 | // Created by Saoud Rizwan on 2/12/17. 6 | // Copyright © 2017 Saoud Rizwan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | @IBOutlet weak var circle: UIView! 14 | @IBOutlet weak var whiteLine: UIView! 15 | 16 | @IBOutlet weak var hasAnimationLabel: UILabel! 17 | @IBOutlet weak var stateLabel: UILabel! 18 | @IBOutlet weak var isRunningLabel: UILabel! 19 | @IBOutlet weak var isReversedLabel: UILabel! 20 | @IBOutlet weak var progressLabel: UILabel! 21 | @IBOutlet weak var slider: UISlider! 22 | 23 | var startPosition: CGPoint! 24 | var endPosition: CGPoint! 25 | 26 | override func viewDidLoad() { 27 | super.viewDidLoad() 28 | circle.layer.cornerRadius = circle.frame.width / 2 29 | startPosition = CGPoint(x: whiteLine.frame.minX, y: whiteLine.center.y / 2) 30 | endPosition = CGPoint(x: whiteLine.frame.maxX, y: whiteLine.center.y / 2) 31 | circle.center = startPosition 32 | slider.value = 0 33 | slider.isContinuous = true 34 | let _ = Timer.scheduledTimer(withTimeInterval: 0.001, repeats: true, block: { (_) in 35 | self.updateValues() 36 | }) 37 | 38 | /* 39 | In order to animate a view, you first need to define an animation block with that view's final properties inside. 40 | Remember, when you first access a view's dance, you must use the 'dance' keyword. (Ex. view.dance) 41 | 42 | For a list of properties you can animate, see Table 4-1 on: 43 | https://developer.apple.com/library/content/documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/AnimatingViews/AnimatingViews.html 44 | */ 45 | 46 | /* 47 | You can create an animation using: 48 | 49 | 1) built-in UIKit timing curve 50 | * easeInOut (slow at beginning and end) 51 | * easeIn (slow at beginning) 52 | * easeOut (slow at end) 53 | * linear 54 | */ 55 | 56 | circle.dance.animate(duration: 10.0, curve: .easeInOut) { (make) in 57 | make.center = self.endPosition 58 | } 59 | 60 | // Better yet, let's do that with Swift's shorthand notation: 61 | 62 | circle.dance.animate(duration: 10.0, curve: .easeInOut) { 63 | $0.center = self.endPosition 64 | } 65 | 66 | /* 67 | Tip: Option + click the '.animate' part of that ^ function. 68 | That way you can see detailed documentation of each of Dance's functions. 69 | */ 70 | 71 | /* 72 | 2) custom timing curve object 73 | I recommend reading Apple's documentation on UITimingCurveProvider 74 | https://developer.apple.com/reference/uikit/uitimingcurveprovider 75 | */ 76 | 77 | let timingParameters = UISpringTimingParameters(mass: 1.0, stiffness: 0.2, damping: 0.5, initialVelocity: CGVector(dx: 0, dy: 0)) 78 | 79 | circle.dance.animate(duration: 10.0, timingParameters: timingParameters) { 80 | $0.center = self.endPosition 81 | } 82 | 83 | /* 84 | 3) custom cubic Bézier timing curve 85 | https://developer.apple.com/reference/uikit/uiviewpropertyanimator/1648368-init 86 | */ 87 | 88 | let controlPoint1 = CGPoint(x: 0, y: 1) 89 | let controlPoint2 = CGPoint(x: 1, y: 0) 90 | 91 | circle.dance.animate(duration: 10.0, controlPoint1: controlPoint1, controlPoint2: controlPoint2) { 92 | $0.center = self.endPosition 93 | } 94 | 95 | /* 96 | 4) sping-based timing information 97 | Basically, dampingRatio should be a CGFloat between 0 and 1. The closer it is to 0, the 'springy-er' the animation will be. The closer it is to 1, the damper the spring animation will be. 98 | */ 99 | 100 | circle.dance.animate(duration: 10.0, dampingRatio: 0.5) { 101 | $0.center = self.endPosition 102 | } 103 | 104 | /* 105 | 106 | You can even animate constraint changes easily with Dance: 107 | 108 | Recommended reading: 109 | * Animating Constraints 110 | http://stackoverflow.com/a/27372232/3502608 111 | 112 | * What is .layoutIfNeeded() 113 | http://stackoverflow.com/a/29151376/3502608 114 | 115 | * Animating Constraints Using iOS 10’s New UIViewPropertyAnimator 116 | https://medium.com/@sdrzn/animating-constraints-using-ios-10s-new-uiviewpropertyanimator-944bbb42347b#.du139c6c7 117 | 118 | TL;DR: you can call .layoutIfNeeded() on a view to layout it and its subviews. This means that you're updating its constraints and its subviews' constraints, and putting .layoutIfNeeded() in a Dance animation block (or any UIKit animation block) will animate the constraint changes. 119 | */ 120 | 121 | circle.dance.animate(duration: 10.0, curve: .easeOut) { 122 | $0.layoutIfNeeded() 123 | } 124 | 125 | /* 126 | Wait, so what's being animated at the end of viewDidLoad? Didn't we just make a ton of .animate() blocks at once? 127 | 128 | Dance is forgiving, so if we accidentally create a new animation block for a view that already has an animation block associated with it, then Dance will finish the old animation (leaving it at its current position.) 129 | 130 | So let's set our desired animation block on circle (and set a completion block while we're at it) : 131 | */ 132 | 133 | circle.dance.animate(duration: 10.0, curve: .linear) { 134 | $0.center = self.endPosition 135 | }.addCompletion { (position) in 136 | switch position { 137 | case .start: 138 | print("Finished the animation at the start position.") 139 | case .current: 140 | print("Finished the animation at the current position.") 141 | case .end: 142 | print("Finished the animation at the end position.") 143 | } 144 | } 145 | 146 | /* ----- IMPORTANT SIDE NOTE ----- 147 | 148 | You can animate properties of other views besides the dancing view inside its animation block. For example: 149 | 150 | circle.dance.animate(duration: 2.0, curve: .easeIn) { 151 | $0.center = newCenter // $0 is circle 152 | self.triangle.center = newCenter // triangle is a view completely unassociated with circle, but Dance will make it a part of circle's animation block. So whenever you pause circle's dance animation, then the triangle gets paused as well. But you can't access that animation using triangle.dance, since the animation is associated with circle. 153 | } 154 | */ 155 | 156 | /* ---------- DEBUGGING ---------- 157 | 158 | Debugging with Dance is easy. Let's say you accidentally call circle.dance.start() before you ever create a Dance animation for circle. 159 | Instead of causing a runtime error or fatal error, Dance will print the following: 160 | 161 | ** Dance Error: view with dance.tag = does not have an active animation! ** 162 | 163 | Dance assigns each dance animation a dance tag, which you can access like so: 164 | 165 | circle.dance.tag 166 | 167 | This way you can keep track of you views' dance animations and easily handle any of Dance's error print logs. 168 | */ 169 | } 170 | 171 | @IBAction func startTapped(_ sender: Any) { 172 | 173 | // in case you tap 'circle.dance.start()' after the animation declared in viewDidLoad has already finished 174 | if !circle.dance.hasAnimation { resetCircleAnimation() } 175 | 176 | // start the animation associated with circle 177 | circle.dance.start() 178 | 179 | // or circle.dance.start(after: 5.0) for a delay before we start the animation 180 | 181 | /* 182 | We could have just as easily started our animation after declaring an animation block for circle using function chaining, like so: 183 | 184 | circle.dance.animate(duration: 2.0, curve: .easeOut) { 185 | $0.center = self.endPosition 186 | }.addCompletion { (position) in 187 | switch position { 188 | case .start: 189 | print("Finished the animation at the start position.") 190 | case .current: 191 | print("Finished the animation at the current position.") 192 | case .end: 193 | print("Finished the animation at the end position.") 194 | } 195 | }.start() 196 | 197 | Notice how we don't have to use the 'dance' keyword when function chaining. 198 | */ 199 | 200 | 201 | } 202 | 203 | @IBAction func pauseTapped(_ sender: Any) { 204 | // Note: .pause() doesn't render the view at it's current position, you need to call .finish(at:) in order to render the view and its subviews at the desired finishing position (.start, .current, or .end) 205 | 206 | circle.dance.pause() 207 | } 208 | 209 | @IBAction func finishTapped(_ sender: Any) { 210 | /* 211 | .finish(at:) takes a UIViewAnimatingPosition, which can be one of .start, .end, or .current. This method ends the view's animation block and renders the view in the desired position. Then it immediately triggers any completion blocks declared for the view's animation. 212 | Go ahead and change '.current' to '.end' and see what happens. 213 | */ 214 | 215 | circle.dance.finish(at: .current) 216 | } 217 | 218 | @IBAction func reverseTapped(_ sender: Any) { 219 | // .reverse() reverses the animation block. Think of the animation block as a movie, and .reverse() plays the movie backwards. 220 | 221 | circle.dance.reverse() 222 | 223 | /* 224 | You could just as easily do: 225 | circle.dance.isReversed = true 226 | */ 227 | 228 | /* 229 | Note: you can only reverse an animation if it's been started at least once. If you want to reverse an animation from the first start(), you could do something like: 230 | circle.dance.start().reverse() 231 | */ 232 | } 233 | 234 | @IBAction func sliderValueChanged(_ sender: Any) { 235 | circle.dance.setProgress(to: slider.value) 236 | // or circle.dance.progress = CGFloat(slider.value) 237 | } 238 | 239 | @IBAction func resetTapped(_ sender: Any) { 240 | resetCircleAnimation() 241 | } 242 | 243 | func resetCircleAnimation() { 244 | if circle.dance.hasAnimation { 245 | circle.dance.finish(at: .current) 246 | } 247 | circle.center = startPosition 248 | circle.dance.animate(duration: 10.0, curve: .linear) { 249 | $0.center = self.endPosition 250 | }.addCompletion { (position) in 251 | switch position { 252 | case .start: 253 | print("Finished the animation at the start position.") 254 | case .current: 255 | print("Finished the animation at the current position.") 256 | case .end: 257 | print("Finished the animation at the end position.") 258 | } 259 | } 260 | } 261 | 262 | } 263 | 264 | extension ViewController { 265 | func updateValues() { 266 | if circle.dance.hasAnimation { 267 | hasAnimationLabel.text = "circle.dance.hasAnimation = true" 268 | switch circle.dance.state { 269 | case .active: 270 | stateLabel.text = "circle.dance.state = .active" 271 | case .inactive: 272 | stateLabel.text = "circle.dance.state = .inactive" 273 | } 274 | if circle.dance.isRunning { 275 | isRunningLabel.text = "circle.dance.isRunning = true" 276 | } else { 277 | isRunningLabel.text = "circle.dance.isRunning = false" 278 | } 279 | if circle.dance.isReversed { 280 | isReversedLabel.text = "circle.dance.isReversed = true" 281 | } else { 282 | isReversedLabel.text = "circle.dance.isReversed = false" 283 | } 284 | slider.setValue(Float(circle.dance.progress), animated: false) 285 | progressLabel.text = String(format: "circle.dance.progress = %.2f", Float(circle.dance.progress)) 286 | } else { 287 | hasAnimationLabel.text = "circle.dance.hasAnimation = false" 288 | stateLabel.text = "circle.dance.state = .inactive" 289 | isRunningLabel.text = "circle.dance.isRunning = false" 290 | isReversedLabel.text = "circle.dance.isReversed = false" 291 | slider.setValue(0, animated: false) 292 | progressLabel.text = String(format: "circle.dance.progress = 0.0") 293 | } 294 | } 295 | } 296 | 297 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Saoud Rizwan 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Dance 3 |

4 | 5 |

6 | Platform: iOS 10+ 7 | Language: Swift 3 8 | CocoaPods compatible 9 | License: MIT 10 |

11 | 12 |

13 | Installation 14 | • Usage 15 | • Debugging 16 | • Animatable Properties 17 | • License 18 |

19 | 20 | Dance is a **powerful** and **straightforward** animation framework built upon the new `UIViewPropertyAnimator` class introduced in iOS 10. With Dance, creating an animation for a view is as easy as calling `view.dance.animate { ... }`, which can then be started, paused, reversed, scrubbed through, and finished anywhere that the view can be referenced. Dance is especially **forgiving**, and provides the power that `UIViewPropertyAnimator` brings to iOS while maintaining ease of use. 21 | 22 | ## Quick Start 23 | ```swift 24 | import Dance 25 | 26 | class MyViewController: UIViewController { 27 | 28 | let circle = UIView() 29 | 30 | override func viewDidLoad() { 31 | super.viewDidLoad() 32 | 33 | circle.dance.animate(duration: 2.0, curve: .easeInOut) { 34 | $0.transform = CGAffineTransform(scaleX: 1.5, y: 1.5) 35 | $0.center = self.view.center 36 | $0.backgroundColor = .blue 37 | // ... see 'Animatable Properties' for more options 38 | }.addCompletion { _ in 39 | self.view.backgroundColor = .green 40 | }.start(after: 5.0) 41 | } 42 | 43 | func pauseAnimation() { 44 | circle.dance.pause() 45 | } 46 | 47 | } 48 | ``` 49 | 50 | With Dance, you can create referenceable animations attached to views. That means you can call: 51 | * `.pause()` 52 | * `.start()` 53 | * `.reverse()` 54 | * `.setProgress(to:)` 55 | * `.finish(at:)` 56 | 57 | anywhere the view can be referenced. 58 | 59 | ## Compatibility 60 | 61 | Dance requires **iOS 10+** and is compatible with **Swift 3** projects. 62 | 63 | ## Installation 64 | 65 | * Installation for CocoaPods: 66 | 67 | ```ruby 68 | platform :ios, '10.0' 69 | target 'ProjectName' do 70 | use_frameworks! 71 | 72 | pod 'Dance' 73 | 74 | end 75 | ``` 76 | * Or drag and drop `Dance.swift` into your project. 77 | 78 | And `import Dance` in the files you'd like to use it. 79 | 80 | ## Usage 81 | 82 | *It's recommended to look through the example project—it has detailed documentation of everything Dance has to offer.* 83 | 84 | **Note:** throughout this document, `circle` will act as the view being animated. You can use Dance on any instance of a `UIView` or `UIView` subclass, such as `UILabel`, `UITextField`, `UIButton`, etc. 85 | 86 | **Using Dance is easy.** 87 | 88 | 1. [Create an animation](#creating-an-animation) for a view, and optionally [add completion blocks](#adding-completion-blocks). 89 | 90 | 2. [Start](#starting-an-animation) the animation. 91 | 92 | 3. [Pause](#pausing-an-animation), [reverse](#reversing-an-animation), or [scrub through](#scrubbing-through-an-animation) the animation. 93 | 94 | 4. Let the animation complete on its own or manually [finish](#finishing-an-animation) the animation early, triggering any completion blocks. 95 | 96 | ### Creating an Animation 97 | 98 | [What properties can I animate?](#animatable-properties) 99 | 100 | #### UIKit timing curve 101 | * easeInOut (slow at beginning and end) 102 | * easeIn (slow at beginning) 103 | * easeOut (slow at end) 104 | * linear 105 | 106 | ```swift 107 | circle.dance.animate(duration: 2.0, curve: .easeInOut) { (make) in 108 | make.center = newCenter 109 | } 110 | ``` 111 | ... alternatively: 112 | 113 | ```swift 114 | circle.dance.animate(duration: 2.0, curve: .easeInOut) { 115 | $0.center = newCenter 116 | } 117 | ``` 118 | 119 | #### UITimingCurveProvider 120 | ```swift 121 | let timingParameters = UISpringTimingParameters(mass: 1.0, stiffness: 0.2, damping: 0.5, initialVelocity: CGVector(dx: 0, dy: 5)) 122 | 123 | circle.dance.animate(duration: 2.0, timingParameters: timingParameters) { 124 | $0.center = newCenter 125 | } 126 | ``` 127 | 128 | #### Custom Cubic Bézier Timing Curve 129 | ```swift 130 | let controlPoint1 = CGPoint(x: 0, y: 1) 131 | let controlPoint2 = CGPoint(x: 1, y: 0) 132 | 133 | circle.dance.animate(duration: 2.0, controlPoint1: controlPoint1, controlPoint2: controlPoint2) { 134 | $0.center = newCenter 135 | } 136 | ``` 137 | bezier curve 138 | 139 | *https://developer.apple.com/videos/play/wwdc2016/216/* 140 | 141 | #### Spring-based Timing Information 142 | ```swift 143 | circle.dance.animate(duration: 2.0, dampingRatio: 0.5) { 144 | $0.center = newCenter 145 | } 146 | ``` 147 | 148 | ### Starting an Animation 149 | After creating an animation block using `.animate { ... }`, the animation doesn't start until you call `.start()`. 150 | ```swift 151 | circle.dance.start() 152 | ``` 153 | ```swift 154 | circle.dance.start(after: 5.0) // for a delay (in seconds) before starting the animation 155 | ``` 156 | 157 | ### Adding Completion Blocks 158 | Add as many completion blocks as you need, wherever you need to. When an animation finishes, either by playing out the set animation or by calling `.finish(at:)`, then all completion blocks are triggered. 159 | ```swift 160 | circle.dance.addCompletion { (position) in 161 | switch position { 162 | case .start: 163 | print("Finished the animation at the start position.") 164 | case .current: 165 | print("Finished the animation at the current position.") 166 | case .end: 167 | print("Finished the animation at the end position.") 168 | } 169 | } 170 | ``` 171 | **Note:** you can't add a completion block to a finished animation. 172 | 173 | ### Pausing an Animation 174 | ```swift 175 | circle.dance.pause() 176 | ``` 177 | ```swift 178 | circle.dance.pause(after: 5.0) // for a delay (in seconds) before pausing the animation 179 | ``` 180 | **Note:** this won't render the view at the paused position, you must then also call `.finish(at: .current)` to do that. 181 | 182 | 183 | ### Reversing an Animation 184 | Calling this method will reverse the animation in its tracks, like playing a video backwards. 185 | ```swift 186 | circle.dance.reverse() 187 | ``` 188 | ```swift 189 | circle.dance.isReversed = true 190 | ``` 191 | **Note:** the position value in the completion block will stay the same after calling `.reverse()`. For example, if a view's animation is reversed and the view ends up in its initial position, then the completion closure's position parameter will be `.start`, not `.end`. 192 | 193 | 194 | ### Scrubbing Through an Animation 195 | Dance animations are like movies—you can scrub through them using the `.progress` property. 196 | ```swift 197 | circle.dance.setProgress(to: 0.5) // takes value 0-1 198 | ``` 199 | ```swift 200 | circle.dance.progress = 0.5 201 | ``` 202 | 203 | ### Finishing an Animation 204 | Animations will automatically finish when they complete and reach their target values, triggering any completion blocks. However if you pause an animation and/or want to finish that animation early, you must call `.finish(at:)`. 205 | ```swift 206 | circle.dance.finish(at: .current) // or .start, .end 207 | ``` 208 | 209 | ### Dance Properties 210 | ```swift 211 | circle.dance.hasAnimation: Bool { get } 212 | ``` 213 | ```swift 214 | circle.dance.progress: CGFloat { get, set } 215 | ``` 216 | ```swift 217 | circle.dance.isRunning: Bool { get } 218 | ``` 219 | ```swift 220 | circle.dance.isReversed: Bool { get, set } 221 | ``` 222 | For [debugging](#debugging) purposes: 223 | ```swift 224 | circle.dance.state: DanceAnimationState { get } // .inactive, .active 225 | ``` 226 | ```swift 227 | circle.dance.tag: Int { get } 228 | ``` 229 | 230 | ### What About Constraints? 231 | 232 | Dance works great with constraints. To animate constraint changes: 233 | 234 | ```swift 235 | // update constraints for circle and/or its subviews first 236 | // ... 237 | circle.dance.animate(duration: 2.0, curve: .easeInOut) { 238 | $0.layoutIfNeeded() 239 | } 240 | ``` 241 | > Usually most developers would call `self.view.layoutIfNeeded()` in a standard `UIView.animate()` block. However this is bad practice as it lays out all subviews in the current view, when they may only want to animate constraint changes for certain views. With Dance, calling `$0.layoutIfNeeded()` only lays out the view that's being animated and its subviews, ensuring low energy impact and high FPS. 242 | 243 | ### Function Chaining 244 | 245 | Dance allows you to chain multiple animation commands together, resulting in an elegant and easy-to-read syntax. 246 | For example: 247 | ```swift 248 | circle.dance.animate(duration: 2.0, curve: .easeInOut) { 249 | $0.transform = CGAffineTransform(scaleX: 1.5, y: 1.5) 250 | $0.center = self.view.center 251 | $0.backgroundColor = .blue 252 | }.addCompletion { (position) in 253 | if position == .end { 254 | print("Animation reached the target end position!") 255 | } 256 | }.start(after: 5.0) 257 | ``` 258 | ```swift 259 | circle.dance.pause().setProgress(to: 0.25) 260 | ``` 261 | ```swift 262 | print(circle.dance.pause().progress) 263 | ``` 264 | ```swift 265 | circle.dance.start().reverse() 266 | ``` 267 | 268 | ### Debugging 269 | 270 | Dance is *forgiving*, meaning that it handles any mistakes that you might make without causing any runtime errors. If you do make a mistake, for example starting an animation that doesn't exist, then Dance will print the following error in the console: 271 | ``` 272 | ** Dance Error: view with dance.tag = does not have an active animation! ** 273 | ``` 274 | Dance assigns each dance animation a dance tag, which you can access like so: 275 | ```swift 276 | circle.dance.tag 277 | ``` 278 | This way you can keep track of you views' dance animations and easily handle any of Dance's error print logs. 279 | 280 | Furthermore, you can get the state of a view's dance animation: 281 | 282 | ```swift 283 | switch circle.dance.state { 284 | case .active: 285 | // A dance animation has been created for the view and has been started. 286 | // Note: a paused animation's state will return .active 287 | case .inactive: 288 | // Either there is no dance animation associated with the view, or an animation exists but hasn't been started. 289 | // Note: a finished animation is inactive because the animation effectively ceases to exist after it finishes 290 | } 291 | ``` 292 | 293 | ## Animatable Properties 294 | 295 | | UIView Property | Changes you can make | 296 | | -------------------- |------------------------------------------------------------| 297 | | [frame](https://developer.apple.com/reference/uikit/uiview/1622621-frame) | Modify this property to change the view’s size and position relative to its superview’s coordinate system. (If the `transform` property does not contain the identity transform, modify the `bounds` or `center` properties instead.) | 298 | | [bounds](https://developer.apple.com/reference/uikit/uiview/1622580-bounds) | Modify this property to change the view’s size. | 299 | | [center](https://developer.apple.com/reference/uikit/uiview/1622627-center) | Modify this property to change the view’s position relative to its superview’s coordinate system. | 300 | | [transform](https://developer.apple.com/reference/uikit/uiview/1622459-transform) | Modify this property to scale, rotate, or translate the view relative to its center point. Transformations using this property are always performed in 2D space. (To perform 3D transformations, you must animate the view’s layer object using Core Animation.) | 301 | | [alpha](https://developer.apple.com/reference/uikit/uiview/1622417-alpha) | Modify this property to gradually change the transparency of the view. | 302 | | [backgroundColor](https://developer.apple.com/reference/uikit/uiview/1622591-backgroundcolor) | Modify this property to change the view’s background color. | 303 | | [contentStretch](https://developer.apple.com/reference/uikit/uiview/1622511-contentstretch) | Modify this property to change the way the view’s contents are stretched to fill the available space. | 304 | 305 | *https://developer.apple.com/library/content/documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/AnimatingViews/AnimatingViews.html* 306 | 307 | ## Documentation 308 | Option + click on any of Dance's methods for detailed documentation. 309 | documentation 310 | 311 | ## License 312 | 313 | Dance uses the MIT license. Please file an issue if you have any questions or if you'd like to share how you're using Dance. 314 | 315 | ## Contribute 316 | 317 | Dance is in its infancy, but v1.0 provides the barebones of a revolutionary new way to animate in iOS. Please feel free to send pull requests of any features you think would add to Dance and its philosophy. 318 | 319 | ## Questions? 320 | 321 | Contact me by email hello@saoudmr.com, or by twitter @sdrzn. Please create an issue if you come across a bug or would like a feature to be added. 322 | 323 | ## Credits 324 | 325 | Disco Ball Icon by [Effach from the Noun Project](https://thenounproject.com/francois.hardy.359/) 326 | --------------------------------------------------------------------------------