iOS窗口的根视图控制器通常在一开始就初始化为选项卡控制器或导航控制器吗?在应用程序中多次更改根视图控制器是否可以?
我有一个场景,顶部视图基于用户操作而不同。我考虑使用带有启动屏幕图像的导航控制器,并根据需要推送/弹出视图控制器。或者,我可以不断更改窗口的顶部视图控制器。哪种方法更好?
iOS窗口的根视图控制器通常在一开始就初始化为选项卡控制器或导航控制器吗?在应用程序中多次更改根视图控制器是否可以?
我有一个场景,顶部视图基于用户操作而不同。我考虑使用带有启动屏幕图像的导航控制器,并根据需要推送/弹出视图控制器。或者,我可以不断更改窗口的顶部视图控制器。哪种方法更好?
iOS 8.0, Xcode 6.0.1,启用了ARC
你的大多数问题已经得到解答。但是,我可以处理最近自己遇到的一个问题。
在应用程序中更改根视图控制器多次是否可行?
答案是是的。最近我不得不这样做,以便在启动应用程序后不再需要的初始UIView之后重置我的UIView层次结构。换句话说,在应用程序的“didFinishLoadingWithOptions”之后,您可以从任何其他UIViewController随时重置“rootViewController”。
操作步骤...
1)声明对应用程序委托的引用(名为"Test"的应用程序)...
TestAppDelegate *testAppDelegate = (TestAppDelegate *)[UIApplication sharedApplication].delegate;
2) 选择一个你想要作为“rootViewController”的UIViewController,可以从storyboard中选择,也可以通过编程定义...
UIStoryboard *mainStoryBoard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
NewRootViewController *newRootViewController = [mainStoryBoard instantiateViewControllerWithIdentifier:@"NewRootViewController"];
UIViewController *newRootViewController = [[UIViewController alloc] init];
newRootViewController.view = [[UIView alloc] initWithFrame:CGRectMake(0, 50, 320, 430)];
newRootViewController.view.backgroundColor = [UIColor whiteColor];
3) 把所有内容整合起来...
testAppDelegate.window.rootViewController = newRootViewController;
[testAppDelegate.window makeKeyAndVisible];
4) 你甚至可以加入动画效果...
testAppDelegate.window.rootViewController = newRootViewController;
[testAppDelegate.window makeKeyAndVisible];
newRootViewController.view.alpha = 0.0;
[UIView animateWithDuration:2.0 animations:^{
newRootViewController.view.alpha = 1.0;
}];
[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;
}];
通常使用“已呈现视图控制器”(presentViewController:animated:completion:
)。您可以有任意数量的这些视图控制器,它们有效地出现在根视图控制器的前面(并基本上被替换)。如果您不想要动画效果,则不必使用动画。您可以关闭已呈现的视图控制器以返回到原始的根视图控制器,但您不必这样做;如果您愿意,已呈现的视图控制器可以一直存在。
以下是我书中关于已呈现视图控制器的部分:
http://www.apeth.com/iOSBook/ch19.html#_presented_view_controller
在该章节早期的这个图表中,一个已呈现的视图控制器已经完全接管了应用程序界面;根视图控制器及其子视图不再在界面中。根视图控制器仍然存在,但它是轻量级的,无关紧要。
从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()
}
}
}
只需使用以下代码即可替换rootViewController:
let newRootViewController = self.storyboard!.instantiateViewControllerWithIdentifier("BlackViewController")
UIApplication.sharedApplication().keyWindow!.replaceRootViewControllerWith(newRootViewController, animated: true, completion: nil)
希望这可以帮到你:) 已在iOS 8.4上进行了测试;也测试了对导航控制器的支持(应该也支持选项卡控制器等,但我没有测试)
说明
如果一个模态视图控制器出现在旧的根视图控制器之上,根视图控制器会被替换,但旧视图仍然悬挂在新的根视图控制器视图下面(例如在水平翻转或淡入淡出的过渡动画期间可见),并且旧的视图控制器层次仍被分配(如果替换发生多次则可能会导致严重的内存问题)。
所以唯一的解决方案是关闭所有模态视图控制器,然后替换根视图控制器。在解除和替换期间,在窗口上放置屏幕快照以隐藏丑陋的闪烁过程。
TabBarController
修复它? - SwingerDingerThread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
。 - Krunal NagvadiaUIViewController *viewController = [UIViewController alloc] init];
[self.window setRootViewController:viewController];
- (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];
}
}
}
对于iOS8,我们还需要将以下两个参数设置为YES。
providesPresentationContextTransitionStyle
definesPresentationContext
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;
}];
}];
}
对于那些尝试在 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
}
}
创建了一个实用工具类,其中包括更改根视图控制器的方法。
class AppUtilities {
class func changeRootVC( _ vc: UIViewController) {
SceneDelegate.shared.window?.rootViewController = vc
SceneDelegate.shared.window?.makeKeyAndVisible()
}
}
//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)
}
}