UITabBarController、MoreNavigationController 和设备旋转的圣杯
Posted
技术标签:
【中文标题】UITabBarController、MoreNavigationController 和设备旋转的圣杯【英文标题】:UITabBarController, MoreNavigationController and the Holy Grail of Device Rotation 【发布时间】:2009-08-13 02:16:28 【问题描述】:更新:请先查看我对这个问题的回答。这似乎是一个错误。已经创建了一个最小的测试用例,并向 Apple 提交了一份报告。 (自 iPhone OS 3.1 起已修复。)
这是“我好近了!”中的一个谜题。部门。
我有一个基于标签栏的 iPhone 应用程序。每个选项卡都有一个 UINavigationController 与通常的嫌疑人(导航栏、表格视图......这反过来又可以导致另一个 VC 等)。
现在,这些较低级别的 VC 之一将用于纵向 和 横向模式。但是有一个问题。我们的景观友好型 VC 的 shouldAutorotateToInterfaceOrientation: 不会被直接调用!怎么办?
这就是我们所做的。在我在自己的文件中实现的标签栏控制器中,我有这个:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
return [self.selectedViewController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
这最终将请求传播到我的横向友好型 VC,它也响应此消息。我所有的其他 VC 都没有实现这个方法,所以他们只是使用默认的纵向。
问题解决了!!!耶!
嗯,不完全是。 :(
当从标签栏控制器的 MoreNavigationController 深处调用我的横向友好型 VC 时,似乎情况不太好。
我决定比较/对比从前四个标签栏UINavigationControllers之一调用的VC和从MoreNavigationController。这将有点超详细,所以请耐心等待。希望逐个播放证明对侦破有用。
当应用加载时,标签栏控制器的 shouldAutorotate... 方法有几个初始调用。在这些早期情况下,selectedViewController 为 nil。但是,我们最终加载完成,选择了初始选项卡项,一切正常。
没错。首先,让我们从前四个标签栏项目中选择一个并深入了解我们的 VC。
我们将选择第三个导航栏项目,这就是第三个导航控制器。我们深入了解支持轮换的 VC。快速检查确认父级确实是我们标签栏的视图控制器列表中的第三个导航控制器。好!
让我们旋转设备。标签栏控制器被要求自动旋转(参见上面的代码)。我们观察到 selectedViewController 也是第三个导航控制器,加上导航控制器的顶部和可见视图控制器都设置为我们信任的支持旋转的 VC。
因此,标签栏控制器会将 shouldAutorotate 消息转发到第三个导航控制器……但我们的旋转友好型 VC 最终会收到消息。 (我在这里没有做任何特别的事情。也许所需的 VC 会收到消息,因为它是顶部和/或可见的 VC?)无论如何,我们旋转到横向,调整大小,一切都很好。 “大获成功!”
现在让我们点击返回按钮并弹出 VC 堆栈,在此过程中保留横向模式。再次查询标签栏控制器。
这里有一点时间。我们导航控制器的 topViewController 仍然是旋转友好的 VC,但 visibleViewController 现在设置为 UISnapshotModalViewController!呵呵。以前从未见过这个……但是Erica Sadun has。看起来它是用于“消失的视图控制器”(在这种情况下当然是真的 - 它正在消失)。
当我继续执行时,可见 VC 保持为快照,但顶部 VC 最终会更改为堆栈中的下一个 VC,因为我的特殊 VC 最终消失了。很公平。
所以这是一切正常的场景。
现在让我们尝试相同的测试,只是这次我们将转到 MoreNavigationController(更多选项卡栏项)并深入到与之前相同的 VC 类。在我的例子中,它恰好是标签栏控制器的 VC 列表中的第 7 个。
我们进入旋转感知 VC 并且......这次它被要求直接旋转!标签栏控制器没有被要求获得旋转权限。嗯。
快速检查父 VC 表明它是一个 MoreNavigationController。好的,有道理。
现在让我们尝试旋转设备。 什么都不会调用。我们的断点都没有被命中。不在我们的 VC 中。不在我们的标签栏控制器中。 (嗯?!?!)
噢-kaaaay。让我们弹出堆栈,回到同一个 VC 并再次尝试旋转。奇怪的。现在我们在标签栏控制器中接到一个请求自动旋转权限的电话。在这里,选择的控制器是我们信任的导航控制器(#7),但这次它的 visibleViewController 和 topViewController 是 SET TO NIL! p>
一旦我们从这里继续,调试器控制台中会出现一条神秘消息:
使用两阶段旋转动画。到 使用更平滑的单级 动画,这个应用程序必须 去除两阶段法 实现。
神秘因为我没有使用两阶段旋转动画!我的源代码中的任何地方都没有 SecondHalf 方法变体。
唉,我的旋转感知 VC 从来没有被告知旋转正在发生(即使旋转确实发生在屏幕上),所以我的观点当然被弄乱了。混乱和悲伤随之而来。 :(
此时我们甚至不会费心弹出堆栈。
我认为 View Controller 文档暗示了可能的问题:
如果您想执行自定义 定向期间的动画 改变,你可以在两个之一中做到这一点 方法。用于改变方向 分两步发生,带有通知 发生在开头、中间、 和旋转的终点。 但是,在 iPhone OS 3.0 中,支持 添加用于执行定向 一步改变。使用一步 方向变化往往更快 比旧的两步过程,是 一般推荐用于任何新的 代码。
我想知道 MoreNavigationController 是否仍在响应两步流程,因此是否会阻止任何使用一步流程的尝试?请注意,如果您回复两步消息,则一步变体将不起作用(同样,根据文档)。我没有回应他们,但我有一个隐秘的怀疑幕后是什么。
事实上,如果我将单步方法注释掉,并尝试响应willAnimateSecondHalfOfRotationFromInterfaceOrientation:duration:,我确实得到了备忘录!但它仍然不能非常干净地从堆栈中弹出(就视觉效果而言)。更奇怪的是:willAnimateFirstHalfOfRotationFromInterfaceOrientation:duration: 不会被调用,即使我试图在 shouldAutorotateToInterfaceOrientation: 中偷偷调用 self(使用 FirstHalf 消息)。它在跟踪期间立即返回,就好像我从未定义过它一样。叹息。
所以这就是逐个播放。
总之,任何人 是否成功地处理了从标签栏控制器的 MoreNavigationController 中调用的 VC 的一步设备旋转?有求知欲的人想知道!
【问题讨论】:
【参考方案1】:Apple 建议不要将 UITabBarController 子类化,因此我找到了一种使用类别来处理自动旋转的简单方法。它不能使用 More... 视图控制器修复您的错误,但我认为这是一种更适合 Apple 的完成工作的方式(并且意味着您的子类化更少)。
为了使我的应用程序中的每个选项卡都能正确自动旋转,我在我的自定义视图控制器中定义了 -shouldAutorotateToInterfaceOrientation:,但它们都在 UITabBarController 内的 UINavigationControllers 内,因此消息不会沿着链发送到我的 VC直到那两个也回应。所以我在我的应用委托文件中添加了以下几行:
添加到 MyAppDelegate.h 的底部
@interface UITabBarController (MyApp)
@end
@interface UINavigationController (MyApp)
@end
添加到 MyAppDelegate.m 的底部
@implementation UITabBarController (MyApp)
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
return YES;
@end
@implementation UINavigationController (MyApp)
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
return YES;
@end
【讨论】:
干得好!类别是一件美丽的事情。同时,我忘了更新这个问题:如果您使用的是 iPhone OS 3.1+,Apple 已经修复了自动旋转问题 (RADAR 7139857)! 我在这里遗漏了什么吗?我尝试了这种分类方法,以及子类化。我可以让我所有的视图控制器旋转或不旋转。我在我的大多数标签栏项目中使用导航控制器,我只希望它们中的一小部分根据它们的 shouldAutoRotateToInterfaceOrientation 方法进行旋转。【参考方案2】:看来我们有一个错误。我创建了一个可重现的最小测试用例,并通过 Apple Bug Reporter (RADAR Problem 7139857) 进行了报告。
更新:从 iPhone OS 3.1 开始,此问题已得到修复。
本质问题是:
视图控制器已经在 导航控制器堆栈不 收到 willAnimateRotationToInterfaceOrientation:duration: 当标签栏控制器的消息 “更多导航控制器”在 使用。
当标签栏项目视图控制器是基本视图控制器时,不会出现此问题。仅当它们是导航控制器并且仅在使用“更多”导航层次结构时。
控制台消息(关于两阶段旋转动画)表明框架内的某些东西(更多导航控制器?)仍在使用两阶段动画,尽管现在从 iPhone OS 3.0 开始建议使用单阶段。
这可以解释为什么在特定情况下不调用 willAnimateRotationToInterfaceOrientation:。根据 Apple 的视图控制器文档,在响应两阶段、前/后半方向消息时不会调用此消息。
【讨论】:
(从 iPhone OS 3.1 开始,Apple 修复了这个错误!)【参考方案3】:Victorb's answer 的略微修改版本,它允许每个视图控制器决定是否允许旋转。
Here as a gist for easier copying & forking
AppDelegate.h
@interface UITabBarController (MyApp)
@end
@interface UINavigationController (MyApp)
@end
AppDelegate.m
@implementation UITabBarController (MyApp)
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
UIViewController *selectedVC = [self selectedViewController];
if ([selectedVC respondsToSelector:@selector(shouldAutorotateToInterfaceOrientation:)])
return [selectedVC shouldAutorotateToInterfaceOrientation:toInterfaceOrientation];
//optimistic return - if you want no rotation, you have to specifically tell me!
return YES;
@end
@implementation UINavigationController (MyApp)
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
UIViewController *visibleVC = [self visibleViewController];
if ([visibleVC respondsToSelector:@selector(shouldAutorotateToInterfaceOrientation:)])
return [visibleVC shouldAutorotateToInterfaceOrientation:toInterfaceOrientation];
//optimistic return - if you want no rotation, you have to specifically tell me!
return YES;
@end
【讨论】:
以上是关于UITabBarController、MoreNavigationController 和设备旋转的圣杯的主要内容,如果未能解决你的问题,请参考以下文章
如何使屏幕仅垂直。 (UITabBarController/Swift)