UINavigationController 和自动旋转

发布于 2024-07-22 22:12:30 字数 945 浏览 7 评论 0原文

我有一个 UIViewController,它在 shouldAutorotateToInterfaceOrientation: 中为 UIDeviceOrientationPortrait 返回 YES ,为其他所有内容返回 NO 。 将该视图置于堆栈顶部后,我使用 pushViewController:animated: 推送新的 UIViewController。 对于 shouldAutorotateToInterfaceOrientation: 中的任何内容,新控制器都会返回 YES

第一个视图拒绝旋转(如预期)。 推送第二个视图后,用户可以旋转设备,并且 UI 将旋转(也如预期)。 如果第二个视图处于横向模式并且用户按下后退按钮(调用 popViewControllerAnimated:),第一个视图将出现旋转(意外!)。

如果用户将设备旋转回纵向,视图将旋转,然后像以前一样停留在纵向模式。 这可行,但对于用户来说很难看,直到他们旋转回来。 所以我正在寻找一种方法使该视图保持纵向模式。

到目前为止,我发现的唯一解决方法是使用 -[UIDevice setOrientation:],它会抛出警告(orientation 是只读的),但可以工作,因为它实际上是定义的。 这是一个巨大的黑客攻击,我想要一个真正的解决方案。 为了寻找真正的解决方案,我将 GDB 连接到照片应用程序 (MobileSlideshow.app),并发现它也使用 -[UIDevice setOrientation:]。 作为一个内部应用程序,尽管我猜他们有不同的规则。

是否有正确的方法来实现预期的自旋转行为?

I have a UIViewController that returns YES in shouldAutorotateToInterfaceOrientation: for UIDeviceOrientationPortrait and NO for everything else. With that view on the top of the stack, I use pushViewController:animated: to push a new UIViewController. The new controller returns YES for anything in shouldAutorotateToInterfaceOrientation:.

The first view refuses to rotate (as expected). Once the second view is pushed, the user can rotate the device and the UI will rotate (also as expected). If the second view is in landscape mode and the user presses the back button (which calls popViewControllerAnimated:), the first view will appear rotated (unexpected!).

If the user rotates the device back to portrait orientation, the view will rotate and then be stuck in portrait mode as before. This works but it's ugly for the user until they rotate back. So I'm in search of a way to make this view stay in portrait mode.

The only workaround I have found so far is to use -[UIDevice setOrientation:], which throws a warning (orientation is read-only) but works since it is actually defined. This is a huge hack and I'd like a real solution. In search of a real solution I attached GDB to the Photos application (MobileSlideshow.app) and discovered that it too uses -[UIDevice setOrientation:]. Being an internal application though I guess they have different rules.

Is there a correct way to achieve the expected autorotation behavior?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

森林迷了鹿 2024-07-29 22:12:30

UIDevice setOrientation 的合法替代方案如下:

UIWindow* window = UIApplication.sharedApplication.keyWindow;
UIView* view = [window.subviews objectAtIndex:0];
[view removeFromSuperview];
[window addSubview:view];

这强制当前视图控制器评估其方向,调用 shouldAutorotateToInterfaceOrientation 并切换远离任何禁止的方向。

例如,以下代码将用于支持所有方向但其父级仅支持横向的视图控制器:

- (void)popBack
{
    [self.navigationController popToRootViewControllerAnimated:YES]; 
}

- (IBAction)backPressed:(id)sender
{   
    portraitOrientationPermitted = NO;

    // Force the framework to re-evaluate the interface orientation.
    UIWindow* window = UIApplication.sharedApplication.keyWindow;
    UIView* view = [window.subviews objectAtIndex:0];
    [view removeFromSuperview];
    [window addSubview:view];

    [self performSelector:@selector(popBack) withObject:nil afterDelay:0.8];

    portraitOrientationPermitted = YES;
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return portraitOrientationPermitted || 
        UIInterfaceOrientationIsLandscape(interfaceOrientation);
}

A legal alternative to UIDevice setOrientation is as follows:

UIWindow* window = UIApplication.sharedApplication.keyWindow;
UIView* view = [window.subviews objectAtIndex:0];
[view removeFromSuperview];
[window addSubview:view];

This forces the current view controller to evaluate its orientation, calling shouldAutorotateToInterfaceOrientation and switching away from any prohibited orientations.

E.g. the following code would be used in a view controller that supports all orientations but whose parent only supports landscape:

- (void)popBack
{
    [self.navigationController popToRootViewControllerAnimated:YES]; 
}

- (IBAction)backPressed:(id)sender
{   
    portraitOrientationPermitted = NO;

    // Force the framework to re-evaluate the interface orientation.
    UIWindow* window = UIApplication.sharedApplication.keyWindow;
    UIView* view = [window.subviews objectAtIndex:0];
    [view removeFromSuperview];
    [window addSubview:view];

    [self performSelector:@selector(popBack) withObject:nil afterDelay:0.8];

    portraitOrientationPermitted = YES;
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return portraitOrientationPermitted || 
        UIInterfaceOrientationIsLandscape(interfaceOrientation);
}
别念他 2024-07-29 22:12:30

这是一个旧帖子,但至今尚未解决。 我想为其他可能感到头疼的人分享我的解决方案。

客观的:
UINavigationController 及其堆栈中的大多数视图控制器固定为纵向,但堆栈中的一个视图控制器允许旋转为纵向和横向。

问题:
直观地,我通过检查 topViewController 是否是 rotableViewController 来设置选择性的 shouldAutorotateToInterfaceOrientation。 然而,在横向模式下从 rotableViewController 弹出后,导航控制器现在以横向模式显示,尽管这是不允许的。

解决方案:杀手锏是禁止 viewWillAppear 处的旋转并呈现 & 。 关闭没有动画的 modalViewController。

  1. 一个 appViewController 作为宿主添加到窗口中
    viewController,即比 RootViewController 更根;
  2. 将 navigationController 添加到 appViewController 中,其中
    委托设置为appViewController;
  3. 在AppViewController中

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    if (interfaceOrientation == UIInterfaceOrientationPortrait) return YES;
    return canRotate;
}

- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    [viewController viewDidAppear:animated];
    canRotate = ([navigationController.topViewController isKindOfClass:[MyRotatable class]]);
}

- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    [viewController viewWillAppear:animated];
    if (![navigationController.topViewController isKindOfClass:[MyRotatable class]]) {
        canRotate = NO;
        UIViewController * blanck = [[UIViewController alloc] initWithNibName:nil bundle:nil];
        [self presentModalViewController:blanck animated:NO];
        [self dismissModalViewControllerAnimated:NO];
        [blanck release];
    }
}

It has been a old post but since it hasn't been solved. I would like to share my solution, for any others who might be in a headache.

Objective:
A UINavigationController and most of viewcontrollers in its stack fixed at portrait, except for one viewcontroller in the stack being allowed to rotate to both portrait and landscape.

Problem:
intuitively I set an selective shouldAutorotateToInterfaceOrientation by checking if the topViewController is the rotableViewController. However after poping back from the rotableViewController at the landscape mode, the navigationcontroller is now shown in the landscape mode although it is not allowed.

Solution:The killer is to disallow the rotation at viewWillAppear and present & dismiss a modalViewController without animation.

  1. An appViewController is added to the window as the host
    viewController, i.e. rooter than the RootViewController;
  2. A navigationController is added to the appViewController, with
    delegate set to appViewController;
  3. In the AppViewController

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    if (interfaceOrientation == UIInterfaceOrientationPortrait) return YES;
    return canRotate;
}

- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    [viewController viewDidAppear:animated];
    canRotate = ([navigationController.topViewController isKindOfClass:[MyRotatable class]]);
}

- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    [viewController viewWillAppear:animated];
    if (![navigationController.topViewController isKindOfClass:[MyRotatable class]]) {
        canRotate = NO;
        UIViewController * blanck = [[UIViewController alloc] initWithNibName:nil bundle:nil];
        [self presentModalViewController:blanck animated:NO];
        [self dismissModalViewControllerAnimated:NO];
        [blanck release];
    }
}
萝莉病 2024-07-29 22:12:30

我正想告诉你,可能没有办法,但后来我有了一个想法。 这很难做到正确,但如果您使用两个单独的 UINavigationController ,您也许能够使其工作:一个控制根视图并禁止旋转,另一个用于允许子视图它。 您将手动处理根控制器和子控制器之间的转换。

您必须修补子导航控制器才能拥有正确的后退按钮。 当然,您必须自己按下后退按钮。 您可能必须使用虚拟 UINavigationBar 来执行从一个导航控制器到下一个导航控制器的动画,以便过渡看起来正确。 您还必须为导航控制器之间的“推送”转换设置动画,这可能需要进行一些调整才能使其看起来正确。 您必须:

  1. 配置一个虚拟导航栏以与传出的导航控制器完全匹配,并将其直接放置在导航控制器栏的顶部。 (您可以复制当前视图控制器的 UINavigationItem 的配置并将其推上)
  2. 将新的导航控制器放在屏幕外的右边缘,
  3. 以动画方式将新旧控制器的框架从右到右移动left
  4. 为传入的视图控制器创建 UINavigationItem 的副本,并将其推送到虚拟导航栏上。
  5. 当动画完成时,从视图中删除虚拟 UINavigationBar 以及输出导航控制器。

所有这些都需要大量工作,但如果您非常聪明(并且非常顽强),您也许能够让它发挥作用。 我很想看看结果!

也就是说,您可能最好只使用 setOrientation: 并在 App Store 审批流程中抓住机会;-)

I was about to tell you that there was probably no way, but then I had a thought. It would be difficult to get right, but you might be able to make it work if you used two separate UINavigationControllers: one that controls the root view and prohibits rotation, and one for the child views that allows it. You would manually handle the transition to and from the root controller and the child controller.

You'd have to patch up the child navigation controller to have the correct back button. And, of course, you'd have to handle the back button press yourself. You would probably have to use a dummy UINavigationBar to do the animation from one navigation controller to the next so that the transition will look right. You would also have to animate the "push" transition between the navigation controllers, as well, which might take a bit of tweaking to get it to look right. You would have to:

  1. Configure a dummy navigation bar to exactly match the outgoing navigation controller's and place it directly on top of the navigation controller's bar. (You could copy the configuration of the current view controller's UINavigationItem and push it on)
  2. Place the new navigation controller off-screen at the right edge
  3. Animate the movement of the new and old controllers' frames from right to left
  4. Create a copy of the UINavigationItem for the incoming view controller and push it on the dummy navigation bar
  5. When the animation completes, remove the dummy UINavigationBar from the view, and also the outgoing navigation controller.

All of this is a lot of work, but if you're very clever (and very tenacious), you might be able to get it to work. I'd love to see the result!

That said, you might be better off just using setOrientation: and taking your chances with the App Store approval process ;-)

思念满溢 2024-07-29 22:12:30

iOS 5添加了+[UIViewController attemptsRotationToDeviceOrientation],这为我解决了这个问题。

iOS 5 adds +[UIViewController attemptRotationToDeviceOrientation], which solves the problem for me.

a√萤火虫的光℡ 2024-07-29 22:12:30

我找到了解决这个问题的一个很好的方法。 线索是支持 UINavigationController所有视图的所有方向。

我在控制器中有 2 个视图。 根视图只支持 LandscapeRight,其次支持 LandscapeRightPortrait

第二个视图 shouldAutorotateToInterfaceOrientation 方法看起来像往常一样:

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return (interfaceOrientation == UIInterfaceOrientationLandscapeRight) ||
           (interfaceOrientation == UIInterfaceOrientationPortrait);
}

解决方法本身包含在根视图源代码中
现在根视图按照代码旋转,但用户看不到它。

//auxiliary function
-(void) fixOrientation:(UIInterfaceOrientation)orientation
{
    if (orientation == UIInterfaceOrientationPortrait)
        self.view.transform = CGAffineTransformMakeRotation(M_PI_2);
    else if (orientation == UIInterfaceOrientationLandscapeRight)
        self.view.transform = CGAffineTransformMakeRotation(0);
}

-(void) viewWillAppear:(BOOL)animated
{
    [self fixOrientation:[[UIApplication sharedApplication] statusBarOrientation]];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    [self fixOrientation:interfaceOrientation];
    //notice, that both orientations are accepted
    return (interfaceOrientation == UIInterfaceOrientationLandscapeRight) ||
           (interfaceOrientation == UIInterfaceOrientationPortrait);
}

//these two functions helps to avoid blinking
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    [UIView setAnimationsEnabled:NO]; // disable animations temporarily

}

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
    [UIView setAnimationsEnabled:YES]; // rotation finished, re-enable them
}

I found a nice workaround for this problem. The clue is to support all orientations for all views in UINavigationController.

I've got 2 views in controller. Root view is to support only LandscapeRight, and second supports both LandscapeRight and Portrait.

Second view shouldAutorotateToInterfaceOrientation method looks as usual:

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return (interfaceOrientation == UIInterfaceOrientationLandscapeRight) ||
           (interfaceOrientation == UIInterfaceOrientationPortrait);
}

The workaround itself is contained in Root view source
Now the root view rotates in terms of code, but the user cant see it.

//auxiliary function
-(void) fixOrientation:(UIInterfaceOrientation)orientation
{
    if (orientation == UIInterfaceOrientationPortrait)
        self.view.transform = CGAffineTransformMakeRotation(M_PI_2);
    else if (orientation == UIInterfaceOrientationLandscapeRight)
        self.view.transform = CGAffineTransformMakeRotation(0);
}

-(void) viewWillAppear:(BOOL)animated
{
    [self fixOrientation:[[UIApplication sharedApplication] statusBarOrientation]];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    [self fixOrientation:interfaceOrientation];
    //notice, that both orientations are accepted
    return (interfaceOrientation == UIInterfaceOrientationLandscapeRight) ||
           (interfaceOrientation == UIInterfaceOrientationPortrait);
}

//these two functions helps to avoid blinking
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    [UIView setAnimationsEnabled:NO]; // disable animations temporarily

}

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
    [UIView setAnimationsEnabled:YES]; // rotation finished, re-enable them
}
我为君王 2024-07-29 22:12:30

使用 3.0 操作系统再试一次(现在我们可以讨论它了)。 3.0 下已经解决了这种情况的两个特殊情况:

  1. 在横向视图上呈现纵向模态视图,反之亦然。 一个例子是 Mail 3.0 之前查看附件的行为。 您可以将 PDF 旋转为横向视图,并在关闭附件视图时恢复邮件的纵向视图。 (现在我们在 3.0 中有了横向消息视图,这种行为似乎已经消失了)。

  2. 在视图堆栈中混合方向并在弹出堆栈时恢复正确的方向。 例如,电影的表格视图和 YouTube 应用中的电影视图之间的转换。

所有排列的默认转换应该是什么样子似乎存在一些棘手的美学问题。 如果你以慢动作观看,会有一些奇怪的元素,但它比在你自己的代码中向后弯腰要好。

Try it again with the 3.0 OS (now that we can talk about it). Two special cases of this have been worked out under 3.0:

  1. Presenting a portrait modal view over a landscape view and vice-versa. An example is the pre-3.0 behaviour in Mail for viewing attachments. You could rotate to landscape for a PDF and get back the portrait view for the message when you dismissed the attachment view. (Now that we have landscape message view in 3.0 this behaviour seems to be gone).

  2. Mixing orientations within a view stack and getting the correct orientation back when you pop the stack. An example is the transition between the tableview of movies and the movie view in the YouTube app.

There seem to have been some thorny aesthetic problems of how the default transitions for all the permutations should look. There are some weird elements if you view in slo-mo, but it beats bending over backwards in your own code.

鸢与 2024-07-29 22:12:30

所以这个恼人的 bug 从 iOS 1 到 iOS 4 已经有很长的路要走。

我相信最好的解决方案是复制 这个 bug 并让 Apple 知道我们确实希望修复它。 我刚刚再次报告了该问题,错误 ID 为 8478525。

So this annoying bug comes a really long way from iOS 1 to iOS 4.

I believe the best solution we have it duplicate this bug and let Apple know we really want it fixed. I've just reported it again under the bug ID 8478525.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文