我正在编写一个 iPad 应用程序。应用程序中的屏幕之一非常适合使用 UISplitViewController。但是,应用程序的顶层是主菜单,我不想使用 UISplitViewController。这带来了一个问题,因为苹果声明:
UISplitViewController
应该是应用程序中的顶级 View Controller ,即它的 View 应该添加为UIWindow
如果使用,
UISplitViewController
应该在应用程序的生命周期内一直存在——即不要从 UIWindow 中删除其 View 并放置另一个 View ,反之亦然
经过阅读和实验,似乎满足 Apple 要求和我们自己的要求的唯一可行选择是使用模式对话框。因此,我们的应用程序在根级别有一个 UISplitViewController (即,它的 View 作为 UIWindow 的 subview 添加),并且为了显示我们的主菜单,我们将其作为全屏模式对话框推送到 UISplitViewController 上。然后通过关闭主菜单 View Controller 模式对话框,我们实际上可以显示我们的分割 View 。
这个策略看起来效果很好。但这引出了一些问题:
1)是否有更好的方式来构造这个,没有模式,也满足所有提到的要求?通过将主 UI 作为模式对话框推送来出现似乎有点奇怪。 (模态框应该用于集中的用户任务。)
2) 我是否会因为我的方法而面临被应用商店拒绝的风险?根据苹果的人机界面指南,这种模式策略可能“滥用”模式对话框。但他们还给了我什么其他选择呢?无论如何,他们会知道我正在这样做吗?
最佳答案
我真的不相信在 UISplitViewController (例如登录表单)之前显示一些 UIViewController 的概念会如此复杂,直到我不得不创建这种类型 View 层次结构。
我的示例基于 iOS 8 和 XCode 6.0 (Swift),所以我不确定这个问题以前是否以同样的方式存在,或者是由于 iOS 8 引入的一些新错误,但从在我发现的所有类似问题中,我没有看到这个问题的完整“不是很老套”的解决方案。
在最终找到解决方案之前(在这篇文章的末尾),我将引导您完成我尝试过的一些事情。每个示例都基于在未启用 CoreData 的情况下从主从模板创建新项目。
<小时/>第一次尝试(模态转场到 UISplitViewController):
- 创建新的 UIViewController 子类(例如 LoginViewController)
- 在 Storyboard中添加新的 View Controller ,将其设置为初始 View Controller (而不是 UISplitViewController)并将其连接到 LoginViewController
- 将 UIButton 添加到 LoginViewController 并创建从该按钮到 UISplitViewController 的模式转场
- 从 AppDelegate 的
didFinishLaunchingWithOptions
中移动 UISplitViewController 的样板设置代码到 LoginViewController 的prepareForSegue
这几乎成功了。我说几乎,因为在使用 LoginViewController 启动应用程序并点击按钮并转到 UISplitViewController 后,会出现一个奇怪的错误:在方向更改时显示和隐藏主视图 Controller 不再是动画的。
经过一段时间的努力解决这个问题并且没有真正的解决方案,我认为它与 UISplitViewController 必须是 rootViewController 的奇怪规则有某种联系(在这种情况下它不是,LoginViewController 是)所以我放弃了这个不太完美的解决方案。
<小时/>第二次尝试(来自 UISplitViewController 的模态转场):
- 创建新的 UIViewController 子类(例如 LoginViewController)
- 在 Storyboard中添加新的 View Controller ,并将其连接到 LoginViewController(但这次将 UISplitViewController 保留为初始 View Controller )
- 创建从 UISplitViewController 到 LoginViewController 的模态转场
- 将 UIButton 添加到 LoginViewController 并从该按钮创建展开转场
最后,将此代码添加到 AppDelegate 的 didFinishLaunchingWithOptions
中在设置 UISplitViewController 的样板代码之后:
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
这两个例子都会产生同样的一些不好的事情:
- 控制台输出:
Unbalanced calls to begin/end appearance transitions for <UISplitViewController: 0x7fc8e872fc00>
- UISplitViewController 必须在 LoginViewController 模态连接之前首先显示(我宁愿只显示登录表单,这样用户在登录之前就看不到 UISplitViewController)
- Unwind segue 不会被调用(这完全是另一个错误,我现在不打算讨论这个故事)
解决方案(更新rootViewController)
我发现正确工作的唯一方法是动态更改窗口的 rootViewController:
- 为 LoginViewController 和 UISplitViewController 定义 Storyboard ID, 并向AppDelegate添加某种loggedIn属性。
- 根据此属性,实例化适当的 View Controller ,然后将其设置为 rootViewController。
- 在
didFinishLaunchingWithOptions
中不使用动画进行操作但从 UI 调用时会呈现动画。
以下是 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 中正常工作。
关于iphone - 在 UISplitViewController 和其他 View Controller 之间切换的最佳方式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4213097/