UITabBarController、MoreNavigationController 和设备轮换的圣杯 [英] UITabBarController, MoreNavigationController and the Holy Grail of Device Rotation

查看:32
本文介绍了UITabBarController、MoreNavigationController 和设备轮换的圣杯的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

更新:首先看我对这个问题的回答.这似乎是一个错误.已经创建了一个最小的测试用例,并且已经向 Apple 提交了一份报告.(从 iPhone OS 3.1 开始修复.)

这是来自我很接近!"的谜题.部门.

我有一个基于标签栏的 iPhone 应用程序.每个选项卡都有一个 UINavigationController,其中包含常见的可疑对象(导航栏、表格视图……这反过来又会导致另一个 VC 等).

现在,这些较低级别的 VC 之一将用于纵向横向模式.但是有一个问题.我们的景观友好型 VC 的 shouldAutorotateToInterfaceOrientation: 不会被立即调用!怎么办?

这就是我们要做的.在我在自己的文件中实现的 Tab Bar Controller 中,我有这个:

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {返回 [self.selectedViewController shouldAutorotateToInterfaceOrientation:interfaceOrientation];}

这最终将请求传播到我的横向友好 VC,它也响应此消息.我所有的其他 VC 都没有实现这种方法,所以他们只是使用默认的纵向.

问题解决了!!!耶!

嗯,不完全. :(

当我的横向友好 VC 从标签栏控制器的 MoreNavigationController 的深处被调用时,事情似乎不太顺利.

我决定比较/对比从前四个标签栏UINavigationControllers中的一个调用的VC和从调用的同一个VC>更多导航控制器.这将是有点超详细,所以请耐心等待.希望逐个游戏证明对侦查事情有用.

当应用加载时,有几个对标签栏控制器的 shouldAutorotate... 方法的初始调用.在这些早期情况下,selectedViewController 为零.然而,我们最终完成了加载,选择了初始标签项,一切都很好.

没错.首先,让我们从前四个选项卡栏项中选择一个并深入到我们的 VC.

我们将选择第三个导航栏项目,这是第三个导航控制器.我们深入研究支持轮换的 VC.快速检查确认父级确实是我们标签栏视图控制器列表中的第三个导航控制器.好!

让我们旋转设备.标签栏控制器被要求自动旋转(见上面的代码).我们观察到 selectedViewController 也是第三个导航控制器,加上导航控制器的顶部和可见视图控制器都设置为我们可信赖的支持旋转的 VC.

因此,标签栏控制器会将 shouldAutorotate 消息转发到第三个导航控制器……但我们的旋转友好 VC 最终会收到消息.(我在这里没有做任何特别的事情.也许想要的 VC 得到了消息,因为它是顶部和/或可见的 VC?)无论如何,我们旋转到横向,调整大小,一切都很好.大获成功!"

现在让我们点击后退按钮并弹出 VC 堆栈,在此过程中保留横向模式.再次查询标签栏控制器.

是时候在这里稍作休息了.我们导航控制器的 topViewController 仍然是旋转友好的 VC,但是 visibleViewController 现在设置为 UISnapshotModalViewController!呵呵.以前从未见过这个……但是 Erica Sadun 有.看起来它是为了消失的视图控制器"(在这种情况下肯定是正确的 - 它正在消失).

当我继续执行时,可见的 VC 仍然是 Snapshot,但顶部 VC 最终会更改为堆栈中的下一个 VC,因为我的特殊 VC 最终消失了.很公平.

所以这就是一切正常的场景.

现在让我们尝试相同的测试,只是这次我们将转到 MoreNavigationController(更多选项卡栏项)并深入到与以前相同的 VC 类.就我而言,它恰好是标签栏控制器 VC 列表中的第 7 个.

我们进入了旋转感知 VC 并且......这次它被要求直接旋转!标签栏控制器根本不需要 获得旋转许可.嗯.

对父 VC 的快速检查显示它是一个 MoreNavigationController.好的,说得通.

现在让我们尝试旋转设备.没有被调用.我们的断点都没有被击中.不在我们的 VC 中.不在我们的标签栏控制器中.(嗯?!?!)

O-kaaaay.让我们弹出堆栈,回到同一个 VC 并再次尝试旋转.奇怪的.现在我们在 Tab Bar Controller 中接到一个电话,要求获得自动旋转权限.在这里,选定的控制器是我们可信赖的导航控制器 (#7),但这次它的 visibleViewControllertopViewController SET TO NIL!>

一旦我们从这里继续,调试器控制台中会出现一条神秘消息:

<块引用>

使用两阶段旋转动画.到使用更平滑的单级动画,这个应用程序必须删除两阶段方法实现.

神秘,因为我没有使用两阶段旋转动画!我的源代码中没有任何SecondHalf 方法变体.

唉,我的旋转感知 VC 从来没有被告知旋转正在发生(即使旋转确实发生在屏幕上),所以当然我的观点因此被弄脏了.混乱和悲伤随之而来.:(

此时我们甚至不会去弹出堆栈.

我认为视图控制器文档暗示了可能存在的问题:

<块引用>

如果您想执行自定义定向期间的动画改变,你可以在两个之一中这样做方法.方向改变用于分两步进行,带有通知发生在开始,中间,和旋转的终点.但是,在 iPhone OS 3.0 中,支持是添加用于执行定向一步改变.使用一步方向变化往往更快比旧的两步过程通常推荐用于任何新的代码.

我想知道 MoreNavigationController 是否仍在响应两步过程,因此是否会阻碍使用一步过程的任何尝试?请注意,如果您响应两步消息,一步变体将不起作用(同样,根据文档).我没有回应他们,但我偷偷怀疑幕后有什么东西.

事实上,如果我注释掉单步方法,并尝试响应willAnimateSecondHalfOfRotationFromInterfaceOrientation:duration:,我得到备忘录!但它仍然没有非常干净地从堆栈中弹出(就视觉效果而言).更奇怪的是:willAnimateFirstHalfOfRotationFromInterfaceOrientation:duration: 没有被调用,即使我试图在 shouldAutorotateToInterfaceOrientation: 中偷偷调用 self(使用 FirstHalf 消息).它在跟踪过程中立即返回,就好像我从未定义过它一样.叹气.

这就是逐场比赛.

总而言之,有没有任何人成功地为从 Tab Bar Controller 的 MoreNavigationController 中调用的 VC 处理了一步设备旋转?好奇心想知道!

解决方案

Apple 建议不要对 UITabBarController 进行子类化,因此我找到了一种使用类别来处理自动旋转的简单方法.它不会通过 More... 视图控制器修复您的错误,但我认为这是一种更适合 Apple 完成工作的方式(并且意味着对您来说更少的子类化).

为了使我的应用程序中的每个选项卡都正确地自动旋转,我在我的自定义视图控制器中定义了 -shouldAutorotateToInterfaceOrientation:,但它们都在 UITabBarController 中的 UINavigationControllers 内,因此消息不会沿着链发送到我的 VC直到那两人也反应过来.所以我在我的应用程序委托文件中添加了以下几行:

<块引用>

添加到 MyAppDelegate.h 底部

@interface UITabBarController (MyApp)@结尾@interface UINavigationController (MyApp)@结尾

<块引用>

添加到 MyAppDelegate.m 的底部

@implementation UITabBarController (MyApp)- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {返回是;}@结尾@implementation UINavigationController (MyApp)- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {返回是;}@结尾

UPDATE: See my answer to this question first. This appears to be a bug. A minimal test case has been created and a report has been filed with Apple. (Fixed as of iPhone OS 3.1.)

Here's a puzzler from the "I'm so close!" department.

I have a Tab Bar-based iPhone app. Each tab features a UINavigationController with the usual suspects (nav bar, table view ... which in turn can lead to another VC, etc.).

Now, one of those lower-level VCs is to be used in portait and landscape modes. But there's a problem. Our landscape-friendly VC's shouldAutorotateToInterfaceOrientation: won't get called out-of-the-box! What to do?

Here's what we do. In my Tab Bar Controller, which I have implemented in its own file, I have this:

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
     return [self.selectedViewController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
}

This ends up propagating the request to my landscape-friendly VC, which also responds to this message. All my other VCs don't implement this method, so they simply go with the default portrait orientation.

Problem solved!!! Yay!

Well, not quite. :(

Seems like things don't go so well when my landscape-friendly VC is invoked from within the depths of the tab bar controller's MoreNavigationController.

I decided to compare/contrast between a VC called from within one of the first four tab bar UINavigationControllers ... and that same VC called from within the MoreNavigationController. This is going to be a bit ultra-detailed, so bear with me. Hopefully the play by play proves useful for sleuthing things out.

When the app loads, there are several initial calls to the tab bar controller's shouldAutorotate... method. In these early cases, selectedViewController is nil. However, we eventually finish loading, the initial tab item is selected, and all is well.

Right. First, let's pick one of the first four tab bar items and drill down to our VC.

We'll pick third nav bar item, so that's the third nav controller. We drill down to our VC that supports rotation. A quick inspection confirms that the parent is indeed the third nav controller from our tab bar's view controller list. Good!

Let's rotate the device. The tab bar controller is asked to autorotate (see the above code). We observe that selectedViewController is also that third nav controller, plus the nav controller's top and visible view controllers are both set to our trusty VC that supports rotation.

Thus, the tab bar controller will forward the shouldAutorotate message over to the third nav controller ... but our rotation-friendly VC ultimately gets the message. (I'm not doing anything special here. Maybe the desired VC gets the message because it's the top and/or visible VC?) In any event, we rotate to landscape, things are resized, and all is well. "Great Success!"

Now let's hit that back button and pop the VC stack, leaving landscape mode in the process. The tab bar controller is queried again.

Time for a little aside here. The topViewController for our nav controller is still that rotation-friendly VC, but visibleViewController is now set to UISnapshotModalViewController! Heh. Never saw this one before ... but Erica Sadun has. Looks like it's for "disappearing view controllers" (which in this case is certainly true - it's disappearing alright).

As I keep stepping through, the visible VC stays as Snapshot, but the top VC eventually changes to the next VC on the stack, since my special VC is eventually gone. Fair enough.

So that's the scenario where everything works well.

Now let's try the same test, only this time we're going to go to the MoreNavigationController (the More tab bar item) and drill down to the same VC class as before. In my case it happens to be the 7th one in the tab bar controller's VC list.

We enter the rotation-aware VC and ... this time it gets asked to rotate directly! The Tab Bar Controller is not asked for permission to rotate at all. Hmm.

A quick check of the parent VC shows it is a MoreNavigationController. OK, that makes sense.

Now let's try to rotate the device. NOTHING GETS CALLED. None of our breakpoints get hit. Not in our VC. Not in our tab bar controller. (Huh?!?!)

O-kaaaay. Let's pop the stack, go back into the same VC and try to rotate again. Weird. NOW we get a call in the Tab Bar Controller asking for autorotation permission. Here, the selected Controller is our trusty Nav controller (#7), but this time its visibleViewController and topViewController are SET TO NIL!

Once we continue from here, a mysterious message appears in the debugger console:

Using two-stage rotation animation. To use the smoother single-stage animation, this application must remove two-stage method implementations.

Mysterious because I'm not using two-stage rotation animation! No SecondHalf method variants are in play anywhere in my source code.

Alas, my rotation-aware VC is never told that rotation is occurring (even though rotation does occur on-screen), so of course my view is all fouled up as a result. Mayhem and sadness ensue. :(

We won't even bother popping the stack at this point.

I think the View Controller doc hints at the possible problem:

If you want to perform custom animations during an orientation change, you can do so in one of two ways. Orientation changes used to occur in two steps, with notifications occurring at the beginning, middle, and end points of the rotation. However, in iPhone OS 3.0, support was added for performing orientation changes in one step. Using a one-step orientation change tends to be faster than the older two-step process and is generally recommended for any new code.

I wonder if MoreNavigationController is still responding to the two-step process, and is thus tripping up any attempts to use the one-step process? Note that, if you respond to the two-step messages, the one-step variant will not work (again, per the docs). I'm not responding to them, but I have a sneaking suspicion something behind-the-scenes IS.

In fact, if I comment out the single-step method, and try to respond to willAnimateSecondHalfOfRotationFromInterfaceOrientation:duration:, I do get the memo! But it still doesn't pop off the stack very cleanly (in terms of visuals). Even stranger: willAnimateFirstHalfOfRotationFromInterfaceOrientation:duration: is NOT called, even when I tried to sneak a call to self (using the FirstHalf message) in shouldAutorotateToInterfaceOrientation:. It returns immediately during a trace, as if I never even defined it. Sigh.

So that's the play-by-play.

In summary, has anyone successfully handled one-step device rotation for a VC invoked from within a Tab Bar Controller's MoreNavigationController? Inquiring minds want to know!

解决方案

Apple advises against subclassing UITabBarController, so I found an easy way to handle autorotation using categories instead. It doesn't fix your bug with the More... view controllers, but I think it's a more Apple-friendly way of getting the job done (and means less subclassing for you).

To make every tab in my application autorotate properly, I've defined -shouldAutorotateToInterfaceOrientation: in my custom view controllers, but they are all inside UINavigationControllers within a UITabBarController, so the message won't get sent down the chain to my VC until those two also respond. So I added the following lines to my app delegate files:

Added to the bottom of MyAppDelegate.h

@interface UITabBarController (MyApp)
@end

@interface UINavigationController (MyApp)
@end

Added to the bottom of MyAppDelegate.m

@implementation UITabBarController (MyApp) 
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
    return YES;
}
@end

@implementation UINavigationController (MyApp) 
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
    return YES;
}
@end

这篇关于UITabBarController、MoreNavigationController 和设备轮换的圣杯的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆