更改iOS窗口的根视图控制器

时间:2022-02-16 09:27:38

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窗口的根视图控制器通常在开始时初始化为标签栏控制器或导航控制器吗?在应用程序中多次更改根视图控制器是否可以?

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?

我有一个场景,根据用户操作,顶视图是不同的。我正在考虑使用导航控制器,顶视图控制器具有启动画面的图像,并根据需要按下/弹出视图控制器。或者,我可以继续更改窗口的顶视图控制器。哪种方法更好?

5 个解决方案

#1


45  

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.

在该图中(从该章的前面开始),呈现的视图控制器已经完全接管了应用程序界面;根视图控制器及其子视图不再在界面中。根视图控制器仍然存在,但这是轻量级的并不重要。

更改iOS窗口的根视图控制器

#2


50  

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)声明对您的应用的引用。委托(app称为“测试”)......

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):

UIStoryboard *mainStoryBoard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];

NewRootViewController *newRootViewController = [mainStoryBoard instantiateViewControllerWithIdentifier:@"NewRootViewController"];

    b) programmatically (could addSubview, etc.)

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

* 2015年9月2日更新

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;

                }];

#3


24  

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 Horizo​​ntal或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。在解雇和替换期间,屏幕的快照被放置在窗口上以隐藏丑陋的闪烁过程。

#4


5  

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];
      }
    }
}

#5


3  

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;

        }];
    }];
}

#1


45  

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.

在该图中(从该章的前面开始),呈现的视图控制器已经完全接管了应用程序界面;根视图控制器及其子视图不再在界面中。根视图控制器仍然存在,但这是轻量级的并不重要。

更改iOS窗口的根视图控制器

#2


50  

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)声明对您的应用的引用。委托(app称为“测试”)......

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):

UIStoryboard *mainStoryBoard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];

NewRootViewController *newRootViewController = [mainStoryBoard instantiateViewControllerWithIdentifier:@"NewRootViewController"];

    b) programmatically (could addSubview, etc.)

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

* 2015年9月2日更新

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;

                }];

#3


24  

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 Horizo​​ntal或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。在解雇和替换期间,屏幕的快照被放置在窗口上以隐藏丑陋的闪烁过程。

#4


5  

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];
      }
    }
}

#5


3  

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;

        }];
    }];
}