                 return false
             }
         }
-        
+
         return true
     }
-    
-    private func beginScroll() {
-        beginScroll(afterDelay: true)
-    }
-    
-    private func beginScroll(afterDelay: Bool) {
-        switch self.type {
-        case .leftRight, .rightLeft:
-            scrollAway(animationDuration, delay: animationDelay)
-        default:
-            scrollcontinuous(animationDuration, delay: animationDelay)
-        }
-    }
-    
+
     private func returnLabelToHome() {
         // Remove any gradient animation
         maskLayer?.removeAllAnimations()
-        
+
         // Remove all sublabel position animations
         sublabel.layer.removeAllAnimations()
-    }
-    
-    private func scroll(_ interval: CGFloat, delay: CGFloat = 0.0, scroller: Scroller, fader: CAKeyframeAnimation?) {
-        var scroller = scroller
+
+        // Remove completion block
+        scrollCompletionBlock = nil
+    }
+
+    private func beginScroll(_ sequence: [MarqueeStep]) {
+        let scroller = generateScrollAnimation(sequence)
+        let fader = generateGradientAnimation(sequence, totalDuration: scroller.duration)
+
+        scroll(scroller, fader: fader)
+    }
+
+    private func scroll(_ scroller: MLAnimation, fader: MLAnimation?) {
         // Check for conditions which would prevent scrolling
         if !labelReadyForScroll() {
             return
         }
-        
+        // Convert fader to var
+        var fader = fader
+
         // Call pre-animation hook
         labelWillBeginScroll()
-        
+
         // Start animation transactions
         CATransaction.begin()
-        let transDuration = transactionDurationType(type, interval: interval, delay: delay)
-        CATransaction.setAnimationDuration(transDuration)
-        
+        CATransaction.setAnimationDuration(TimeInterval(scroller.duration))
+
         // Create gradient animation, if needed
         let gradientAnimation: CAKeyframeAnimation?
         // Check for IBDesignable
         #if !TARGET_INTERFACE_BUILDER
-            if fadeLength > 0.0 {
-                // Remove any setup animation, but apply final values
-                if let setupAnim = maskLayer?.animation(forKey: "setupFade") as? CABasicAnimation,
-                    let finalColors = setupAnim.toValue as? [CGColor] {
-                    maskLayer?.colors = finalColors
-                }
-                maskLayer?.removeAnimation(forKey: "setupFade")
-                
-                // Generate animation if needed
-                if let previousAnimation = fader {
-                    gradientAnimation = previousAnimation
-                } else {
-                    gradientAnimation = self.gradientKeyFrameAnimation(for: fadeLength, interval: interval, delay: delay)
-                }
-                
-                // Apply scrolling animation
-                maskLayer?.add(gradientAnimation!, forKey: "gradient")
+        if fadeLength > 0.0 {
+            // Remove any setup animation, but apply final values
+            if let setupAnim = maskLayer?.animation(forKey: "setupFade") as? CABasicAnimation, let finalColors = setupAnim.toValue as? [CGColor] {
+                maskLayer?.colors = finalColors
+            }
+            maskLayer?.removeAnimation(forKey: "setupFade")
+
+            // Generate animation if needed
+            if let previousAnimation = fader?.anim {
+                gradientAnimation = previousAnimation
             } else {
-                // No animation needed
                 gradientAnimation = nil
             }
+
+            // Apply fade animation
+            maskLayer?.add(gradientAnimation!, forKey: "gradient")
+        } else {
+            // No animation needed
+            fader = nil
+        }
         #else
-            gradientAnimation = nil;
+        fader = nil;
         #endif
-        
+
         scrollCompletionBlock = { [weak self] (finished: Bool) -> () in
-            guard finished else {
-                // Do not continue into the next loop
-                return
-            }
-            
             guard (self != nil) else {
                 return
             }
-            
+
             // Call returned home function
-            self!.labelReturnedToHome(finished: true)
-            
+            self!.labelReturnedToHome(true)
+
             // Check to ensure that:
-            // 1) We don't double fire if an animation already exists
-            // 2) The instance is still attached to a window - this completion block is called for
+
+            // 1) The instance is still attached to a window - this completion block is called for
             //    many reasons, including if the animation is removed due to the view being removed
             //    from the UIWindow (typically when the view controller is no longer the "top" view)
             guard self!.window != nil else {
                 return
             }
-            
+            // 2) We don't double fire if an animation already exists
             guard self!.sublabel.layer.animation(forKey: "position") == nil else {
                 return
             }
-            
+            // 3) We don't not start automatically if the animation was unexpectedly interrupted
+            guard finished else {
+                // Do not continue into the next loop
+                return
+            }
+            // 4) A completion block still exists for the NEXT loop. A notable case here is if
+            // returnLabelToHome() was called during a subclass's labelReturnToHome() function
+            guard (self!.scrollCompletionBlock != nil) else {
+                return
+            }
+
             // Begin again, if conditions met
             if (self!.labelShouldScroll() && !self!.tapToScroll && !self!.holdScrolling) {
                 // Perform completion callback
-                self!.scroll(interval, delay: delay, scroller: scroller, fader: gradientAnimation)
-            }
-        }
-        
-        // Call scroller
-        let scrolls = scroller.generate(interval, delay: delay)
-        
-        // Perform all animations in scrolls
-        for (index, scroll) in scrolls.enumerated() {
-            let layer = scroll.layer
-            let anim = scroll.anim
-            
-            // Add callback to single animation
-            if index == 0 {
-                anim.setValue(true, forKey: MarqueeKeys.completionClosure.rawValue)
-                anim.delegate = self
-            }
-            
-            // Add animation
-            layer.add(anim, forKey: "position")
-        }
-        
+                self!.scroll(scroller, fader: fader)
+            }
+        }
+
+        // Perform scroll animation
+        scroller.anim.setValue(true, forKey: MarqueeKeys.CompletionClosure.rawValue)
+        scroller.anim.delegate = self
+        if type == .left || type == .right {
+            // Make it stay at away permanently
+            scroller.anim.isRemovedOnCompletion = false
+            scroller.anim.fillMode = .forwards
+        }
+        sublabel.layer.add(scroller.anim, forKey: "position")
+
         CATransaction.commit()
     }
-    
-    private func scrollAway(_ interval: CGFloat, delay: CGFloat = 0.0) {
+
+    private func generateScrollAnimation(_ sequence: [MarqueeStep]) -> MLAnimation {
         // Create scroller, which defines the animation to perform
         let homeOrigin = homeLabelFrame.origin
-        let awayOrigin = offset(homeLabelFrame.origin, dx: awayOffset)
-        let scroller = Scroller(generator: { [unowned self] (interval: CGFloat, delay: CGFloat) -> [(layer: CALayer, anim: CAKeyframeAnimation)] in
-            // Create animation for position
-            let values: [NSValue] = [
-                NSValue(cgPoint: homeOrigin), // Start at home
-                NSValue(cgPoint: homeOrigin), // Stay at home for delay
-                NSValue(cgPoint: awayOrigin), // Move to away
-                NSValue(cgPoint: awayOrigin), // Stay at away for delay
-                NSValue(cgPoint: homeOrigin)  // Move back to home
-            ]
-            
-            let layer = self.sublabel.layer
-            let anim = self.keyFrameAnimation(forProperty: "position", values: values, interval: Float(interval), delay: Float(delay))
-            
-            return [(layer: layer, anim: anim)]
-            })
-        
-        // Scroll
-        scroll(interval, delay: delay, scroller: scroller, fader: nil)
-    }
-    
-    
-    private func scrollcontinuous(_ interval: CGFloat, delay: CGFloat) {
-        // Create scroller, which defines the animation to perform
-        let homeOrigin = homeLabelFrame.origin
-        let awayOrigin = offset(homeLabelFrame.origin, dx: awayOffset)
-        let scroller = Scroller(generator: { [unowned self] (interval: CGFloat, delay: CGFloat) -> [(layer: CALayer, anim: CAKeyframeAnimation)] in
-            // Create animation for position
-            let values: [NSValue] = [
-                NSValue(cgPoint: homeOrigin), // Start at home
-                NSValue(cgPoint: homeOrigin), // Stay at home for delay
-                NSValue(cgPoint: awayOrigin)  // Move to away
-            ]
-            
-            // Generate animation
-            let layer = self.sublabel.layer
-            let anim = self.keyFrameAnimation(forProperty: "position", values: values, interval: Float(interval), delay: Float(delay))
-            
-            return [(layer: layer, anim: anim)]
-            })
-        
-        // Scroll
-        scroll(interval, delay: delay, scroller: scroller, fader: nil)
-    }
-    
-    private func applyGradientMask(_ fadeLength: CGFloat, animated: Bool) {
+        let awayOrigin = offsetCGPoint(homeLabelFrame.origin, offset: awayOffset)
+
+        let scrollSteps = sequence.filter({ $0 is ScrollStep }) as! [ScrollStep]
+        let totalDuration = scrollSteps.reduce(0.0) { $0 + $1.timeStep }
+
+        // Build scroll data
+        var totalTime: CGFloat = 0.0
+        var scrollKeyTimes = [NSNumber]()
+        var scrollKeyValues = [NSValue]()
+        var scrollTimingFunctions = [CAMediaTimingFunction]()
+
+        for (offset, step) in scrollSteps.enumerated() {
+            // Scroll Times
+            totalTime += step.timeStep
+            scrollKeyTimes.append(NSNumber(value:Float(totalTime/totalDuration)))
+
+            // Scroll Values
+            let scrollPosition: CGPoint
+            switch step.position {
+            case .home:
+                scrollPosition = homeOrigin
+            case .away:
+                scrollPosition = awayOrigin
+            case .partial(let frac):
+                scrollPosition = offsetCGPoint(homeOrigin, offset: awayOffset*frac)
+            }
+            scrollKeyValues.append(NSValue(cgPoint:scrollPosition))
+
+            // Scroll Timing Functions
+            // Only need n-1 timing functions, so discard the first value as it's unused
+            if offset == 0 { continue }
+            scrollTimingFunctions.append(timingFunctionForAnimationCurve(step.timingFunction))
+        }
+
+        // Create animation
+        let animation = CAKeyframeAnimation(keyPath: "position")
+        // Set values
+        animation.keyTimes = scrollKeyTimes
+        animation.values = scrollKeyValues
+        animation.timingFunctions = scrollTimingFunctions
+
+        return (anim: animation, duration: totalDuration)
+    }
+
+    private func generateGradientAnimation(_ sequence: [MarqueeStep], totalDuration: CGFloat) -> MLAnimation {
+        // Setup
+        var totalTime: CGFloat = 0.0
+        var stepTime: CGFloat = 0.0
+        var fadeKeyValues = [[CGColor]]()
+        var fadeKeyTimes = [NSNumber]()
+        var fadeTimingFunctions = [CAMediaTimingFunction]()
+        let transp = UIColor.clear.cgColor
+        let opaque = UIColor.black.cgColor
+
+        // Filter to get only scroll steps and valid precedent/subsequent fade steps
+        let fadeSteps = sequence.enumerated().filter { (arg: (offset: Int, element: MarqueeStep)) -> Bool in
+            let (offset, element) = arg
+
+            // Include all Scroll Steps
+            if element is ScrollStep { return true }
+
+            // Include all Fade Steps that have a directly preceding or subsequent Scroll Step
+            // Exception: Fade Step cannot be first step
+            if offset == 0 { return false }
+
+            // Subsequent step if 1) positive/zero time step and 2) follows a Scroll Step
+            let subsequent = element.timeStep >= 0 && (sequence[max(0, offset - 1)] is ScrollStep)
+            // Precedent step if 1) negative time step and 2) precedes a Scroll Step
+            let precedent = element.timeStep < 0 && (sequence[min(sequence.count - 1, offset + 1)] is ScrollStep)
+
+            return (precedent || subsequent)
+        }
+
+        for (offset, step) in fadeSteps {
+            // Fade times
+            if (step is ScrollStep) {
+                totalTime += step.timeStep
+                stepTime = totalTime
+            } else {
+                if step.timeStep >= 0 {
+                    // Is a Subsequent
+                    stepTime = totalTime + step.timeStep
+                } else {
+                    // Is a Precedent, grab next step
+                    stepTime = totalTime + fadeSteps[offset + 1].element.timeStep + step.timeStep
+                }
+            }
+            fadeKeyTimes.append(NSNumber(value:Float(stepTime/totalDuration)))
+
+            // Fade Values
+            let values: [CGColor]
+            let leading = step.edgeFades.contains(.leading) ? transp : opaque
+            let trailing = step.edgeFades.contains(.trailing) ? transp : opaque
+            switch type {
+            case .leftRight, .left, .continuous:
+                values = [leading, opaque, opaque, trailing]
+            case .rightLeft, .right, .continuousReverse:
+                values = [trailing, opaque, opaque, leading]
+            }
+            fadeKeyValues.append(values)
+
+            // Fade Timing Function
+            // Only need n-1 timing functions, so discard the first value as it's unused
+            if offset == 0 { continue }
+            fadeTimingFunctions.append(timingFunctionForAnimationCurve(step.timingFunction))
+        }
+
+        // Create new animation
+        let animation = CAKeyframeAnimation(keyPath: "colors")
+
+        animation.values = fadeKeyValues
+        animation.keyTimes = fadeKeyTimes
+        animation.timingFunctions = fadeTimingFunctions
+
+        return (anim: animation, duration: max(totalTime,totalDuration))
+    }
+
+    private func applyGradientMask(_ fadeLength: CGFloat, animated: Bool, firstStep: MarqueeStep? = nil) {
         // Remove any in-flight animations
         maskLayer?.removeAllAnimations()
-        
+
         // Check for zero-length fade
-        if fadeLength <= 0.0 {
+        if (fadeLength <= 0.0) {
             removeGradientMask()
             return
         }
-        
+
         // Configure gradient mask without implicit animations
         CATransaction.begin()
         CATransaction.setDisableActions(true)
-        
+
         // Determine if gradient mask needs to be created
         let gradientMask: CAGradientLayer
         if let currentMask = self.maskLayer {

        shutdownLabel()
        // Restart scrolling if appropriate
        if labelShouldScroll() && !tapToScroll && !holdScrolling {
            beginScroll()
        }
    }
    
    /**
     Resets the label text, recalculating the scroll animation.
     
     The text is immediately returned to the home position, and the scroll animation positions are cleared. Scrolling will not resume automatically after
     a call to this method. To re-initiate scrolling, use either a call to `restartLabel` or make a change to a UILabel property such as text, bounds/frame,
     font, font size, etc.
     
     - SeeAlso: restartLabel
     */
    public func resetLabel() {
        returnLabelToHome()
        homeLabelFrame = CGRect.null
        awayOffset = 0.0
    }
    
    /**
     Immediately resets the label to the home position, cancelling any in-flight scroll animation.
     
     The text is immediately returned to the home position. Scrolling will not resume automatically after a call to this method.
     To re-initiate scrolling use a call to `restartLabel` or `triggerScrollStart`, or make a change to a UILabel property such as text, bounds/frame,
     font, font size, etc.
     
     - SeeAlso: restartLabel
     - SeeAlso: triggerScrollStart
     */

        maskLayer?.beginTime = 0.0
        maskLayer?.beginTime = maskLayer!.convertTime(CACurrentMediaTime(), from:nil) - gradientPauseTime!
    }

    @objc public func labelWasTapped(_ recognizer: UIGestureRecognizer) {
        if labelShouldScroll() && !awayFromHome {
            // Set shouldBeginScroll to true to begin single scroll due to tap
            updateAndScroll(overrideHold: true)
        }
    }

    /**
     Called when the label animation is about to begin.

     The default implementation of this method does nothing. Subclasses may override this method in order to perform any custom actions just as
     the label animation begins. This is only called in the event that the conditions for scrolling to begin are met.
     */
    open func labelWillBeginScroll() {
        // Default implementation does nothing - override to customize
        return
    }

    /**
     Called when the label animation has finished, and the label is at the home position.

     The default implementation of this method does nothing. Subclasses may override this method in order to perform any custom actions jas as
     the label animation completes, and before the next animation would begin (assuming the scroll conditions are met).

     - Parameter finished: A Boolean that indicates whether or not the scroll animation actually finished before the completion handler was called.

     - Warning: This method will be called, and the `finished` parameter will be `NO`, when any property changes are made that would cause the label
     scrolling to be automatically reset. This includes changes to label text and font/font size changes.
     */
    open func labelReturnedToHome(_ finished: Bool) {
        // Default implementation does nothing - override to customize
        return
    }

    //
    // MARK: - Modified UILabel Functions/Getters/Setters
    //

    #if os(iOS)
    override open func forBaselineLayout() -> UIView {
        // Use subLabel view for handling baseline layouts
        return sublabel
    }

    override open var forLastBaselineLayout: UIView {
        // Use subLabel view for handling baseline layouts
        return sublabel
    }
    #endif

    override open var text: String? {
        get {
            return sublabel.text
        }

        set {
            if sublabel.text == newValue {
                return
            }
            sublabel.text = newValue
            updateAndScroll()
            super.text = text
        }
    }

    override open var attributedText: NSAttributedString? {
        get {
            return sublabel.attributedText
        }

        set {
            if sublabel.attributedText == newValue {
                return
            }
            sublabel.attributedText = newValue
            updateAndScroll()
            super.attributedText = attributedText
        }
    }

    override open var font: UIFont! {
        get {
            return sublabel.font
        }

        set {
            if sublabel.font == newValue {
                return
            }
            sublabel.font = newValue
            super.font = newValue

            updateAndScroll()
        }
    }

    override open var textColor: UIColor! {
        get {
            return sublabel.textColor
        }

        set {
            sublabel.textColor = newValue
            super.textColor = newValue
        }
    }

    override open var backgroundColor: UIColor? {
        get {
            return sublabel.backgroundColor
        }

        set {
            sublabel.backgroundColor = newValue
            super.backgroundColor = newValue
        }
    }

    override open var shadowColor: UIColor? {
        get {
            return sublabel.shadowColor
        }

        set {
            sublabel.shadowColor = newValue
            super.shadowColor = newValue
        }
    }

    override open var shadowOffset: CGSize {
        get {
            return sublabel.shadowOffset
        }

        set {
            sublabel.shadowOffset = newValue
            super.shadowOffset = newValue
        }
    }

    override open var highlightedTextColor: UIColor? {
        get {
            return sublabel.highlightedTextColor
        }

        set {
            sublabel.highlightedTextColor = newValue
            super.highlightedTextColor = newValue
        }
    }

    override open var isHighlighted: Bool {
        get {
            return sublabel.isHighlighted
        }

        set {
            sublabel.isHighlighted = newValue
            super.isHighlighted = newValue
        }
    }

    override open var isEnabled: Bool {
        get {
            return sublabel.isEnabled
        }

        set {
            sublabel.isEnabled = newValue
            super.isEnabled = newValue
        }
    }

    override open var numberOfLines: Int {
        get {
            return super.numberOfLines
        }

        set {
            // By the nature of MarqueeLabel, this is 1
            super.numberOfLines = 1
        }
    }

    override open var adjustsFontSizeToFitWidth: Bool {
        get {
            return super.adjustsFontSizeToFitWidth
        }

        set {
            // By the nature of MarqueeLabel, this is false
            super.adjustsFontSizeToFitWidth = false
        }
    }

    override open var minimumScaleFactor: CGFloat {
        get {
            return super.minimumScaleFactor
        }

        set {
            super.minimumScaleFactor = 0.0
        }
    }

    override open var baselineAdjustment: UIBaselineAdjustment {
        get {
            return sublabel.baselineAdjustment
        }

        set {
            sublabel.baselineAdjustment = newValue
            super.baselineAdjustment = newValue
        }
    }

    override open var intrinsicContentSize: CGSize {
        var content = sublabel.intrinsicContentSize
        content.width += leadingBuffer
        return content
    }

    override open var tintColor: UIColor! {
        get {
            return sublabel.tintColor
        }

        set {
            sublabel.tintColor = newValue
            super.tintColor = newValue
        }
    }

    override open func tintColorDidChange() {
        super.tintColorDidChange()
        sublabel.tintColorDidChange()
    }

    override open var contentMode: UIView.ContentMode {
        get {
            return sublabel.contentMode
        }

        set {
            super.contentMode = contentMode
            sublabel.contentMode = newValue
        }
    }

    open override var isAccessibilityElement: Bool {
        didSet {
            sublabel.isAccessibilityElement = self.isAccessibilityElement
        }
    }

    //
    // MARK: - Support
    //

    fileprivate func offsetCGPoint(_ point: CGPoint, offset: CGFloat) -> CGPoint {
        return CGPoint(x: point.x + offset, y: point.y)
    }

    //
    // MARK: - Deinit
    //

    deinit {
        NotificationCenter.default.removeObserver(self)
    }

}


//
// MARK: - Support
//
public protocol MarqueeStep {
    var timeStep: CGFloat { get }
    var timingFunction: UIView.AnimationCurve { get }
    var edgeFades: EdgeFade { get }
}


/**
 `ScrollStep` types define the label position at a specified time delta since the last `ScrollStep` step, as well as
 the animation curve to that position and edge fade state at the position
 */
public struct ScrollStep: MarqueeStep {
    /**
     An enum that provides the possible positions defined by a ScrollStep
     - `home`: The starting, default position of the label
     - `away`: The calculated position that results in the entirety of the label scrolling past.
     - `partial(CGFloat)`: A fractional value, specified by the associated CGFloat value, between the `home` and `away` positions (must be between 0.0 and 1.0).

     The `away` position depends on the MarqueeLabel `type` value.
     - For `left`, `leftRight`, `right`, and `rightLeft` types, the `away` position means the trailing edge of the label
     is visible. For `leftRight` and `rightLeft` default types, the scroll animation reverses direction after reaching
     this point and returns to the `home` position.
     - For `continuous` and `continuousReverse` types, the `away` position is the location such that if the scroll is completed
     at this point (i.e. the animation is removed), there will be no visible change in the label appearance.
     */
    public enum Position {
        case home
        case away
        case partial(CGFloat)
    }

    /**
     The desired time between this step and the previous `ScrollStep` in a sequence.
     */
    public let timeStep: CGFloat

    /**
     The animation curve to utilize between the previous `ScrollStep` in a sequence and this step.

     - Note: The animation curve value for the first `ScrollStep` in a sequence has no effect.
     */
    public let timingFunction: UIView.AnimationCurve

    /**
     The position of the label for this scroll step.
     - SeeAlso: Position
     */
    public let position: Position

    /**
     The option set defining the edge fade state for this scroll step.

     Possible options include `.leading` and `.trailing`, corresponding to the leading edge of the label scrolling (i.e.
     the direction of scroll) and trailing edge of the label.
     */
    public let edgeFades: EdgeFade

    public init(timeStep: CGFloat, timingFunction: UIView.AnimationCurve = .linear, position: Position, edgeFades: EdgeFade) {
        self.timeStep = timeStep
        self.position = position
        self.edgeFades = edgeFades
        self.timingFunction = timingFunction
    }
}


/**
 `FadeStep` types allow additional edge fade state definitions, around the states defined by the `ScrollStep` steps of
 a sequence. `FadeStep` steps are defined by the time delta to the preceding or subsequent `ScrollStep` step and the timing
 function to their edge fade state.

 - Note: A `FadeStep` cannot be the first step in a sequence. A `FadeStep` defined as such will be ignored.
 */
public struct FadeStep: MarqueeStep {
    /**
     The desired time between this `FadeStep` and the preceding or subsequent `ScrollStep` in a sequence.

     `FadeSteps` with a negative `timeStep` value will be associated _only_ with an immediately-subsequent `ScrollStep` step
     in the sequence.

     `FadeSteps` with a positive `timeStep` value will be associated _only_ with an immediately-prior `ScrollStep` step in the
     sequence.

     - Note: A `FadeStep` with a `timeStep` value of 0.0 will have no effect, and is the same as defining the fade state with
     a `ScrollStep`.
     */
    public let timeStep: CGFloat

    /**
     The animation curve to utilize between the previous fade state in a sequence and this step.
     */
    public let timingFunction: UIView.AnimationCurve

    /**
     The option set defining the edge fade state for this fade step.

     Possible options include `.leading` and `.trailing`, corresponding to the leading edge of the label scrolling (i.e.
     the direction of scroll) and trailing edge of the label.

     As an Option Set type, both edge fade states may be defined using an array literal: `[.leading, .trailing]`.
     */
    public let edgeFades: EdgeFade

    public init(timeStep: CGFloat, timingFunction: UIView.AnimationCurve = .linear, edgeFades: EdgeFade) {
        self.timeStep = timeStep
        self.timingFunction = timingFunction
        self.edgeFades = edgeFades
    }
}

public struct EdgeFade : OptionSet {
    public let rawValue: Int
    public static let leading  = EdgeFade(rawValue: 1 << 0)
    public static let trailing = EdgeFade(rawValue: 1 << 1)

    public init(rawValue: Int) {
        self.rawValue = rawValue;
    }
}

// Define helpful typealiases
fileprivate typealias MLAnimationCompletionBlock = (_ finished: Bool) -> ()
fileprivate typealias MLAnimation = (anim: CAKeyframeAnimation, duration: CGFloat)

fileprivate class GradientSetupAnimation: CABasicAnimation {
}

fileprivate extension UIResponder {
    // Thanks to Phil M
    // http://stackoverflow.com/questions/1340434/get-to-uiviewcontroller-from-uiview-on-iphone

    func firstAvailableViewController() -> UIViewController? {
        // convenience function for casting and to "mask" the recursive function
        return self.traverseResponderChainForFirstViewController()
    }

    func traverseResponderChainForFirstViewController() -> UIViewController? {
        if let nextResponder = self.next {
            if nextResponder is UIViewController {
                return nextResponder as? UIViewController
            } else if nextResponder is UIView {
                return nextResponder.traverseResponderChainForFirstViewController()
            } else {
                return nil
