skip to Main Content

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

2

Answers


  1. The "black screen suddenly turns into viewControllerB" because the new controller hasn’t been added yet as viewController.view.alpha = 1 will execute before appDelegate.window?.rootViewController = viewController.

    I suggest you create a new UIViewController that acts as a container for the controllers in question. To switch between them:

    1. Add the new controller to the container with a 0 alpha.
    2. Slide the old controller and fade the new one in.
    3. Remove the old controller from the container.
    Login or Signup to reply.
  2. 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
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search