UINavigationController和自动旋转

24

我有一个UIViewController,在shouldAutorotateToInterfaceOrientation:中对UIDeviceOrientationPortrait返回YES,对其他情况返回NO。当该视图位于堆栈的顶部时,我使用pushViewController:animated:来推出一个新的UIViewController。新的控制器在shouldAutorotateToInterfaceOrientation:中对任何内容都返回YES

第一个视图拒绝旋转(正如预期)。一旦推入第二个视图,用户可以旋转设备并且UI会旋转(也是预期的)。如果第二个视图处于横向模式,并且用户按下返回按钮(调用popViewControllerAnimated:),则第一个视图将显示为旋转的(意外!)。

如果用户将设备旋转回竖屏方向,则视图将旋转,然后像之前一样停留在竖直模式中。这样可以运行,但用户体验不佳,除非他们旋转回来。因此,我正在寻找使此视图保持纵向模式的方法。

迄今为止,我发现的唯一解决方法是使用-[UIDevice setOrientation:],这会引发警告(orientation是只读的),但实际上可以工作。这是一个巨大的hack,我想要一个真正的解决方案。为了寻找真正的解决方案,我连接了GDB到照片应用程序(MobileSlideshow.app),并发现它也使用-[UIDevice setOrientation:]。不过,我猜他们有不同的规则。

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


我相信在应用商店中有使用setOrientation的应用程序示例(可能)。其中一个是Tweetie,它可以在编写推文时强制使用横向键盘。也许他们之所以能够这样做是因为这不是默认设置。 - Daniel Dickison
现在已经是2014年了,这个问题仍然存在于iOS 8上。有人找到了一个真正的解决方案吗?而不是看起来像黑客攻击的东西? - Ben G
7个回答

6

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);
}

2
在iOS 7上对我没用。但是,删除窗口的rootViewController并重新放置它确实有效。只是提醒一下,以防有人遇到同样的问题。 - entropy

5

这是一个旧帖子,但由于它还没有解决。我想分享我的解决方案,为那些可能遇到问题的人提供帮助。

目标: 一个UINavigationController和其堆栈中的大多数视图控制器都固定在纵向模式下,除了堆栈中的一个视图控制器被允许旋转到纵向和横向模式。

问题: 直觉上,我通过检查topViewController是否为rotableViewController来设置一个有选择性的shouldAutorotateToInterfaceOrientation。然而,在从rotableViewController返回到横向模式后,navigationcontroller现在显示为横向模式,尽管它不被允许。

解决方法:在viewWillAppear中禁止旋转,并呈现和取消modalViewController时不使用动画。

  1. 将appViewController作为主机viewController添加到窗口中,即根视图控制器;
  2. 在appViewController中添加navigationController,将delegate设置为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];
    }
}

4
iOS 5增加了+[UIViewController attemptRotationToDeviceOrientation]方法,这对我解决了问题。

1
根据文档(“尝试将所有窗口旋转到设备的方向。”),似乎这是用于相反的用例,即您推送一个支持之前未支持的界面方向的新视图控制器。 - Koraktor

1

我找到了一个不错的解决方法。关键是支持UINavigationController所有视图的所有方向。

我的控制器中有两个视图。根视图仅支持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
}

1

我原本想告诉你可能没有办法,但是我想到了一点。如果您使用两个分别控制根视图和禁止旋转的 UINavigationController :一个用于子视图,允许旋转,那么可能会很难正确操作,但您可能可以使其正常工作。您将手动处理到和从根控制器和子控制器的转换。

您必须修补子导航控制器以具有正确的返回按钮。当然,您需要自己处理返回按钮按下事件。您可能需要使用虚拟的 UINavigationBar 来执行从一个导航控制器到另一个导航控制器的动画,以便过渡看起来合理。您还必须对导航控制器之间的“push”转换进行动画处理,这可能需要一些微调才能使其看起来合理。您需要:

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

所有这些都需要很多工作,但如果你非常聪明(而且非常坚韧),你可能会使它正常工作。我很想看到结果!

话虽如此,您最好只使用 setOrientation: 并冒着 App Store 审批的风险;-)


1

现在我们可以谈论3.0操作系统了,再试一次。在3.0下已经解决了两个特殊情况:

  1. 在横屏视图和竖屏模态视图之间进行切换。一个例子是Mail中查看附件的预3.0行为。你可以将其旋转到横屏以查看PDF,并在关闭附件视图时返回纵向视图以查看消息。(现在我们在3.0中拥有横向消息视图,这种行为似乎已经消失了)。

  2. 在视图堆栈中混合方向,并在弹出堆栈时获得正确的方向。一个例子是在YouTube应用程序中,在电影的表格视图和电影视图之间的过渡。

似乎存在一些棘手的美学问题,即所有排列组合的默认转换应该如何呈现。如果您以慢动作查看,则会出现一些奇怪的元素,但这比在自己的代码中费尽心思要好。


我尝试了这个,#1 确实可以正常工作,但是 #2 对我来说不起作用。我使用表视图控制器设置了一个导航控制器项目。根视图无法旋转,但是推送的视图可以旋转。当您弹出第二个视图时,它将保持旋转状态。事实上,导航栏项似乎会混淆,即使在弹出后仍显示第二个视图的标题。 - Daniel Dickison

0

这个烦人的 bug 从 iOS 1 到 iOS 4 已经存在了很长时间。

我认为我们最好的解决方案是复制 this bug 并让苹果知道我们真的希望它能够被修复。我刚刚在 bug ID 8478525 下再次报告了它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接