         }
     }
 
+    // MARK: - Utilities
+
+    private func timeRequiredToMove(from from: CGFloat, to: CGFloat, withVelocity velocity: CGFloat) -> NSTimeInterval {
+        let distanceToMove = to - from
+        let requiredTime = NSTimeInterval(abs(distanceToMove / velocity))
+        return requiredTime
+    }
+
 }
-

    )
}

class ArchiveViewController: UITableViewController {

    // MARK: - UIViewController methods

    override func viewDidLoad() {
        super.viewDidLoad()
        applyCurrentStyle()
    }

    // MARK: - UITableViewDelegate methods

        archiveCell.apply(style: currentStyle.tableViewStyle.cellStyle)
    }

    // MARK: - Gesture recognizer interaction

    private func setupPanGestureRecognizer() {
        let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(panRecognizerDidChange(_:)))
        panRecognizer.maximumNumberOfTouches = 2
        panRecognizer.minimumNumberOfTouches = 2
        panRecognizer.delegate = self
        tableView.addGestureRecognizer(panRecognizer)
    }

    func panRecognizerDidChange(panRecognizer: UIPanGestureRecognizer) {
        switch panRecognizer.state {
        case .Began:
            beginInteractiveStyleTransition(withPanRecognizer: panRecognizer)
        case .Changed:
            adjustMaskViewPosition(basedOn: panRecognizer)
        case .Ended, .Failed:
            endInteractiveStyleTransition(withPanRecognizer: panRecognizer)
        default: break
        }
    }

    func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
        guard let panRecognizer = gestureRecognizer as? UIPanGestureRecognizer else {
            return true
        }

        // A pan gesture recognizer recognizes pans in all directions, but we only
        // want the recognizer to begin if the user pans downwards.
        let translation = panRecognizer.translationInView(tableView.window)
        let isMovingDownwards = translation.y > 0.0
        return isMovingDownwards
    }

    // MARK: - Interactive style transition

    /// During the interactive transition, this property contains a
    /// snapshot of the view when it was styled with the previous style
    /// (i.e. the style we're transitioning _from_).
    /// As the transition progresses, less and less of the snapshot view
    /// will be visible, revealing more of the real view which is styled
    /// with the new style.
    private var previousStyleViewSnapshot: UIView?

    /// During the interactive transition, this property contains the view
    /// used to mask the contents of `previousStyleViewSnapshot`.
    /// When the user pans, the frame of `snapshotMaskView` is adjusted to
    /// reflect the current translation of the pan recognizer.
    private var snapshotMaskView: UIView?

    private func beginInteractiveStyleTransition(withPanRecognizer panRecognizer: UIPanGestureRecognizer) {
        guard let window = tableView.window else {
            return
        }

        // We snapshot the window before applying the new style, and make sure
        // it's positioned on top of all the other content.
        previousStyleViewSnapshot = window.snapshotViewAfterScreenUpdates(false)
        window.addSubview(previousStyleViewSnapshot!)
        window.bringSubviewToFront(previousStyleViewSnapshot!)

        // When we have the snapshot we create a new mask view that's used to
        // control how much of the previous view we display as the transition
        // progresses.
        snapshotMaskView = UIView(frame: window.bounds)
        snapshotMaskView?.backgroundColor = .blackColor()
        previousStyleViewSnapshot?.maskView = snapshotMaskView

        // Now we're free to apply the new style. This won't be visible until
        // the user pans more since the snapshot is displayed on top of the
        // actual content.
        useDarkMode = !useDarkMode

        // Finally we make our first adjustment to the mask view's position
        // based on the values of the pan recognizer.
        adjustMaskViewPosition(basedOn: panRecognizer)
    }

    private func adjustMaskViewPosition(basedOn panRecognizer: UIPanGestureRecognizer) {
        guard let window = tableView.window else {
            return
        }

        let verticalTranslation = panRecognizer.translationInView(window).y
        if verticalTranslation < 0.0 {
            // We wan't to prevent the user from moving the mask view out the
            // top of the window, since doing so would show the new style at
            // the bottom of the window instead.
            // By resetting the translation we make sure there's no visual 
            // delay between when the user tries to pan upwards and when they 
            // start panning downwards again.
            panRecognizer.setTranslation(.zero, inView: window)
            snapshotMaskView?.frame.origin.y = 0.0
        } else {
            // Simply move the mask view as much as the user has panned.
            // Note that if we had used the _location_ of the pan recognizer
            // instead of the translation, the top of the mask view would 
            // follow the fingers exactly. Using the translation results in a 
            // better user experience since the location of the mask view is 
            // instead relative to the distance moved.
            snapshotMaskView?.frame.origin.y = verticalTranslation
        }
    }

    private func endInteractiveStyleTransition(withPanRecognizer panRecognizer: UIPanGestureRecognizer) {
        guard let window = tableView.window else {
            return
        }

        let velocity = panRecognizer.velocityInView(window)
        let translation = panRecognizer.translationInView(window)

        let isMovingDownwards = velocity.y > 0.0
        let hasPassedThreshold = translation.y > window.bounds.midY

        // We support both completing the transition and cancelling the transition.
        // The transition to the new style should be completed if the user is panning
        // downwards or if they've panned enough that more than half of the new view
        // is already shown.
        let shouldCompleteTransition = isMovingDownwards || hasPassedThreshold

        if shouldCompleteTransition {
            completeInteractiveStyleTransition(withVelocity: velocity)
        } else {
            cancelInteractiveStyleTransition(withVelocity: velocity)
        }
    }

    private func cancelInteractiveStyleTransition(withVelocity velocity: CGPoint) {
        guard let snapshotMaskView = snapshotMaskView else {
            return
        }

        let duration = timeRequiredToMove(from: snapshotMaskView.frame.minY, to: 0.0, withVelocity: velocity.y)

        // When cancelling the transition we simply move the mask view to it's original
        // location (which means that the entire previous style snapshot is shown), then
        // reset the style to the previous style and remove the snapshot.
        UIView.animateWithDuration(duration, animations: {
            snapshotMaskView.frame.origin.y = 0.0
        }, completion: { _ in
            self.useDarkMode = !self.useDarkMode
            self.cleanupAfterInteractiveStyleTransition()
        })
    }

    private func completeInteractiveStyleTransition(withVelocity velocity: CGPoint) {
        guard let
            window = tableView.window,
            snapshotMaskView = snapshotMaskView else {
                return
        }

        let targetLocation = window.bounds.maxY
        let duration = timeRequiredToMove(from: snapshotMaskView.frame.minY, to: targetLocation, withVelocity: velocity.y)

        // When completing the transition we slide the mask view down to the bottom of
        // the window and then remove the snapshot. The further down the mask view is, 
        // the more of the underlying view is visible. When the mask view reaches the
        // bottom of the window, the entire underlying view will be visible so removing
        // the snapshot will have no visual effect.
        UIView.animateWithDuration(duration, animations: {
            snapshotMaskView.frame.origin.y = targetLocation
        }, completion: { _ in
            self.cleanupAfterInteractiveStyleTransition()
        })
    }

    private func cleanupAfterInteractiveStyleTransition() {
        self.previousStyleViewSnapshot?.removeFromSuperview()
        self.previousStyleViewSnapshot = nil
        self.snapshotMaskView = nil
    }

    // MARK: - Applying styles

    private var currentStyle = ViewControllerStyle.Light {
        didSet { applyCurrentStyle() }
    }

    private var useDarkMode = false {
        didSet { currentStyle = useDarkMode ? .Dark : .Light }
    }

    private func applyCurrentStyle() {
