在UISplitViewController和其他视图控制器之间切换的最佳方法是什么?

我正在创作一个iPad应用程序。 应用程序中的一个屏幕非常适合使用UISplitViewController。 但是,应用程序的顶层是一个主菜单,我不想使用UISplitViewController。 这提出了一个问题,因为苹果声明:

  1. UISplitViewController应该是应用程序中的顶级视图控制器,即它的视图应该被添加为UIWindow的子视图

  2. 如果使用, UISplitViewController应该在那里的应用程序的生命周期 – 即不要从UIWindow中删除其视图,并把另一个地方,反之亦然

经过阅读和实验,似乎只有可行的select,以满足苹果的要求,我们自己的是使用modal dialog。 所以我们的应用程序在根级别有一个UISplitViewController(即它的视图作为UIWindow的子视图添加),并显示我们的主菜单,我们把它作为一个全屏模式对话框推到UISplitViewController上。 然后通过closures主菜单视图控制器模式对话框,我们可以实际显示我们的分割视图。

这个策略似乎工作正常。 但是它提出了一些问题:

1)有没有更好的方式来构造这个,没有模态,也符合所有提到的要求? 由于被推送为模态对话,主UI显得有些奇怪。 (Modals被认为是专注于用户的任务。)

2)由于我的方法,我有遭受app store拒绝的风险吗? 按照苹果的人机界面指导方针,这种模式策略可能是“滥用”模式对话。 但是他们给了我什么其他select? 难道他们知道我在做这个吗?

德勤! 跑到相同的问题,并用同样的方式解决它使用模态。 在我的情况下,它是一个login视图,然后主菜单以及在分割视图之前显示。 我使用了和你想象的一样的策略。 我(以及其他几位知识渊博的iOS人士)找不到更好的解决办法。 为我工作得很好。 用户永远不会注意到模态。 给他们如此。 是的,我也可以告诉你,有相当多的应用程序在应用程序商店的引擎盖下的技巧。 :)在另一个说明,请让我知道,如果你想出一些更好的方式有时候某处:)

我认真地不相信在UISplitViewController (例如login表单)之前显示一些UIViewController的概念变得如此复杂,直到我不得不创build这种视图hiearchy。

我的例子是基于iOS 8和XCode 6.0(Swift),所以我不确定这个问题是否以同样的方式存在,或者是由于iOS 8引入了一些新的错误,但是从所有类似的问题发现,我没有看到这个问题的完整的“不是很hacky”的解决scheme。

在我结束了一个解决scheme之前,我会指导你完成一些我已经尝试过的事情(在本文末尾)。 每个示例都基于在未启用CoreData的情况下从Master-Detail模板创build新项目。


首先尝试(模态segue到UISplitViewController):

  1. 创build新的UIViewController子类(例如LoginViewController)
  2. 在storyboard中添加新的视图控制器,将其设置为初始视图控制器(而不是UISplitViewController)并将其连接到LoginViewController
  3. 将UIButton添加到LoginViewController并从该button创build模式segue到UISplitViewController
  4. 将UISplitViewController的样板设置代码从AppDelegate的didFinishLaunchingWithOptions到LoginViewController的prepareForSegue

这几乎工作。 我几乎说,因为在应用程序启动LoginViewController后,你点击button,继续到UISplitViewController,有一个奇怪的错误: 显示和隐藏主视图控制器的方向变化不再animation。

经过一段时间的努力,这个问题没有真正的解决scheme,我认为这是不可思议的规则连接UISplitViewController必须是rootViewController(在这种情况下,它不是,LoginViewController是),所以我放弃了从这个不完美的解决scheme。


第二次尝试(模式从UISplitViewController segue):

  1. 创build新的UIViewController子类(例如LoginViewController)
  2. 在storyboard中添加新的视图控制器,并将其连接到LoginViewController(但这次离开UISplitViewController成为初始视图控制器)
  3. 从UISplitViewController创build模态segue到LoginViewController
  4. 将UIButton添加到LoginViewController中,并从该button创buildunwind segue

最后,在设置UISplitViewController的样板代码之后,将此代码添加到AppDelegate的didFinishLaunchingWithOptions

 window?.makeKeyAndVisible() splitViewController.performSegueWithIdentifier("segueToLogin", sender: self) return true 

或者用这个代码来代替:

 window?.makeKeyAndVisible() let loginViewController = splitViewController.storyboard?.instantiateViewControllerWithIdentifier("LoginVC") as LoginViewController splitViewController.presentViewController(loginViewController, animated: false, completion: nil) return true 

这两个例子都产生了相同的几个坏的东西:

  1. 控制台输出: Unbalanced calls to begin/end appearance transitions for <UISplitViewController: 0x7fc8e872fc00>
  2. 必须首先显示UISplitViewController,然后才能以模态方式对LoginViewController进行查找(我宁愿仅显示login窗体,以便用户在login之前不会看到UISplitViewController)
  3. 放松segue不会被调用(这完全是其他错误,现在我不会去那个故事)

解决scheme(更新rootViewController)

我发现哪个工作正常的唯一方法是,如果你改变窗口的rootViewController:

  1. 为LoginViewController和UISplitViewController定义Storyboard ID,并向AppDelegate添加某种loggedIn属性。
  2. 基于这个属性,实例化适当的视图控制器,然后将其设置为rootViewController。
  3. didFinishLaunchingWithOptions没有animation的情况下做animation,但是当从UI调用的时候animation。

以下是来自AppDelegate的示例代码:

 var loggedIn = false func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { setupRootViewController(false) return true } func setupRootViewController(animated: Bool) { if let window = self.window { var newRootViewController: UIViewController? = nil var transition: UIViewAnimationOptions // create and setup appropriate rootViewController if !loggedIn { let loginViewController = window.rootViewController?.storyboard?.instantiateViewControllerWithIdentifier("LoginVC") as LoginViewController newRootViewController = loginViewController transition = .TransitionFlipFromLeft } else { let splitViewController = window.rootViewController?.storyboard?.instantiateViewControllerWithIdentifier("SplitVC") as UISplitViewController let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as UINavigationController navigationController.topViewController.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem() splitViewController.delegate = self let masterNavigationController = splitViewController.viewControllers[0] as UINavigationController let controller = masterNavigationController.topViewController as MasterViewController newRootViewController = splitViewController transition = .TransitionFlipFromRight } // update app's rootViewController if let rootVC = newRootViewController { if animated { UIView.transitionWithView(window, duration: 0.5, options: transition, animations: { () -> Void in window.rootViewController = rootVC }, completion: nil) } else { window.rootViewController = rootVC } } } } 

这是来自LoginViewController的示例代码:

 @IBAction func login(sender: UIButton) { let delegate = UIApplication.sharedApplication().delegate as AppDelegate delegate.loggedIn = true delegate.setupRootViewController(true) } 

我也想听听有没有更好的/更清洁的方式,以在iOS 8中正常工作。

谁说你只能有一个窗口? 🙂

看看我对这个类似问题的回答是否有帮助。

这种方法对我来说工作得很好。 只要你不必担心多个显示或状态恢复,这个链接的代码应该足以做你所需要的:你不必让你的逻辑看起来倒退或重写现有的代码,并仍然可以利用在您的应用程序的更深层次的UISplitView – 没有(AFAIK)打破苹果的指导方针。

对于将来的iOS开发人员遇到同样的问题:这里是另一个答案和解释。 你必须使它成为根视图控制器。 如果不是,覆盖一个模式。

UISplitviewcontroller不作为rootview控制器

我想提出我的方法来呈现一个UISplitViewController,你可能喜欢通过-presentViewController:animated:completion:我们都知道这将不起作用)。 我创build了一个UISplitViewController子类,它响应:

 -presentAsRootViewController -returnToPreviousViewController 

类,像其他成功的方法一样,将UISplitViewController设置为窗口的rootViewController,但是通过使用-presentViewController:animated:completion:获取类似于默认的-presentViewController:animated:completion:

PresentableSplitViewController.h

 #import <UIKit/UIKit.h> @interface PresentableSplitViewController : UISplitViewController - (void) presentAsRootViewController; @end 

PresentableSplitViewController.m

 #import "PresentableSplitViewController.h" @interface PresentableSplitViewController () @property (nonatomic, strong) UIViewController *previousViewController; @end @implementation PresentableSplitViewController - (void) presentAsRootViewController { UIWindow *window=[[[UIApplication sharedApplication] delegate] window]; _previousViewController=window.rootViewController; UIView *windowSnapShot = [window snapshotViewAfterScreenUpdates:YES]; window.rootViewController = self; [window insertSubview:windowSnapShot atIndex:0]; CGRect dstFrame=self.view.frame; CGSize offset=CGSizeApplyAffineTransform(CGSizeMake(0, 1), window.rootViewController.view.transform); offset.width*=self.view.frame.size.width; offset.height*=self.view.frame.size.height; self.view.frame=CGRectOffset(self.view.frame, offset.width, offset.height); [UIView animateWithDuration:0.5 delay:0.0 usingSpringWithDamping:1.0 initialSpringVelocity:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ self.view.frame=dstFrame; } completion:^(BOOL finished) { [windowSnapShot removeFromSuperview]; }]; } - (void) returnToPreviousViewController { if(_previousViewController) { UIWindow *window=[[[UIApplication sharedApplication] delegate] window]; UIView *windowSnapShot = [window snapshotViewAfterScreenUpdates:YES]; window.rootViewController = _previousViewController; [window addSubview:windowSnapShot]; CGSize offset=CGSizeApplyAffineTransform(CGSizeMake(0, 1), window.rootViewController.view.transform); offset.width*=windowSnapShot.frame.size.width; offset.height*=windowSnapShot.frame.size.height; CGRect dstFrame=CGRectOffset(windowSnapShot.frame, offset.width, offset.height); [UIView animateWithDuration:0.5 delay:0.0 usingSpringWithDamping:1.0 initialSpringVelocity:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ windowSnapShot.frame=dstFrame; } completion:^(BOOL finished) { [windowSnapShot removeFromSuperview]; _previousViewController=nil; }]; } } @end 

我做了一个UISplitView作为初始视图,而不是模拟到全屏UIView和回到UISplitView。 如果你需要回到SplitView你必须使用自定义的继续。

阅读此链接(从日语翻译)

UIViewController到UISplitViewController

添加到@tadija的答案我在类似的情况:

我的应用程序只用于手机,我正在添加一个平板电脑的用户界面。 我决定在同一个应用程序中使用Swift,并最终将所有的应用程序迁移到同一个故事板(当我觉得IPad版本是稳定的时候,使用XCode6的新类对于手机来说应该是微不足道的)。

我的场景中没有定义赛段,它仍然有效。

我在我的应用程序委托的代码是在ObjectiveC中,略有不同 – 但使用相同的想法。 请注意,我正在使用场景中的默认视图控制器,与以前的示例不同。 我觉得这也可以在IOS7 / iPhone上运行,运行时将生成一个普通的UINavigationController而不是UISplitViewController 。 我甚至可能会添加新的代码,这将推动login视图控制器的IPhone,而不是改变rootVC。

 - (void) setupRootViewController:(BOOL) animated { UIViewController *newController = nil; UIStoryboard *board = [UIStoryboard storyboardWithName:@"Storyboard" bundle:nil]; UIViewAnimationOptions transition = UIViewAnimationOptionTransitionCrossDissolve; if (!loggedIn) { newController = [board instantiateViewControllerWithIdentifier:@"LoginViewController"]; } else { newController = [board instantiateInitialViewController]; } if (animated) { [UIView transitionWithView: self.window duration:0.5 options:transition animations:^{ self.window.rootViewController = newController; NSLog(@"setup root view controller animated"); } completion:^(BOOL finished) { NSLog(@"setup root view controller finished"); }]; } else { self.window.rootViewController = newController; } } 

另一种select:在细节视图控制器中,我显示一个模式视图控制器:

 let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate if (!appDelegate.loggedIn) { // display the login form let storyboard = UIStoryboard(name: "Storyboard", bundle: nil) let login = storyboard.instantiateViewControllerWithIdentifier("LoginViewController") as UIViewController self.presentViewController(login, animated: false, completion: { () -> Void in // user logged in and is valid now self.updateDisplay() }) } else { updateDisplay() } 

不要在没有设置login标志的情况下closureslogin控制器。 请注意,在IPhones中,主视图控制器将首先出现,因此需要在主视图控制器上使用非常类似的代码。