更改 iOS 窗口的根视图控制器
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/15774003/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Changing root view controller of a iOS Window
提问by Anna
Is the root view controller of a iOS Window usually initialized once in the beginning to a tab bar controller or navigation controller? Is it okay to change the root view controller multiple times within an app?
iOS Window 的根视图控制器是否通常在开始时初始化为标签栏控制器或导航控制器?可以在应用程序中多次更改根视图控制器吗?
I have a scenario where the top view is different based on user action. I was thinking of having a navigation controller with the top view controller having the image of the splash screen, and pushing/popping view controllers as required. Alternately, I can keep changing the window's top view controller. Which will be a better approach?
我有一个场景,顶视图根据用户操作而不同。我正在考虑使用带有启动画面图像的顶部视图控制器的导航控制器,并根据需要推送/弹出视图控制器。或者,我可以不断更改窗口的顶视图控制器。哪个是更好的方法?
采纳答案by matt
It is more usual to use a "presented view controller" (presentViewController:animated:completion:
). You can have as many of these as you like, effectively appearing in front of (and basically replacing) the root view controller. There doesn't have to be any animation if you don't want, or there can be. You can dismiss the presented view controller to go back to the original root view controller, but you don't have to; the presented view controller can just be there forever if you like.
使用“呈现的视图控制器”( presentViewController:animated:completion:
)更为常见。您可以根据需要拥有任意数量的这些,有效地出现在(并基本上替换)根视图控制器之前。如果您不想要,则不必有任何动画,或者可以有。您可以关闭呈现的视图控制器以返回原始根视图控制器,但您不必这样做;如果您愿意,呈现的视图控制器可以永远存在。
Here's the section on presented view controllers from my book:
这是我书中关于呈现的视图控制器的部分:
http://www.apeth.com/iOSBook/ch19.html#_presented_view_controller
http://www.apeth.com/iOSBook/ch19.html#_presented_view_controller
In this diagram (from earlier in that chapter), a presented view controller has completely taken over the app interface; the root view controller and its subviews are no longer in the interface. The root view controller still exists, but this is lightweight and doesn't matter.
在这个图中(从那一章的前面开始),一个呈现的视图控制器完全接管了应用程序界面;根视图控制器及其子视图不再在界面中。根视图控制器仍然存在,但这是轻量级的并且无关紧要。
回答by serge-k
iOS 8.0, Xcode 6.0.1, ARC enabled
iOS 8.0、Xcode 6.0.1、ARC 已启用
Most of your questions were answered. However, I can tackle one that I recently had to deal with myself.
你的大部分问题都得到了回答。但是,我可以解决一个我最近不得不自己处理的问题。
Is it okay, to change the root view controller multiple times, within an app?
可以在应用程序中多次更改根视图控制器吗?
The answer is yes. I had to do this recently to reset my UIView hierarchy after the initial UIViews that were part of the app. starting up were no longer needed. In other words, you can reset your "rootViewController" from any other UIViewController at anytime after the app. "didFinishLoadingWithOptions".
答案是肯定的。我最近不得不这样做以在作为应用程序一部分的初始 UIViews 之后重置我的 UIView 层次结构。不再需要启动。换句话说,您可以在应用程序之后随时从任何其他 UIViewController 重置您的“rootViewController”。“didFinishLoadingWithOptions”。
To do this...
去做这个...
1) Declare a reference to your app. delegate (app called "Test")...
1) 声明对您的应用程序的引用。委托(称为“测试”的应用程序)...
TestAppDelegate *testAppDelegate = (TestAppDelegate *)[UIApplication sharedApplication].delegate;
2) Pick a UIViewController you wish to make your "rootViewController"; either from storyboard or define programmatically...
2) 选择一个你想制作“rootViewController”的 UIViewController;从故事板或以编程方式定义...
- a) storyboard (make sure identifier, i.e. storyboardID, exists
in Identity Inspector for the UIViewController):
- a) 故事板(确保标识符,即 storyboardID,存在于 UIViewController 的 Identity Inspector 中):
UIStoryboard *mainStoryBoard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
NewRootViewController *newRootViewController = [mainStoryBoard instantiateViewControllerWithIdentifier:@"NewRootViewController"];
- b) programmatically (could addSubview, etc.)
- b) 以编程方式(可以添加子视图等)
UIViewController *newRootViewController = [[UIViewController alloc] init];
newRootViewController.view = [[UIView alloc] initWithFrame:CGRectMake(0, 50, 320, 430)];
newRootViewController.view.backgroundColor = [UIColor whiteColor];
3) Putting it all together...
3)把它们放在一起……
testAppDelegate.window.rootViewController = newRootViewController;
[testAppDelegate.window makeKeyAndVisible];
4) You can even throw in an animation...
4)你甚至可以加入动画......
testAppDelegate.window.rootViewController = newRootViewController;
[testAppDelegate.window makeKeyAndVisible];
newRootViewController.view.alpha = 0.0;
[UIView animateWithDuration:2.0 animations:^{
newRootViewController.view.alpha = 1.0;
}];
Hope this helps someone! Cheers.
希望这可以帮助某人!干杯。
The root view controller for the window.
The root view controller provides the content view of the window. Assigning a view controller to this property (either programmatically or using Interface Builder) installs the view controller's view as the content view of the window. If the window has an existing view hierarchy, the old views are removed before the new ones are installed. The default value of this property is nil.
窗口的根视图控制器。
根视图控制器提供窗口的内容视图。将视图控制器分配给此属性(以编程方式或使用 Interface Builder)会将视图控制器的视图安装为窗口的内容视图。如果窗口具有现有视图层次结构,则在安装新视图之前删除旧视图。此属性的默认值为 nil。
*Update 9/2/2015
*更新 9/2/2015
As comments below point out, you must handle the removal of the old view controller when the new view controller is presented. You may elect to have a transitional view controller in which you will handle this. Here are a few hints on how to implement this:
正如下面的评论所指出的,当新的视图控制器出现时,你必须处理旧视图控制器的删除。你可以选择有一个过渡视图控制器来处理这个问题。以下是有关如何实现这一点的一些提示:
[UIView transitionWithView:self.containerView
duration:0.50
options:options
animations:^{
//Transition of the two views
[self.viewController.view removeFromSuperview];
[self.containerView addSubview:aViewController.view];
}
completion:^(BOOL finished){
//At completion set the new view controller.
self.viewController = aViewController;
}];
回答by Matej Vargov?ík
From comments on serge-k's answer I have built a working solution with a workaround of strange behavior when there is a modal view controller presented over the old rootViewController:
从对 serge-k 的回答的评论中,我构建了一个工作解决方案,当在旧的 rootViewController 上显示一个模态视图控制器时,它可以解决奇怪的行为:
extension UIView {
func snapshot() -> UIImage {
UIGraphicsBeginImageContextWithOptions(bounds.size, false, UIScreen.mainScreen().scale)
drawViewHierarchyInRect(bounds, afterScreenUpdates: true)
let result = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return result
}
}
extension UIWindow {
func replaceRootViewControllerWith(_ replacementController: UIViewController, animated: Bool, completion: (() -> Void)?) {
let snapshotImageView = UIImageView(image: self.snapshot())
self.addSubview(snapshotImageView)
let dismissCompletion = { () -> Void in // dismiss all modal view controllers
self.rootViewController = replacementController
self.bringSubview(toFront: snapshotImageView)
if animated {
UIView.animate(withDuration: 0.4, animations: { () -> Void in
snapshotImageView.alpha = 0
}, completion: { (success) -> Void in
snapshotImageView.removeFromSuperview()
completion?()
})
}
else {
snapshotImageView.removeFromSuperview()
completion?()
}
}
if self.rootViewController!.presentedViewController != nil {
self.rootViewController!.dismiss(animated: false, completion: dismissCompletion)
}
else {
dismissCompletion()
}
}
}
To replace the rootViewController just use:
要替换 rootViewController 只需使用:
let newRootViewController = self.storyboard!.instantiateViewControllerWithIdentifier("BlackViewController")
UIApplication.sharedApplication().keyWindow!.replaceRootViewControllerWith(newRootViewController, animated: true, completion: nil)
Hope this helps :) tested on iOS 8.4; also tested for navigation controllers support (should support also tab bar controllers etc., but I did not test it)
希望这会有所帮助:) 在 iOS 8.4 上测试;还测试了导航控制器支持(也应该支持标签栏控制器等,但我没有测试)
Explanation
解释
If there is a modal view controller presented over old rootViewController, the rootViewController is replaced, but the old view still remains hanging below the new rootViewController's view (and can be seen for example during Flip Horizontal or Cross Dissolve transition animations) and the old view controller hierarchy remains allocated (which may cause severe memory problems if replacement happens multiple times).
如果旧的 rootViewController 上存在模态视图控制器,则替换 rootViewController,但旧的视图仍然悬挂在新的 rootViewController 的视图下方(并且可以在例如 Flip Horizontal 或 Cross Dissolve 过渡动画期间看到)和旧的视图控制器层次结构保持分配状态(如果多次替换,可能会导致严重的内存问题)。
So the only solution is to dismiss all modal view controllers and then replace the rootViewController. A snapshot of the screen is placed over the window during dismissal and replacement to hide the ugly flashing process.
所以唯一的解决方案是关闭所有模态视图控制器,然后替换 rootViewController。屏幕快照在关闭和更换期间放置在窗口上,以隐藏丑陋的闪烁过程。
回答by Ben M
You can change the window's rootViewController throughout the application life cycle.
您可以在整个应用程序生命周期中更改窗口的 rootViewController。
UIViewController *viewController = [UIViewController alloc] init];
[self.window setRootViewController:viewController];
When you change the rootViewController, you still may want to add a UIImageView as a subview on the window to act as a splash image. I hope this makes sense, something like this:
当您更改 rootViewController 时,您仍然可能希望添加一个 UIImageView 作为窗口上的子视图以充当启动图像。我希望这是有道理的,像这样:
- (void) addSplash {
CGRect rect = [UIScreen mainScreen].bounds;
UIImageView *splashImage = [[UIImageView alloc] initWithFrame:rect];
splashImage.image = [UIImage imageNamed:@"splash.png"];
[self.window addSubview:splashImage];
}
- (void) removeSplash {
for (UIView *view in self.window.subviews) {
if ([view isKindOfClass:[UIImageView class]]) {
[view removeFromSuperview];
}
}
}
回答by Zeeshan
For iOS8, we also need to set below two parameters to YES.
对于iOS8,我们还需要将下面两个参数设置为YES。
providesPresentationContextTransitionStyle
definesPresentationContext
Here is my code for presenting transparent model view controller under navigation controller for iOS 6 and above.
这是我的代码,用于在 iOS 6 及更高版本的导航控制器下呈现透明模型视图控制器。
ViewController *vcObj = [[ViewController alloc] initWithNibName:NSStringFromClass([ViewController class]) bundle:nil];
UINavigationController *navCon = [[UINavigationController alloc] initWithRootViewController:vcObj];
if ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0) {
navCon.providesPresentationContextTransitionStyle = YES;
navCon.definesPresentationContext = YES;
navCon.modalPresentationStyle = UIModalPresentationOverCurrentContext;
[self presentViewController:navCon animated:NO completion:nil];
}
else {
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[self presentViewController:navCon animated:NO completion:^{
[navCon dismissViewControllerAnimated:NO completion:^{
appDelegate.window.rootViewController.modalPresentationStyle = UIModalPresentationCurrentContext;
[self presentViewController:navCon animated:NO completion:nil];
appDelegate.window.rootViewController.modalPresentationStyle = UIModalPresentationFullScreen;
}];
}];
}
回答by Mahendra
For the people who try to change the root view controller for iOS 13 and laterthen you need to change root view controller using SceneDelegate
's window
property.
对于尝试更改iOS 13 及更高版本的根视图控制器的人,您需要使用SceneDelegate
的window
属性更改根视图控制器。
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
static let shared = SceneDelegate()
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
//other stuff
}
}
Created a utility class which has the method to change the root view controller.
创建了一个实用程序类,该类具有更改根视图控制器的方法。
class AppUtilities {
class func changeRootVC( _ vc: UIViewController) {
SceneDelegate.shared.window?.rootViewController = vc
SceneDelegate.shared.window?.makeKeyAndVisible()
}
}
You can change root view controller in following way.
您可以通过以下方式更改根视图控制器。
//Here I'm setting HomeVC as root view controller
if let homeVC = UIStoryboard(name: "Main", bundle: nil)?.instantiateViewController(identifier: "HomeVC") as? HomeVC {
let rootVC = UINavigationController(rootViewController: homeVC)
AppUtilities.changeRootVC(rootVC)
}
}