用动画交换 rootViewController? ?

我试图切换到另一个根视图控制器与标签栏; 通过应用程序委托,我想添加过渡动画。默认情况下,它只显示没有任何动画的视图。

let tabBar = self.instantiateViewController(storyBoard: "Main", viewControllerID: "MainTabbar")
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window = UIWindow(frame: UIScreen.main.bounds)
appDelegate.window?.rootViewController = tabBar
appDelegate.window?.makeKeyAndVisible()

这就是我如何切换到另一个 rootview 控制器。

36073 次浏览

Try this:

UIView.transition(from: appdelegate.window.rootViewController!.view, to: tabbar.view, duration: 0.6, options: [.transitionCrossDissolve], completion: {
_ in
appdelegate.window.rootViewController = tabbar
})

You can use UIView.transition(with: view) to replace the rootViewController of a UIWindow:

guard let window = UIApplication.shared.keyWindow else {
return
}


let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "MainTabbar")


// Set the new rootViewController of the window.
// Calling "UIView.transition" below will animate the swap.
window.rootViewController = vc


// A mask of options indicating how you want to perform the animations.
let options: UIView.AnimationOptions = .transitionCrossDissolve


// The duration of the transition animation, measured in seconds.
let duration: TimeInterval = 0.3


// Creates a transition animation.
// Though `animations` is optional, the documentation tells us that it must not be nil. ¯\_(ツ)_/¯
UIView.transition(with: window, duration: duration, options: options, animations: {}, completion:
{ completed in
// maybe do something on completion here
})

Im trying to swap to another root view controller ... and I want to add transition animation

I have an app that does this: it changes the root view controller with animation (it's called Albumen).

But my app actually doesn't actually change the root view controller. The root view controller is a custom container view controller that never changes. Its view is never seen and it has no functionality. Its only job is to be the place where the change happens: it swaps one child view controller for another — and thus the transition animation works.

In other words, you add one view controller to your view controller hierarchy, right at the top of the hierarchy, and the whole problem is solved neatly and correctly.

An alternative solution:

let stb = UIStoryboard(name: "YOUR_STORYBOARD_NAME", bundle: nil)
let rootVC = stb.instantiateViewController(withIdentifier: "YOUR_TABBAR_VIEWCONTROLLER_NAME")
let snapshot = (UIApplication.shared.keyWindow?.snapshotView(afterScreenUpdates: true))!
rootVC.view.addSubview(snapshot)


UIApplication.shared.keyWindow?.rootViewController = rootVC
UIView.transition(with: snapshot,
duration: 0.4,
options: .transitionCrossDissolve,
animations: {
snapshot.layer.opacity = 0
},
completion: { status in
snapshot.removeFromSuperview()
})

Swift 4

Paste function into AppDelegate:

func setRootViewController(_ vc: UIViewController, animated: Bool = true) {
guard animated, let window = self.window else {
self.window?.rootViewController = vc
self.window?.makeKeyAndVisible()
return
}


window.rootViewController = vc
window.makeKeyAndVisible()
UIView.transition(with: window,
duration: 0.3,
options: .transitionCrossDissolve,
animations: nil,
completion: nil)
}

I have created a helper class for this based on d.felber's answer:


import UIKit


class ViewPresenter {


public static func replaceRootView(for viewController: UIViewController,
duration: TimeInterval = 0.3,
options: UIView.AnimationOptions = .transitionCrossDissolve,
completion: ((Bool) -> Void)? = nil) {
guard let window = UIApplication.shared.keyWindow else {
return
}


guard let rootViewController = window.rootViewController else {
return
}


viewController.view.frame = rootViewController.view.frame
viewController.view.layoutIfNeeded()


UIView.transition(with: window, duration: duration, options: options, animations: {
window.rootViewController = viewController
}, completion: completion)
}
}

You can use it like this:

    let loginVC = SignInViewController(nibName: "SignInViewController", bundle: nil)
ViewPresenter.replaceRootView(for: loginVC)

or

ViewPresenter.replaceRootView(for: loginVC, duration: 0.3, options: .transitionCrossDissolve) {
(bool) in
// do something
}

Updated Swift 5.3 version:

    let foregroundedScenes = UIApplication.shared.connectedScenes.filter { $0.activationState == .foregroundActive }
let window = foregroundedScenes.map { $0 as? UIWindowScene }.compactMap { $0 }.first?.windows.filter { $0.isKeyWindow }.first
    

guard let uWindow = window else { return }


uWindow.rootViewController = customTabBarController
UIView.transition(with: uWindow, duration: 0.3, options: [.transitionCrossDissolve], animations: {}, completion: nil)
}

And here is example of transitionCrossDissolve with transform translation Y of snapshotView, I think this looks better than regular transition animation.

Tested with Swift 4~5, iOS 11 ~ 15.7

if let window = UIApplication.shared.keyWindow {
    

var snapShot = UIView()
    

let destinationVC = UIViewController()
if let realSnapShot = window.snapshotView(afterScreenUpdates: true) {
snapShot = realSnapShot
}
destinationVC.view.addSubview(snapShot)
window.rootViewController = destinationVC
window.makeKeyAndVisible()
    

UIView.transition(
with: window,
duration: 0.5,
options: .transitionCrossDissolve,
animations: {
snapShot.transform = CGAffineTransform(translationX: 0, y: snapShot.frame.height)
},
completion: { status in
snapShot.removeFromSuperview()
}
)
}