My subviews doesn't animate with the view controller

1.6k views Asked by At

I've a simple sample project in which I'm playing around some view controller custom animations, to integrate in a bigger project, but I've found some issues with them.

I have 2 simple view controllers, the first one with a button to present the second one, with custom animation; and the second one has some labels and a dismiss button which animates the view controller out 'of the scene'

When I'm presenting the view controller, after I click the present button, the subviews of the presented view controller, appear on the screen before the view controller animates in, but when I'm dismissing the VC, all the subviews go with the dismissed view controller.

On my view controller 2 (presented) I've the views with default auto-layout (reset to suggested constraints)

I can't find a justification why the subviews doesn't animate in, inside the view controller as I expected to.

Below goes a gif showing what's happening, and the source-code:

GIF

CODE: view controller 1 (presenting VC)

import UIKit

class ViewController: UIViewController, UIViewControllerTransitioningDelegate {

    var animator = Animator()

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

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @IBAction func presentButton(_ sender: Any) {
        let storyboard = UIStoryboard(name: "Main", bundle: nil);
        let vc = storyboard.instantiateViewController(withIdentifier: "vc2") as! ViewController2
    
        vc.transitioningDelegate = self
        vc.modalPresentationStyle = .custom // chama as funções à parte
    
        present(vc, animated: true, completion: nil)
    }

    // REMARK: UIViewControllerTransitioningDelegate
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        animator.transitioningMode = .Present // sabe que está em presenting mode
        return animator
    }

    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        animator.transitioningMode = .Dismiss // Sabe que está em dismissing mode
        return animator
    }

    func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
        return CustomPresentationController(presentedViewController: presented, presenting: presenting)
    }

}

CODE: view controller 2 (presented VC)

import UIKit

class ViewController2: UIViewController {

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

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    @IBAction func dismissButton(_ sender: Any) {
        dismiss(animated: true, completion: nil)
    }

}

CODE: CustomPresentationController

import UIKit
import Foundation

class CustomPresentationController: UIPresentationController {

    override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController!) {
        super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
    }

    override var frameOfPresentedViewInContainerView: CGRect {
        // arranca a 0
        var presentedViewFrame = CGRect.zero
    
        // Calcula os bounds do container
        let containerBounds = self.containerView?.bounds
    
        // Recalcula o size
        presentedViewFrame.size = CGSize(width: (containerBounds?.size.width)! , height: ((containerBounds?.size.height)! * 0.90))
    
        presentedViewFrame.origin.x = 0
        presentedViewFrame.origin.y = (containerBounds?.size.height)! * 0.1
    
        return presentedViewFrame
    }

}

CODE: Animator class

import Foundation
import UIKit

class Animator: NSObject, UIViewControllerAnimatedTransitioning {

    enum Status {
        case Present
        case Dismiss
    }
    var transitioningMode: Status = .Present
    var presentDuration = 1.0
    var dismissDuration = 0.3

    // Tempo da animação
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        if (transitioningMode == .Present) {
            return presentDuration
        } else {
            return dismissDuration
        }
    }

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        // Get the set of relevant objects.
        let containerView = transitionContext.containerView
    
        guard
            let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
            let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
            else {
                print("Returning animateTransition VC")
                return
            }
    
        let toView = transitionContext.view(forKey: UITransitionContextViewKey.to)
        let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from)
    
        // Set up some variables for the animation.
        let containerFrame: CGRect = containerView.frame
        var toViewStartFrame: CGRect = transitionContext.initialFrame(for: toVC)
        let toViewFinalFrame: CGRect = transitionContext.finalFrame(for: toVC)
        var fromViewFinalFrame: CGRect = transitionContext.finalFrame(for: fromVC)
    
        // Set up animation parameters.
        if (transitioningMode == .Present) {
            // Modify the frame of the presented view so that it starts
            // offscreen at the lower-right corner of the container.
            toViewStartFrame.origin.x = 0//containerFrame.size.width
            toViewStartFrame.origin.y = containerFrame.size.height * 0.1
        } else {
            // Modify the frame of the dismissed view so it ends in
            // the lower-right corner of the container view.
            fromViewFinalFrame = CGRect(x: containerFrame.size.width,
                                        y: containerFrame.size.height,
                                        width: (toVC.view.frame.size.width),
                                        height: (toVC.view.frame.size.height))
        }
    
        if (transitioningMode == .Present) {
            // Always add the "to" view to the container.
            // And it doesn't hurt to set its start frame.
            containerView.addSubview(toView!)
            toView?.frame = toViewStartFrame
        }
    
        // Animate using the animator's own duration value.
        UIView.animate(withDuration: presentDuration, animations: {
            if (self.transitioningMode == .Present) {
                // Move the presented view into position.
                toView?.frame = toViewFinalFrame
            }
            else {
                // Move the dismissed view offscreen.
                fromView?.frame = fromViewFinalFrame
            }
        }) { (finished) in
            let success = !(transitionContext.transitionWasCancelled)
            // After a failed presentation or successful dismissal, remove the view.
            if ((self.transitioningMode == .Present && !success) || (self.transitioningMode == .Dismiss && success)) {
                toView?.removeFromSuperview()
            }
        
            // Notify UIKit that the transition has finished
            transitionContext.completeTransition(success)
        }
    }

}
1

There are 1 answers

3
agibson007 On BEST ANSWER

Alright I am going to take a stab at helping you. First the reason why it is not working is autolayout has position your views but you have a frame of size zero basically. To show you this go to the controller in question and have it clip its subviews and they will not show up during the transition. Now I probably would not change the frame to move it in since you just want to slide it in and you can probably get rid of some code. Here is what it looks like for me.

import UIKit

class ViewController: UIViewController,UIViewControllerTransitioningDelegate {

    var animator = Animator()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @IBAction func presentModally(_ sender: Any) {
        self.performSegue(withIdentifier: "modal", sender: nil)
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "modal"{
           let dvc = segue.destination
            dvc.transitioningDelegate = self
            dvc.modalPresentationStyle = .overCurrentContext
        }
    }

    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        animator.transitioningMode = .Present // sabe que está em presenting mode
        return animator
    }

    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        animator.transitioningMode = .Dismiss // Sabe que está em dismissing mode
        return animator
    }

}

Delete the Custom Presentation Controller. I honestly don't see a need for it.

Now the animator.

import UIKit

class Animator: NSObject,UIViewControllerAnimatedTransitioning {
    enum Status {
        case Present
        case Dismiss
    }
    var transitioningMode: Status = .Present
    var presentDuration = 1.0
    var dismissDuration = 0.3


    // Tempo da animação
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        if (transitioningMode == .Present) {
            return presentDuration
        } else {
            return dismissDuration
        }
    }


    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

        // Get the set of relevant objects.
        let containerView = transitionContext.containerView

        guard
            let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
            let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
            else {
                print("Returning animateTransition VC")
                return
        }

        let toView = transitionContext.view(forKey: UITransitionContextViewKey.to)
        let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from)

        // Set up some variables for the animation.
        let containerFrame:     CGRect = containerView.frame
        let toViewFinalFrame:   CGRect = transitionContext.finalFrame(for: toVC)
        var fromViewFinalFrame: CGRect = transitionContext.finalFrame(for: fromVC)

        // Set up animation parameters.
        if (transitioningMode == .Present) {
            let anchor = toViewFinalFrame.origin
            toView?.layer.anchorPoint = anchor
            toView?.layer.position = anchor
            toView?.transform = CGAffineTransform(scaleX: 0, y: 1)
            //another posibility
            //toView?.transform = CGAffineTransform(translationX: -containerView.bounds.width, y: -containerView.bounds.height)
        } else {
            // Modify the frame of the dismissed view so it ends in
            // the lower-right corner of the container view.
            fromViewFinalFrame = CGRect(x: containerFrame.size.width,
                                        y: containerFrame.size.height,
                                        width: (toVC.view.frame.size.width),
                                        height: (toVC.view.frame.size.height))
        }

        if (transitioningMode == .Present) {
            // Always add the "to" view to the container.
            // And it doesn't hurt to set its start frame.
            containerView.addSubview(toView!)
           // toView?.frame = toViewStartFrame
        }

        // Animate using the animator's own duration value.
        UIView.animate(withDuration: presentDuration, animations: {

            if (self.transitioningMode == .Present) {
                // Move the presented view into position.
                toView?.transform = .identity
            }
            else {
                // Move the dismissed view offscreen.
                fromView?.frame = fromViewFinalFrame
            }
        }) { (finished) in
            let success = !(transitionContext.transitionWasCancelled)
            // After a failed presentation or successful dismissal, remove the view.
            if ((self.transitioningMode == .Present && !success) || (self.transitioningMode == .Dismiss && success)) {
                toView?.removeFromSuperview()
            }

            // Notify UIKit that the transition has finished
            transitionContext.completeTransition(success)

        }

    }

}

That's all you need. Cheers.