0

I have 2 root view controllers that I need to animate (slide up/fade) between.

When I press a button on viewControllerA, viewControllerB should load itself in the background and set itself as the root view controller. When that is ready, viewControllerA should slide up and at the same time gradually fade out, revealing viewControllerB. When the animation completes, viewControllerA should be removed from the hierarchy. I set viewcontrollerB as the root before the animation starts so the user can swipe around on viewControllerB as viewControllerA slides up.

viewControllerA will sometimes have a video playing or other animations going on and I do not want them to stop during the transition. viewControllerA should be fully removed from the hierarchy after the animation completes.

After many many hours I have come up with this:

private func setNewRootViewController(_ viewController: UIViewController) {
    DispatchQueue.main.async {
UIView.animate(withDuration: 0.5, animations: {
                // Slide up and fade out the current viewController (vcA)
                self.view.alpha = 0
                self.view.transform = CGAffineTransform(translationX: 0, y: -    self.view.frame.height)
            }) { _ in
                // Remove the current viewController from the parent view controller
                self.removeFromParent()

                // Set the new viewController (vcB) as the root view controller
                let appDelegate = UIApplication.shared.delegate as! AppDelegate
                appDelegate.window?.rootViewController = viewController
            }

            // Animate the appearance of the new viewController (vcB)
            UIView.animate(withDuration: 0.5) {
                viewController.view.alpha = 1
            }
    }
}

It slides up viewControllerA and fades it out but it only reveals a black background. After it finishes, the black screen suddenly turns into viewControllerB. I feel like I am super close but am tearing my hair out and it's late here. I have searched all through stack over flow and by testing several ideas is how I wrote the above. Please help

1 Answer 1

1

Instead of replacing the root view controller, you could create a custom container view controller for viewControllerA and viewControllerB:

https://developer.apple.com/library/archive/featuredarticles/ViewControllerPGforiPhoneOS/ImplementingaContainerViewController.html

Something like this:

class ContainerViewController: UIViewController {
    override func viewDidLoad() {
        let viewControllerA = ViewControllerA()
        addChild(viewControllerA)
        view.addSubview(viewControllerA.view)
        viewControllerA.view.frame = view.bounds
    }
    
    func customTransition(from: UIViewController, to: UIViewController) {
        from.willMove(toParent: nil)
        addChild(to)
        
        from.view.layer.zPosition = 1
        transition(from: from, to: to, duration: 1.0) {
            from.view.alpha = 0
            from.view.transform = CGAffineTransform(translationX: 0, y: -from.view.frame.height)
        } completion: { _ in
            from.removeFromParent()
            to.didMove(toParent: self)
        }
    }
    
    func switchControllers(sender: UIViewController) {
        if sender is ViewControllerA {
            let viewControllerB = ViewControllerB()
            
            customTransition(from: sender, to: viewControllerB)
        }
        
        if sender is ViewControllerB {
            let viewControllerA = ViewControllerA()
            
            customTransition(from: sender, to: viewControllerA)
        }
    }
}

class ChildViewController: UIViewController {
    override func viewDidLoad() {
        view.backgroundColor = .red
        
        // Use a button to switch view controllers in a real app
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(switchViewController(sender:)))
        view.addGestureRecognizer(tapGesture)
    }
    
    @objc func switchViewController(sender: UIGestureRecognizer) {
        if let container = parent as? ContainerViewController {
            container.switchControllers(sender: self)
        }
    }
}

class ViewControllerA: ChildViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .red
    }
}

class ViewControllerB: ChildViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .green
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you Jack, that is a good approach but in this case prefer no main container as each view controller has its own sub containers and want to limit that at the top, only switching between master roots if possible

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.