2
\$\begingroup\$

I have made an interactive transition controller in Swift in two different ways.

This interactive transition controller is the parent class and I have few child classes which adapt the parent for each situation where I need an interactive transition.

The first way, the parent doesn't use a delegate and forces the children to implement the methods by writing fatalError("Must Override") in the implementation of the methods in the parent.

Parent class:

private var presenting = false
weak var delegate: InteractiveControllerTransitionDelegate!

private var interactionController: UIPercentDrivenInteractiveTransition?

var edge: UIRectEdge!

weak var fromViewController: UIViewController! {
didSet {
    let screenEdgePanRecognizer = UIScreenEdgePanGestureRecognizer(target: self, action: "presentingViewController:")
    screenEdgePanRecognizer.edges = edge
    fromViewController.view.addGestureRecognizer(screenEdgePanRecognizer)
}
}

weak var toViewController: UIViewController! {
didSet {
    let screenEdgePanRecognizer = UIScreenEdgePanGestureRecognizer(target: self, action: "dismissingViewController:")
    screenEdgePanRecognizer.edges = oppositeEdgeForEdge(edge)
    toViewController.view.addGestureRecognizer(screenEdgePanRecognizer)
}
}

private let animationSynchronizationQueue = dispatch_queue_create("com.nico.app.animationsynchronization", DISPATCH_QUEUE_SERIAL)
private var animationSynchronizationQueueSuspended = false

func dispatchToMainFromSynchronizationQueue(block: dispatch_block_t) {
    dispatch_async(animationSynchronizationQueue) {
        dispatch_sync(dispatch_get_main_queue(), block)
    }
}


// MARK: - UIViewControllerAnimatedTransitioning protocol methods

func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
    return 0.3
}

func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
    if animationSynchronizationQueueSuspended {
        dispatch_resume(animationSynchronizationQueue)
        animationSynchronizationQueueSuspended = false
    }

    let containerView = transitionContext.containerView()
    let screens: (from: UIViewController, to: UIViewController) = (transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!, transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!)

    let parentViewController = presenting ? screens.from : screens.to
    let childViewController = presenting ? screens.to : screens.from

    let parentView = parentViewController.view
    let childView = childViewController.view

    // positionning the "to" viewController's view for the animation
    if presenting {
        offStageChildViewController(childView)
    }

    containerView.addSubview(parentView)    
    containerView.addSubview(childView)

    let duration = transitionDuration(transitionContext)

    UIView.animateWithDuration(duration, animations: {
        if self.presenting {
            self.onStageViewController(childView)
            self.offStageParentViewController(parentView)
        } else {
            self.onStageViewController(parentView)
            self.offStageChildViewController(childView)
        }}, completion: { finished in
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
    })
}

// MARK: gesture handlers

// presenting
func presentingViewController(pan: UIPanGestureRecognizer) {
    let percentage = getPercentage(pan)

    switch pan.state {

    case .Began:
        dispatch_suspend(animationSynchronizationQueue)
        animationSynchronizationQueueSuspended = true
        interactionController = UIPercentDrivenInteractiveTransition()
        presentViewController(pan)

    case .Changed:
        dispatchToMainFromSynchronizationQueue() {
            self.interactionController?.updateInteractiveTransition(percentage)
        }

    case .Ended:
        let velocity = pan.velocityInView(self.toViewController.view)
        dispatchToMainFromSynchronizationQueue() {
            if self.finishPresenting(velocity, percentage: percentage) {
                self.interactionController?.finishInteractiveTransition()
            } else {
                self.interactionController?.cancelInteractiveTransition()
            }
            self.interactionController = nil
        }


    default:
        break
    }
}

// dismissing
func dismissingViewController(pan: UIPanGestureRecognizer) {
    let percentage = getPercentage(pan)

    switch pan.state {

    case .Began:
        dispatch_suspend(animationSynchronizationQueue)
        animationSynchronizationQueueSuspended = true
        interactionController = UIPercentDrivenInteractiveTransition()
        dismissViewController(pan)

    case .Changed:
        dispatchToMainFromSynchronizationQueue() {
            self.interactionController?.updateInteractiveTransition(percentage)
        }

    case .Ended:
        let velocity = pan.velocityInView(self.toViewController.view)
        dispatchToMainFromSynchronizationQueue() {
            if self.finishDismissing(velocity, percentage: percentage) {
                self.doOnCompletion()
                self.interactionController?.finishInteractiveTransition()
            } else {
                self.doOnCancellation()
                self.interactionController?.cancelInteractiveTransition()
            }
            self.interactionController = nil
        }

    default:
        break
    }
}


// MARK: Affine Transforming methods

// MARK: Mandatory methods
func getPercentage(pan: UIPanGestureRecognizer) -> CGFloat {
    fatalError("Must Override")
}

func onStageViewController(view: UIView) {
    fatalError("Must Override")
}

func offStageParentViewController(view: UIView) {
    fatalError("Must Override")
}

func offStageChildViewController(view: UIView) {
    fatalError("Must Override")
}

func presentViewController(pan: UIPanGestureRecognizer) {
    fatalError("Must Override")
}

func dismissViewController(pan: UIPanGestureRecognizer) {
    fatalError("Must Override")
}

// Optionnal methods
func doOnCompletion() {

}

func doOnCancellation() {

}

func finishPresenting(velocity: CGPoint, percentage: CGFloat) -> Bool {
    return percentage >= 0.4 || abs(velocity.x) > 300
}

func finishDismissing(velocity: CGPoint, percentage: CGFloat) -> Bool {
    return percentage >= 0.4 || abs(velocity.x) > 300
}

Child:

override weak var toViewController: UIViewController! {
didSet {
    let panRecognizer = UIPanGestureRecognizer(target: self, action: "dismissingViewController:")
    toViewController.view.addGestureRecognizer(panRecognizer)
}
}

override func getPercentage(pan: UIPanGestureRecognizer) -> CGFloat {
    let translation = pan.translationInView(pan.view!)
    var percentage = translation.y / pan.view!.bounds.height

    if percentage < 0 {
        let velocity = pan.velocityInView(pan.view!)
        if velocity.y > 0 {
            pan.setTranslation(CGPointZero, inView: pan.view!)
            return 0
        } else {
            return 0
        }
    } else {
        return percentage
    }
}

override func onStageViewController(view: UIView) {
    view.transform = CGAffineTransformIdentity
}

override func offStageParentViewController(view: UIView) {
    view.transform = CGAffineTransformIdentity
}

override func offStageChildViewController(view: UIView) {
    view.transform = CGAffineTransformMakeTranslation(0, view.bounds.height)
}

override func presentViewController(pan: UIPanGestureRecognizer) {
    let location = pan.locationInView((fromViewController as! MainViewController).tableView)
    let indexPath = (fromViewController as! MainViewController).tableView.indexPathForRowAtPoint(location)

    if indexPath == nil {
        pan.state = .Failed
        return
    }
    (fromViewController as! MainViewController).instantiateAmountViewController((fromViewController as! MainViewController).defaultCurrency, indexPath: indexPath)
}

override func dismissViewController(pan: UIPanGestureRecognizer) {
    toViewController.performSegueWithIdentifier("DismissAmountViewControllerSegue", sender: pan)
}

override func doOnCompletion() {
    (toViewController as! AmountViewController).dismissAmountViewController()
}

override func finishDismissing(velocity: CGPoint, percentage: CGFloat) -> Bool {
    return percentage >= 0.4 && velocity.y > 0 || velocity.y > 300
}

The second way, I add a delegate for the parent:

@objc protocol InteractiveControllerTransitionDelegate {
    func getPercentage(pan: UIPanGestureRecognizer) -> CGFloat

    func onStageViewController(view: UIView)
    func offStageParentViewController(view: UIView)
    func offStageChildViewController(view: UIView)

    func presentViewController(pan: UIPanGestureRecognizer)
    func dismissViewController(pan: UIPanGestureRecognizer)

    func finishPresenting(velocity: CGPoint, percentage: CGFloat) -> Bool
    func finishDismissing(velocity: CGPoint, percentage: CGFloat) -> Bool

    optional func doOnCompletion()
    optional func doOnCancellation()
}

Then I just delete the "implementations" of those methods in the parent and precede any call to those methods by delegate. and the children implement those methods.

What is the best way practice? Or maybe both of them are wrong and there is a better one?

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.