在iOS 8中,处理屏幕旋转的“正确”方式是什么?

107

请问有人能告诉我在iOS 8中处理纵向和横向界面方向的"正确"或"最佳"方法是什么吗?我想用于此目的的所有函数似乎都已被弃用,我的研究也没有找到明确而优雅的替代方法。难道我真的应该查看宽度和高度来确定我们处于纵向还是横向模式吗?

例如,在我的视图控制器中,我应该如何实现以下伪代码?

if we are rotating from portrait to landscape then
  do portrait things
else if we are rotating from landscape to portrait then
  do landscape things

3
阅读 UIViewController 的文档。查看标题为“处理视图旋转”的部分。它会解释你应该做什么。 - rmaddy
它们被弃用了,这是一个提示。你必须使用其他东西……那个其他东西应该是AutoLayout和Size Classes :-) - Aaron
5个回答

268

苹果建议使用尺寸类作为屏幕可用空间的粗略度量,以便您的用户界面可以显著改变其布局和外观。请注意,无论是在纵向还是横向,在 iPad 上的尺寸类都相同(宽度正常,高度正常)。这意味着您的用户界面在两个方向上应该是相似的。

然而,在 iPad 上从纵向到横向的更改足够显着,即使尺寸类没有改变,您可能仍需要对用户界面进行一些较小的调整。由于UIViewController上与界面方向相关的方法已被弃用,因此苹果现在建议实现以下新方法以替代:

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator
{
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];

    // Code here will execute before the rotation begins.
    // Equivalent to placing it in the deprecated method -[willRotateToInterfaceOrientation:duration:]

    [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {

        // Place code here to perform animations during the rotation.
        // You can pass nil or leave this block empty if not necessary.

    } completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {

        // Code here will execute after the rotation has finished.
        // Equivalent to placing it in the deprecated method -[didRotateFromInterfaceOrientation:]

    }];
}

太好了!现在你可以在旋转开始前和结束后获取回调。但是,如何确定旋转是横向还是纵向呢?

苹果建议将旋转视为父视图大小的更改。换句话说,在iPad从纵向旋转到横向时,您可以将其视为根级视图仅将其bounds.size{768, 1024}更改为{1024, 768}。有了这个认识,您应该使用上面的viewWillTransitionToSize:withTransitionCoordinator:方法中传递的size来确定您正在旋转到横向还是纵向。

如果您想要一个更无缝地将遗留代码迁移到新的iOS 8操作方式的方法,请考虑在UIView上使用此简单类别,它可以根据其大小确定视图是“纵向”还是“横向”。

回顾一下:

  1. 您应该使用大小类别来确定何时显示根本不同的UI(例如“类似iPhone”的UI与“类似iPad”的UI)
  2. 如果需要在大小类别不会更改但容器(父视图)大小会更改时进行对UI进行小的调整,例如当iPad旋转时,请使用UIViewController中的viewWillTransitionToSize:withTransitionCoordinator:回调。
  3. 应用程序中的每个视图都应仅根据其被分配布局的空间做出布局决策。让视图的自然层次结构将此信息级联向下。
  4. 同样,不要使用statusBarOrientation来确定是否为“纵向”或“横向”布局视图,因为它基本上是设备级属性。状态栏方向仅应由处理UIWindow之类实际位于应用程序最顶层的代码使用。

5
非常好的回答!顺便说一下,你在AL方面的YouTube视频非常有信息量。感谢您的分享。来看看吧!https://www.youtube.com/watch?v=taWaW2GzfCI - SmileBot
2
我发现的唯一问题是有时候你想知道设备的方向,但这个功能不能提供这个信息。在 iPad 上它被称为状态栏之前或设备方向值更改之前被调用。你无法判断用户是否将设备竖直或倒置。同时也很难进行测试,因为模拟 UIViewControllerTransitionCoordinator 是一场噩梦 :-( - Darrarski
@Darrarski,你找到了应对这些问题的优雅解决方案了吗? - Xvolks
那么viewdidlayoutsubviews在哪里呢?我认为viewdidlayoutsubviews用于捕获由旋转引起的边界更改。你能详细说明一下吗? - mfaani
1
如果我们不使用statusbarorientation,如何区分横向左侧和横向右侧?我需要这种区别。此外,还存在其他问题,如此处所述-https://stackoverflow.com/questions/53364498/uiviewcontroller-selective-autorotation-with-size-classes - Deepak Sharma

18

根据smileyborg非常详细(且被接受)的回答,这是一个适用于Swift 3的适应版:

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)
    coordinator.animate(alongsideTransition: nil, completion: {
        _ in
        self.collectionView.collectionViewLayout.invalidateLayout()
    })        
}

UICollectionViewDelegateFlowLayout 实现中,

public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    // retrieve the updated bounds
    let itemWidth = collectionView.bounds.width
    let itemHeight = collectionView.bounds.height
    // do whatever you need to do to adapt to the new size
}

11

我只是使用通知中心:

添加一个方向变量(将在最后解释)

//Above viewdidload
var orientations:UIInterfaceOrientation = UIApplication.sharedApplication().statusBarOrientation

视图出现时添加通知

override func viewDidAppear(animated: Bool) {
    NSNotificationCenter.defaultCenter().addObserver(self, selector: "orientationChanged:", name: UIDeviceOrientationDidChangeNotification, object: nil)
}

当视图消失时移除通知

override func viewWillDisappear(animated: Bool) {
        NSNotificationCenter.defaultCenter().removeObserver(self, name: UIDeviceOrientationDidChangeNotification, object: nil) 
}

当通知被触发时,获取当前方向。

func orientationChanged (notification: NSNotification) {
    adjustViewsForOrientation(UIApplication.sharedApplication().statusBarOrientation)
}

检测屏幕方向(纵向或横向),并处理相关事件

func adjustViewsForOrientation(orientation: UIInterfaceOrientation) {
    if (orientation == UIInterfaceOrientation.Portrait || orientation == UIInterfaceOrientation.PortraitUpsideDown)
    {
        if(orientation != orientations) {
            println("Portrait")
            //Do Rotation stuff here
            orientations = orientation
        }
    }
    else if (orientation == UIInterfaceOrientation.LandscapeLeft || orientation == UIInterfaceOrientation.LandscapeRight)
    {
       if(orientation != orientations) {
            println("Landscape")
            //Do Rotation stuff here
            orientations = orientation
        }
    }
}

我添加方向变量的原因是因为在物理设备上进行测试时,每次设备的微小移动都会触发方向通知,而不仅仅是旋转时才会触发。添加变量和if语句只会在它切换到相反方向时调用代码。


11
这并非苹果推荐的方法,因为这意味着应用中的每个视图都会根据设备的方向来决定如何显示,而不是考虑所给予的空间。 - smileyborg
2
苹果在8.0版本中也没有考虑硬件因素。如果AVPreview图层在视图控制器上呈现,告诉它不需要担心方向问题。这种方法在8.0版本中无效,但在8.2版本中已经修复。 - Nick Turner
应该调用 viewDidAppearviewWillDisappearsuper - Andrew Bogaevskyi
注意:UIDeviceOrientation!= UIInterfaceOrientation。在我的实验中,statusBarOrientation不可靠。 - nnrales

2
从UI的角度来看,我认为使用Size Classes是苹果推荐的处理不同方向、大小和比例接口的方法。
请参见此处的特征描述接口的尺寸类别和比例部分: https://developer.apple.com/library/ios/releasenotes/General/WhatsNewIniOS/Articles/iOS8.html

"iOS 8添加了新功能,使处理屏幕尺寸和方向变得更加灵活多变。"

这篇文章也不错: https://carpeaqua.com/thinking-in-terms-of-ios-8-size-classes/ 编辑 更新链接: https://carpeaqua.com/2014/06/14/thinking-in-terms-of-ios-8-size-classes/(感谢Koen)

是的,看起来他的整个网站目前都无法访问。 - Aaron
这篇文章确实值得一读,以下是正确的地址:https://carpeaqua.com/thinking-in-terms-of-ios-8-size-classes/ - uem
但如果我们不是在讨论大小类别和UI,而是关于AVFoundation,例如,在录制视频时,有些情况下,您必须了解视图控制器的方向以设置正确的元数据。这简直是不可能的。“多才多艺”。 - Julian F. Weinert
这不是原帖所询问/谈论的内容。 - Aaron
1
更新链接:https://carpeaqua.com/2014/06/14/thinking-in-terms-of-ios-8-size-classes/ - koen

0

这里有一个解决方法:

    private var _viewSize: CGSize = .zero
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
    
        if _viewSize.equalTo(.zero) {
            _viewSize = self.view.frame.size
        } else if _viewSize.equalTo(self.view.frame.size) == false {
            onViewSizeChanged(from: _viewSize, to: self.view.frame.size)
            _viewSize = self.view.frame.size
        }
    }

    func onViewSizeChanged(from: CGSize, to: CGSize) {
        // self.view changed size after rotation
        // your code... 
    }

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