//
//  DeckPresentationController.swift
//  DeckTransition
//
//  Created by Harshil Shah on 15/10/16.
//  Copyright © 2016 Harshil Shah. All rights reserved.
//

import UIKit

final class DeckPresentationController: UIPresentationController, UIGestureRecognizerDelegate, DeckSnapshotUpdater {
    
    // MARK: - Internal variables
    
    /// The presentation controller holds a strong reference to the
    /// transitioning delegate because `UIViewController.transitioningDelegate`
    /// is a weak property, and thus the `DeckTransitioningDelegate` would be
    /// unallocated right after the presentation animation.
    ///
    /// Since the transitioningDelegate only vends the presentation controller
    /// object and does not hold a reference to it, there is no issue of a
    /// circular dependency here.
    var transitioningDelegate: DeckTransitioningDelegate?
    
    // MARK: - Private variables
    
    private var isSwipeToDismissGestureEnabled = true
    private var pan: UIPanGestureRecognizer?
    private var scrollViewUpdater: ScrollViewUpdater?
    
    private let backgroundView = UIView()
    private let roundedViewForPresentingView = RoundedView()
    private let roundedViewForPresentedView = RoundedView()
    
    private let snapshotViewContainer = UIView()
    private var snapshotView: UIView?
    
    private var snapshotViewTopConstraint: NSLayoutConstraint?
    private var snapshotViewWidthConstraint: NSLayoutConstraint?
    private var snapshotViewAspectRatioConstraint: NSLayoutConstraint?
    
    private var presentedViewFrameObserver: NSKeyValueObservation?
    private var presentedViewTransformObserver: NSKeyValueObservation?
    
    private var presentAnimation: (() -> ())? = nil
    private var presentCompletion: ((Bool) -> ())? = nil
    private var dismissAnimation: (() -> ())? = nil
    private var dismissCompletion: ((Bool) -> ())? = nil
	
    // MARK: - Initializers
    
    convenience init(presentedViewController: UIViewController,
                     presenting presentingViewController: UIViewController?,
                     isSwipeToDismissGestureEnabled: Bool,
                     presentAnimation: (() -> ())? = nil,
                     presentCompletion: ((Bool) ->())? = nil,
                     dismissAnimation: (() -> ())? = nil,
                     dismissCompletion: ((Bool) -> ())? = nil) {
        self.init(presentedViewController: presentedViewController,
                  presenting: presentingViewController)
        
        self.isSwipeToDismissGestureEnabled = isSwipeToDismissGestureEnabled
        self.presentAnimation = presentAnimation
        self.presentCompletion = presentCompletion
        self.dismissAnimation = dismissAnimation
        self.dismissCompletion = dismissCompletion
      
        #if swift(>=4.2)
        NotificationCenter.default.addObserver(self, selector: #selector(updateForStatusBar), name: UIApplication.didChangeStatusBarFrameNotification, object: nil)
        #else
        NotificationCenter.default.addObserver(self, selector: #selector(updateForStatusBar), name: .UIApplicationDidChangeStatusBarFrame, object: nil)
        #endif
    }

    // MARK: - Public methods
    
    public func requestPresentedViewSnapshotUpdate() {
        updateSnapshotView()
    }
    
    // MARK: - Sizing
    
    private var statusBarHeight: CGFloat {
        return UIApplication.shared.statusBarFrame.height
    }
    
    private var scaleForPresentingView: CGFloat {
        guard let containerView = containerView else {
            return 0
        }
        
        return 1 - (ManualLayout.presentingViewTopInset * 2 / containerView.frame.height)
    }
	
    override var frameOfPresentedViewInContainerView: CGRect {
        guard let containerView = containerView else {
            return .zero
        }
        
        let yOffset = ManualLayout.presentingViewTopInset + Constants.insetForPresentedView
        
        return CGRect(x: 0,
                      y: yOffset,
                      width: containerView.bounds.width,
                      height: containerView.bounds.height - yOffset)
    }
	
	// MARK: - Presentation
    
    override func presentationTransitionWillBegin() {
        guard let containerView = containerView, let window = containerView.window else {
            return
        }
        
        /// A CGRect to be used as a proxy for the frame of the presentingView
        ///
        /// The actual frame isn't used directly because in the case of the
        /// double height status bar on non-X iPhones, the containerView has a
        /// reduced height
        let initialFrame: CGRect = {
            if presentingViewController.isPresentedWithDeck {
                return presentingViewController.view.frame
            } else {
                return containerView.bounds
            }
        }()
        
        /// The presented view's rounded view's frame is updated using KVO
        roundedViewForPresentedView.translatesAutoresizingMaskIntoConstraints = false
        containerView.addSubview(roundedViewForPresentedView)
        setupPresentedViewKVO()
        
        /// The snapshot view initially has the same frame as the presentingView
        containerView.insertSubview(snapshotViewContainer, belowSubview: presentedViewController.view)
        snapshotViewContainer.frame = initialFrame
        updateSnapshotView()
        
        /// The following transforms are performed on the snapshot view:
        /// 1. It's frame's origin is reset to 0. This is done because for
        ///    recursive Deck modals, the reference frame will not have its
        ///    origin at `.zero`
        /// 2. It is translated down by `ManualLayout.presentingViewTopInset`
        ///    points This is the desired inset from the top of the
        ///    containerView
        /// 3. It is scaled down by `scaleForPresentingView` along both axes,
        ///    such that it's top edge is at the same position. In order to do
        ///    this, we translate it up by half it's height, perform the
        ///    scaling, and then translate it back down by the same amount
        ///
        /// On versions of iOS before 11, applying a transform to the parent
        /// view modifies the frame of subviews immediately to the model value,
        /// so this transform is applied directly to the snapshot view and not
        /// the snapshot container view
        ///
        /// Note: For some reason, this behaviour only happens in the
        /// presentation phase; constraints animate as expected on iOS <11 in
        /// the dismissal 🤷🏽‍♂️
        let transformForSnapshotView = CGAffineTransform.identity
            .translatedBy(x: 0, y: -snapshotViewContainer.frame.origin.y)
            .translatedBy(x: 0, y: ManualLayout.presentingViewTopInset)
            .translatedBy(x: 0, y: -snapshotViewContainer.frame.height / 2)
            .scaledBy(x: scaleForPresentingView, y: scaleForPresentingView)
            .translatedBy(x: 0, y: snapshotViewContainer.frame.height / 2)
        
        /// For a recursive modal, the `presentingView` already has rounded
        /// corners so the animation must respect that
        roundedViewForPresentingView.backgroundColor = UIColor.black.withAlphaComponent(0)
        roundedViewForPresentingView.cornerRadius = presentingViewController.isPresentedWithDeck ? Constants.cornerRadius : 0
        containerView.insertSubview(roundedViewForPresentingView, aboveSubview: snapshotViewContainer)
        roundedViewForPresentingView.frame = initialFrame
        
        /// The background view is used to cover up the `presentedView`
        backgroundView.backgroundColor = .black
        backgroundView.translatesAutoresizingMaskIntoConstraints = false
        containerView.insertSubview(backgroundView, belowSubview: snapshotViewContainer)
        
        NSLayoutConstraint.activate([
            backgroundView.topAnchor.constraint(equalTo: window.topAnchor),
            backgroundView.leftAnchor.constraint(equalTo: window.leftAnchor),
            backgroundView.rightAnchor.constraint(equalTo: window.rightAnchor),
            backgroundView.bottomAnchor.constraint(equalTo: window.bottomAnchor)
        ])
        
        /// A snapshot view is used to represent the hierarchy of cards in the
        /// case of recursive presentation
        var rootSnapshotView: UIView?
        var rootSnapshotRoundedView: RoundedView?
        
        if presentingViewController.isPresentedWithDeck {
            guard let rootController = presentingViewController.presentingViewController,
                  let snapshotView = rootController.view.snapshotView(afterScreenUpdates: false)
            else {
                return
            }
            
            containerView.insertSubview(snapshotView, aboveSubview: backgroundView)
            snapshotView.frame = initialFrame
            snapshotView.transform = transformForSnapshotView
            snapshotView.alpha = Constants.alphaForPresentingView
            rootSnapshotView = snapshotView
            
            let snapshotRoundedView = RoundedView()
            snapshotRoundedView.cornerRadius = Constants.cornerRadius
            containerView.insertSubview(snapshotRoundedView, aboveSubview: snapshotView)
            snapshotRoundedView.frame = initialFrame
            snapshotRoundedView.transform = transformForSnapshotView
            rootSnapshotRoundedView = snapshotRoundedView
        }
        
        presentedViewController.transitionCoordinator?.animate(
            alongsideTransition: { [weak self] context in
                guard let `self` = self else {
                    return
                }
                
                self.presentAnimation?()
                self.snapshotView?.transform = transformForSnapshotView
                self.roundedViewForPresentingView.cornerRadius = Constants.cornerRadius
                self.roundedViewForPresentingView.transform = transformForSnapshotView
                self.roundedViewForPresentingView.backgroundColor = UIColor.black.withAlphaComponent(1 - Constants.alphaForPresentingView)
            }, completion: { _ in
                self.snapshotView?.transform = .identity
                rootSnapshotView?.removeFromSuperview()
                rootSnapshotRoundedView?.removeFromSuperview()
            }
        )
    }
    
    /// Method to ensure the layout is as required at the end of the
    /// presentation. This is required in case the modal is presented without
    /// animation.
    ///
    /// The various layout related functions performed by this method are:
    /// - Ensure that the view is in the same state as it would be after
    ///   animated presentation
    /// - Create and add the `presentingViewSnapshotView` to the view hierarchy
    /// - Add a black background view to present to complete cover the
    ///   `presentingViewController`'s view
    /// - Reset the `presentingViewController`'s view's `transform` so that
    ///   further layout updates (such as status bar update) do not break the
    ///   transform
    ///
    /// It also sets up the gesture recognizer to handle dismissal of the modal
    /// view controller by panning downwards
    override func presentationTransitionDidEnd(_ completed: Bool) {
        guard let containerView = containerView else {
            return
        }
        
        presentedViewController.view.frame = frameOfPresentedViewInContainerView
        
        snapshotViewContainer.transform = .identity
        snapshotViewContainer.translatesAutoresizingMaskIntoConstraints = false
        snapshotViewContainer.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).isActive = true
        updateSnapshotViewAspectRatio()
        
        roundedViewForPresentingView.transform = .identity
        roundedViewForPresentingView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            roundedViewForPresentingView.topAnchor.constraint(equalTo: snapshotViewContainer.topAnchor),
            roundedViewForPresentingView.leftAnchor.constraint(equalTo: snapshotViewContainer.leftAnchor),
            roundedViewForPresentingView.rightAnchor.constraint(equalTo: snapshotViewContainer.rightAnchor),
            roundedViewForPresentingView.bottomAnchor.constraint(equalTo: snapshotViewContainer.bottomAnchor)
        ])
        
        if isSwipeToDismissGestureEnabled {
            pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
            pan!.delegate = self
            pan!.maximumNumberOfTouches = 1
            pan!.cancelsTouchesInView = false
            presentedViewController.view.addGestureRecognizer(pan!)
        }

        presentCompletion?(completed)
    }
	
    // MARK: - Layout update methods
    
    /// This method updates the aspect ratio of the snapshot view
    ///
    /// The `snapshotView`'s aspect ratio needs to be updated here because even
    /// though it is updated with the `snapshotView` in `viewWillTransition:`,
    /// the transition is janky unless it's updated before, hence it's performed
    /// here as well, It's also an inexpensive method since constraints are
    /// modified only when a change is actually needed
    override func containerViewWillLayoutSubviews() {
        super.containerViewWillLayoutSubviews()
        
        guard let containerView = containerView else {
            return
        }
        
        updateSnapshotViewAspectRatio()
        #if swift(>=4.2)
        containerView.bringSubviewToFront(roundedViewForPresentedView)
        #else
        containerView.bringSubview(toFront: roundedViewForPresentedView)
        #endif
      
        if presentedViewController.view.isDescendant(of: containerView) {
            UIView.animate(withDuration: 0.1) { [weak self] in
                guard let `self` = self else {
                    return
                }
                
                self.presentedViewController.view.frame = self.frameOfPresentedViewInContainerView
            }
        }
    }
    
    /// Method to handle the modal setup's response to a change in
    /// orientation, size, etc.
    ///
    /// Everything else is handled by AutoLayout or `willLayoutSubviews`; the
    /// express purpose of this method is to update the snapshot view since that
    /// is a relatively expensive operation and only makes sense on orientation
    /// change
    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        
        coordinator.animate(
            alongsideTransition: nil,
            completion: { [weak self] _ in
                self?.updateSnapshotViewAspectRatio()
                self?.updateSnapshotView()
            }
        )
    }
	
    /// Method to handle updating the view when the status bar's height changes
    ///
    /// The `containerView`'s frame is always supposed to be the go 20 pixels
    /// or 1 normal status bar height under the status bar itself, even when the
    /// status bar is of double height, to retain consistency with the system's
    /// default behaviour
    ///
    /// The containerView is the only thing that received layout updates;
    /// AutoLayout and the snapshotView method handle the rest. Additionally,
    /// the mask for the `presentedViewController` is also reset
    @objc private func updateForStatusBar() {
        guard let containerView = containerView else {
            return
        }
        
        /// The `presentingViewController.view` often animated "before" the mask
        /// view that should fully cover it, so it's hidden before altering the
        /// view hierarchy, and then revealed after the animations are finished
        presentingViewController.view.alpha = 0
        
        let fullHeight = containerView.window!.frame.size.height
        
        let currentHeight = containerView.frame.height
        let newHeight = fullHeight - ManualLayout.containerViewTopInset
        
        UIView.animate(
            withDuration: 0.1,
            animations: {
                containerView.frame.origin.y -= newHeight - currentHeight
            }, completion: { [weak self] _ in
                self?.presentingViewController.view.alpha = 1
                containerView.frame = CGRect(x: 0, y: ManualLayout.containerViewTopInset, width: containerView.frame.width, height: newHeight)
                self?.updateSnapshotView()
            }
        )
    }
    
    // MARK: - Snapshot view update methods
    
    /// Method to update the snapshot view showing a representation of the
    /// `presentingViewController`'s view
    ///
    /// The method can only be fired when the snapshot view has been set up, and
    /// then only when the width of the container is updated
    ///
    /// It resets the aspect ratio constraint for the snapshot view first, and
    /// then generates a new snapshot of the `presentingViewController`'s view,
    /// and then replaces the existing snapshot with it
    private func updateSnapshotView() {
        guard let currentSnapshotView = presentingViewController.view.snapshotView(afterScreenUpdates: true) else {
            return
        }
        
        snapshotView?.removeFromSuperview()
        
        currentSnapshotView.translatesAutoresizingMaskIntoConstraints = false
        snapshotViewContainer.addSubview(currentSnapshotView)
        NSLayoutConstraint.activate([
            currentSnapshotView.topAnchor.constraint(equalTo: snapshotViewContainer.topAnchor),
            currentSnapshotView.leftAnchor.constraint(equalTo: snapshotViewContainer.leftAnchor),
            currentSnapshotView.rightAnchor.constraint(equalTo: snapshotViewContainer.rightAnchor),
            currentSnapshotView.bottomAnchor.constraint(equalTo: snapshotViewContainer.bottomAnchor)
        ])
        
        snapshotView = currentSnapshotView
	}
	
    /// Thie method updates the aspect ratio and the height of the snapshot view
    /// used to represent the presenting view controller.
    ///
    /// The aspect ratio is only updated when the width of the container changes
    /// i.e. when just the status bar moves, nothing happens
    private func updateSnapshotViewAspectRatio() {
        guard let containerView = containerView,
              snapshotViewContainer.translatesAutoresizingMaskIntoConstraints == false
        else {
            return
        }
        
        snapshotViewTopConstraint?.isActive = false
        snapshotViewWidthConstraint?.isActive = false
        snapshotViewAspectRatioConstraint?.isActive = false
        
        let snapshotReferenceSize = presentingViewController.view.frame.size
        
        let topInset = ManualLayout.presentingViewTopInset
        
        let aspectRatio = snapshotReferenceSize.width / snapshotReferenceSize.height
        
        roundedViewForPresentingView.cornerRadius = Constants.cornerRadius * scaleForPresentingView
        
        snapshotViewTopConstraint = snapshotViewContainer.topAnchor.constraint(equalTo: containerView.topAnchor, constant: topInset)
        snapshotViewWidthConstraint = snapshotViewContainer.widthAnchor.constraint(equalTo: containerView.widthAnchor, multiplier: scaleForPresentingView)
        snapshotViewAspectRatioConstraint = snapshotViewContainer.widthAnchor.constraint(equalTo: snapshotViewContainer.heightAnchor, multiplier: aspectRatio)
        
        snapshotViewTopConstraint?.isActive = true
        snapshotViewWidthConstraint?.isActive = true
        snapshotViewAspectRatioConstraint?.isActive = true
    }
    
    // MARK: - Presented view KVO + Rounded view update methods
    
    private func setupPresentedViewKVO() {
        presentedViewFrameObserver = presentedViewController.view.observe(\.frame, options: [.initial]) { [weak self] _, _ in
            self?.presentedViewWasUpdated()
        }
        
        presentedViewTransformObserver = presentedViewController.view.observe(\.transform, options: [.initial]) { [weak self] _, _ in
            self?.presentedViewWasUpdated()
        }
    }
    
    private func invalidatePresentedViewKVO() {
        presentedViewFrameObserver = nil
        presentedViewTransformObserver = nil
    }
    
    private func presentedViewWasUpdated() {
        let offset = presentedViewController.view.frame.origin.y
        roundedViewForPresentedView.frame = CGRect(x: 0, y: offset, width: containerView!.bounds.width, height: Constants.cornerRadius)
    }
    
    // MARK: - Dismissal
    
    /// Method to prepare the view hirarchy for the dismissal animation
    ///
    /// The stuff with snapshots and the black background should be invisible to
    /// the dismissal animation, so this method effectively removes them and
    /// restores the state of the `presentingViewController`'s view to the
    /// expected state at the end of the presenting animation
    override func dismissalTransitionWillBegin() {
        guard let containerView = containerView else {
            return
        }
        
        let initialFrame: CGRect = {
            if presentingViewController.isPresentedWithDeck {
                return presentingViewController.view.frame
            } else {
                return containerView.bounds
            }
        }()
        
        let initialTransform = CGAffineTransform.identity
            .translatedBy(x: 0, y: -initialFrame.origin.y)
            .translatedBy(x: 0, y: ManualLayout.presentingViewTopInset)
            .translatedBy(x: 0, y: -initialFrame.height / 2)
            .scaledBy(x: scaleForPresentingView, y: scaleForPresentingView)
            .translatedBy(x: 0, y: initialFrame.height / 2)
        
        roundedViewForPresentingView.translatesAutoresizingMaskIntoConstraints = true
        roundedViewForPresentingView.frame = initialFrame
        roundedViewForPresentingView.transform = initialTransform
        
        snapshotViewTopConstraint?.isActive = false
        snapshotViewWidthConstraint?.isActive = false
        snapshotViewAspectRatioConstraint?.isActive = false
        snapshotViewContainer.translatesAutoresizingMaskIntoConstraints = true
        snapshotViewContainer.frame = initialFrame
        snapshotViewContainer.transform = initialTransform
        
        let finalCornerRadius = presentingViewController.isPresentedWithDeck ? Constants.cornerRadius : 0
        let finalTransform: CGAffineTransform = .identity
        
        var rootSnapshotView: UIView?
        var rootSnapshotRoundedView: RoundedView?
        
        if presentingViewController.isPresentedWithDeck {
            guard let rootController = presentingViewController.presentingViewController,
                  let snapshotView = rootController.view.snapshotView(afterScreenUpdates: false)
            else {
                return
            }
            
            containerView.insertSubview(snapshotView, aboveSubview: backgroundView)
            snapshotView.frame = initialFrame
            snapshotView.transform = initialTransform
            rootSnapshotView = snapshotView
            
            let snapshotRoundedView = RoundedView()
            snapshotRoundedView.cornerRadius = Constants.cornerRadius
            snapshotRoundedView.backgroundColor = UIColor.black.withAlphaComponent(1 - Constants.alphaForPresentingView)
            containerView.insertSubview(snapshotRoundedView, aboveSubview: snapshotView)
            snapshotRoundedView.frame = initialFrame
            snapshotRoundedView.transform = initialTransform
            rootSnapshotRoundedView = snapshotRoundedView
        }
        
        presentedViewController.transitionCoordinator?.animate(
            alongsideTransition: { [weak self] context in
                guard let `self` = self else {
                    return
                }
                
                self.dismissAnimation?()
                self.snapshotViewContainer.transform = finalTransform
                self.roundedViewForPresentingView.transform = finalTransform
                self.roundedViewForPresentingView.cornerRadius = finalCornerRadius
                self.roundedViewForPresentingView.backgroundColor = .clear
            }, completion: { _ in
                rootSnapshotView?.removeFromSuperview()
                rootSnapshotRoundedView?.removeFromSuperview()
            }
        )
    }
    
    /// Method to ensure the layout is as required at the end of the dismissal.
    /// This is required in case the modal is dismissed without animation.
    override func dismissalTransitionDidEnd(_ completed: Bool) {
        guard let containerView = containerView else {
            return
        }
        
        backgroundView.removeFromSuperview()
        snapshotView?.removeFromSuperview()
        snapshotViewContainer.removeFromSuperview()
        roundedViewForPresentingView.removeFromSuperview()
        
        let offscreenFrame = CGRect(x: 0, y: containerView.bounds.height, width: containerView.bounds.width, height: containerView.bounds.height)
        presentedViewController.view.frame = offscreenFrame
        presentedViewController.view.transform = .identity
        
        invalidatePresentedViewKVO()
        
        dismissCompletion?(completed)
    }
    
    // MARK: - Gesture handling
    
    private func isSwipeToDismissAllowed() -> Bool {
        guard let updater = scrollViewUpdater else {
            return isSwipeToDismissGestureEnabled
        }
        
        return updater.isDismissEnabled
    }
    
    @objc private func handlePan(gestureRecognizer: UIPanGestureRecognizer) {
        guard gestureRecognizer.isEqual(pan), isSwipeToDismissGestureEnabled else {
            return
        }
        
        switch gestureRecognizer.state {
        
        case .began:
            let detector = ScrollViewDetector(withViewController: presentedViewController)
            if let scrollView = detector.scrollView {
                scrollViewUpdater = ScrollViewUpdater(
                    withRootView: presentedViewController.view,
                    scrollView: scrollView)
            }
            gestureRecognizer.setTranslation(CGPoint(x: 0, y: 0), in: containerView)
        
        case .changed:
            if isSwipeToDismissAllowed() {
                let translation = gestureRecognizer.translation(in: presentedView)
                updatePresentedViewForTranslation(inVerticalDirection: translation.y)
            } else {
                gestureRecognizer.setTranslation(.zero, in: presentedView)
            }
        
        case .ended:
            UIView.animate(
                withDuration: 0.25,
                animations: {
                    self.presentedView?.transform = .identity
                })
            scrollViewUpdater = nil

        default: break
        
        }
    }
    
    /// Method to update the modal view for a particular amount of translation
    /// by panning in the vertical direction.
    ///
    /// The translation of the modal view is proportional to the panning
    /// distance until the `elasticThreshold`, after which it increases at a
    /// slower rate, given by `elasticFactor`, to indicate that the
    /// `dismissThreshold` is nearing.
    ///
    /// Once the `dismissThreshold` is reached, the modal view controller is
    /// dismissed.
    ///
    /// - parameter translation: The translation of the user's pan gesture in
    ///   the container view in the vertical direction
    private func updatePresentedViewForTranslation(inVerticalDirection translation: CGFloat) {
        
        let elasticThreshold: CGFloat = 120
        let dismissThreshold: CGFloat = 240
        
        let translationFactor: CGFloat = 1/2
        
        /// Nothing happens if the pan gesture is performed from bottom
        /// to top i.e. if the translation is negative
        if translation >= 0 {
            let translationForModal: CGFloat = {
                if translation >= elasticThreshold {
                    let frictionLength = translation - elasticThreshold
                    let frictionTranslation = 30 * atan(frictionLength/120) + frictionLength/10
                    return frictionTranslation + (elasticThreshold * translationFactor)
                } else {
                    return translation * translationFactor
                }
            }()
            
            presentedView?.transform = CGAffineTransform(translationX: 0, y: translationForModal)
            
            if translation >= dismissThreshold {
                presentedViewController.dismiss(animated: true, completion: nil)
            }
        }
    }
    
    // MARK: - UIGestureRecognizerDelegate methods
    
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        guard gestureRecognizer.isEqual(pan) else {
            return false
        }
        
        return true
    }
    
}
