Skip to content

Commit

Permalink
v1.0.3 introduces dance.tag for debugging
Browse files Browse the repository at this point in the history
  • Loading branch information
gitroyalty-bot committed Feb 13, 2017
1 parent db5fb17 commit 76ab4f1
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 35 deletions.
87 changes: 52 additions & 35 deletions Dance.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ fileprivate class DanceFactory {
// MARK: UIViewPropertyAnimator Wrapper

var tagCount: Int = 0
var animators = [Int: UIViewPropertyAnimator]() // [UIView.tag: UIViewPropertyAnimator()]
var animators = [Int: UIViewPropertyAnimator]() // [dance.tag: UIViewPropertyAnimator()]

/// Initialize a UIViewPropertyAnimator with timing parameters.
func createNewAnimator(tag: Int, duration: TimeInterval, timingParameters: UITimingCurveProvider, animations: @escaping (() -> Void)) {
Expand Down Expand Up @@ -216,16 +216,22 @@ fileprivate class DanceFactory {
func handle(error: DanceError, forViewWithTag tag: Int) {
switch error {
case .noAnimation:
print("\n** Dance Error: view \(tag) does not have an animation in progress! **\n")
print("** Dance Error: view with dance.tag = \(tag) does not have an active animation! **")
}
}

}

// MARK: - Dance (should be accessed through UIView extension variable)

// MARK: - Dance

// This class should only be accessed through the 'dance' variable declared in the UIView extension below.
// (Dance needs to have a public modifier in order to be accessed globally through the UIView Extension.)

public class Dance {

fileprivate var dancingView: UIView!
public var tag: Int = 0

fileprivate init(dancingView: UIView) {
self.dancingView = dancingView
Expand All @@ -236,43 +242,43 @@ public class Dance {
/// GET: Returns a boolean value of true if the view has an animation attached to it, and false otherwise.
public var hasAnimation: Bool {
get {
return DanceFactory.instance.getHasAnimation(tag: dancingView.tag)
return DanceFactory.instance.getHasAnimation(tag: self.tag)
}
}

/// GET: Returns a CGFloat value between 0 and 1 that inidicates the fraction value of the completion of the view's animation.
/// 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.
public var progress: CGFloat {
get {
return DanceFactory.instance.getFractionComplete(tag: dancingView.tag)
return DanceFactory.instance.getFractionComplete(tag: self.tag)
}
set {
DanceFactory.instance.setFractionComplete(tag: dancingView.tag, newFractionComplete: newValue)
DanceFactory.instance.setFractionComplete(tag: self.tag, newFractionComplete: newValue)
}
}

/// GET: Returns the view's current animation state (inactive, active, stopped). Dance ensures that your animation doesn't run into any problems, so you should only ever receive .inactive or .active for any view.
public var state: UIViewAnimatingState {
get {
return DanceFactory.instance.getState(tag: dancingView.tag)
return DanceFactory.instance.getState(tag: self.tag)
}
}

/// GET: Returns a boolean value indicating whether the view's animation is currently running (active and started) or not.
public var isRunning: Bool {
get {
return DanceFactory.instance.getIsRunning(tag: dancingView.tag)
return DanceFactory.instance.getIsRunning(tag: self.tag)
}
}

/// GET: Returns a boolean value indicating whether the view's animation is currently reversed or not.
/// 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.
public var isReversed: Bool {
get {
return DanceFactory.instance.getIsReversed(tag: dancingView.tag)
return DanceFactory.instance.getIsReversed(tag: self.tag)
}
set {
DanceFactory.instance.setIsReversed(tag: dancingView.tag, isReversed: newValue)
DanceFactory.instance.setIsReversed(tag: self.tag, isReversed: newValue)
}
}

Expand All @@ -293,13 +299,13 @@ public class Dance {
@discardableResult public func animate(duration: TimeInterval, curve: UIViewAnimationCurve, _ animation: @escaping (make) -> Void) -> Dance {

if self.hasAnimation {
DanceFactory.instance.finishAnimation(tag: dancingView.tag, at: .current)
DanceFactory.instance.finishAnimation(tag: self.tag, at: .current)
}

dancingView.tag = DanceFactory.instance.tagCount
self.tag = DanceFactory.instance.tagCount
DanceFactory.instance.tagCount += 1

DanceFactory.instance.createNewAnimator(tag: dancingView.tag, duration: duration, curve: curve) {
DanceFactory.instance.createNewAnimator(tag: self.tag, duration: duration, curve: curve) {
animation(self.dancingView)
}

Expand All @@ -315,13 +321,13 @@ public class Dance {
@discardableResult public func animate(duration: TimeInterval, timingParameters: UITimingCurveProvider, _ animation: @escaping (make) -> Void) -> Dance {

if self.hasAnimation {
DanceFactory.instance.finishAnimation(tag: dancingView.tag, at: .current)
DanceFactory.instance.finishAnimation(tag: self.tag, at: .current)
}

dancingView.tag = DanceFactory.instance.tagCount
self.tag = DanceFactory.instance.tagCount
DanceFactory.instance.tagCount += 1

DanceFactory.instance.createNewAnimator(tag: dancingView.tag, duration: duration, timingParameters: timingParameters) {
DanceFactory.instance.createNewAnimator(tag: self.tag, duration: duration, timingParameters: timingParameters) {
animation(self.dancingView)
}

Expand All @@ -338,13 +344,13 @@ public class Dance {
@discardableResult public func animate(duration: TimeInterval, controlPoint1 point1: CGPoint, controlPoint2 point2: CGPoint, _ animation: @escaping (make) -> Void) -> Dance {

if self.hasAnimation {
DanceFactory.instance.finishAnimation(tag: dancingView.tag, at: .current)
DanceFactory.instance.finishAnimation(tag: self.tag, at: .current)
}

dancingView.tag = DanceFactory.instance.tagCount
self.tag = DanceFactory.instance.tagCount
DanceFactory.instance.tagCount += 1

DanceFactory.instance.createNewAnimator(tag: dancingView.tag, duration: duration, controlPoint1: point1, controlPoint2: point2) {
DanceFactory.instance.createNewAnimator(tag: self.tag, duration: duration, controlPoint1: point1, controlPoint2: point2) {
animation(self.dancingView)
}

Expand All @@ -360,13 +366,13 @@ public class Dance {
@discardableResult public func animate(duration: TimeInterval, dampingRatio: CGFloat, _ animation: @escaping (make) -> Void) -> Dance {

if self.hasAnimation {
DanceFactory.instance.finishAnimation(tag: dancingView.tag, at: .current)
DanceFactory.instance.finishAnimation(tag: self.tag, at: .current)
}

dancingView.tag = DanceFactory.instance.tagCount
self.tag = DanceFactory.instance.tagCount
DanceFactory.instance.tagCount += 1

DanceFactory.instance.createNewAnimator(tag: dancingView.tag, duration: duration, dampingRatio: dampingRatio) {
DanceFactory.instance.createNewAnimator(tag: self.tag, duration: duration, dampingRatio: dampingRatio) {
animation(self.dancingView)
}

Expand All @@ -377,51 +383,53 @@ public class Dance {
///
/// - 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.)
@discardableResult public func addCompletion(_ completion: @escaping (UIViewAnimatingPosition) -> Void) -> Dance {
DanceFactory.instance.addCompletion(tag: dancingView.tag, completion: completion)
DanceFactory.instance.addCompletion(tag: self.tag, completion: completion)
return dancingView.dance
}

/// Starts the animation for the view (must be called after declaring an animation block.)
@discardableResult public func start() -> Dance {
DanceFactory.instance.startAnimation(tag: dancingView.tag)
DanceFactory.instance.startAnimation(tag: self.tag)
return dancingView.dance
}

/// Starts the animation for the view after a delay (must be called after declaring an animation block.)
///
/// - Parameter delay: The amount of time (measured in seconds) to wait before beginning the animations.
@discardableResult public func start(after delay: TimeInterval) -> Dance {
DanceFactory.instance.startAnimation(tag: dancingView.tag, afterDelay: delay)
DanceFactory.instance.startAnimation(tag: self.tag, afterDelay: delay)
return dancingView.dance
}

/// 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.
@discardableResult public func pause() -> Dance {
DanceFactory.instance.pauseAnimation(tag: dancingView.tag)
DanceFactory.instance.pauseAnimation(tag: self.tag)
return dancingView.dance
}

/// 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.
///
/// - Parameter delay: The amount of time (measured in seconds) to wait before pausing the animations.
@discardableResult public func pause(after delay: TimeInterval) -> Dance {
DanceFactory.instance.pauseAnimation(tag: dancingView.tag, afterDelay: delay)
DanceFactory.instance.pauseAnimation(tag: self.tag, afterDelay: delay)
return dancingView.dance
}

/// Finished the view's current animation. Triggers the animation's completion blocks to take action immediately.
///
/// - 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.
@discardableResult public func finish(at position: UIViewAnimatingPosition) -> Dance {
DanceFactory.instance.finishAnimation(tag: dancingView.tag, at: position)
DanceFactory.instance.finishAnimation(tag: self.tag, at: position)
return dancingView.dance
}

/// Reverses the animation. Calling .reverse() on an already reversed view animation will make it animate in the initial direction.
/// Alternative to setting the .isReversed variable.
@discardableResult public func reverse() -> Dance {
let reversedState = DanceFactory.instance.getIsReversed(tag: dancingView.tag)
DanceFactory.instance.setIsReversed(tag: dancingView.tag, isReversed: !reversedState)
let reversedState = DanceFactory.instance.getIsReversed(tag: self.tag) // leave this here in order to print debug error
if self.hasAnimation {
DanceFactory.instance.setIsReversed(tag: self.tag, isReversed: !reversedState)
}
return dancingView.dance
}

Expand All @@ -431,42 +439,51 @@ public class Dance {
progress = newProgress.convertToCGFloat()
return dancingView.dance
}

}



// MARK: - UIView Extension for Dance

@available(iOS 10.0, *)
extension UIView {

public var dance: Dance {
get {
return DanceExtensionStoredPropertyHandler.associatedObject(base: self, key: &DanceExtensionStoredPropertyHandler.danceKey) {
return Dance(dancingView: self)
return Dance(dancingView: self) // initial value - is reference type so it won't change
}
}
set {
DanceExtensionStoredPropertyHandler.associateObject(base: self, key: &DanceExtensionStoredPropertyHandler.danceKey, value: newValue)
}
}

}


// MARK: - Boilerplate code to store properties in Extensions using Associated Objects

class DanceExtensionStoredPropertyHandler {
fileprivate class DanceExtensionStoredPropertyHandler {

static var danceKey: UInt8 = 0

static func associatedObject<ValueType: AnyObject>(base: AnyObject, key: UnsafePointer<UInt8>, initialiser: () -> ValueType) -> ValueType {
if let associated = objc_getAssociatedObject(base, key)
as? ValueType { return associated }
if let associated = objc_getAssociatedObject(base, key) as? ValueType {
return associated
}
let associated = initialiser()
objc_setAssociatedObject(base, key, associated, .OBJC_ASSOCIATION_RETAIN)
return associated
}

static func associateObject<ValueType: AnyObject>(base: AnyObject, key: UnsafePointer<UInt8>, value: ValueType) {
objc_setAssociatedObject(base, key, value, .OBJC_ASSOCIATION_RETAIN)
}

}


// MARK: - CGFloatConvertable Extension for Double, Float, CGFloat
// (in order to accept non-CGFloats in setProgress(to:))

Expand Down
14 changes: 14 additions & 0 deletions DanceExample/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,20 @@ class ViewController: UIViewController {
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.
}
*/

/* ---------- DEBUGGING ----------

Debugging with Dance is easy. Let's say you accidentally call circle.dance.start() before you ever create a Dance animation for circle.
Instead of causing a runtime error or fatal error, Dance will print the following:

** Dance Error: view with dance.tag = <tag> does not have an active animation! **

Dance assigns each dance animation a dance tag, which you can access like so:

circle.dance.tag

This way you can keep track of you views' dance animations and easily handle any of Dance's error print logs.
*/
}

@IBAction func startTapped(_ sender: Any) {
Expand Down

0 comments on commit 76ab4f1

Please sign in to comment.