【问题标题】:Force View Controller Orientation in iOS 9在 iOS 9 中强制视图控制器方向
【发布时间】:2016-06-17 16:14:39
【问题描述】:

我正在开发一个摄影应用程序,该应用程序允许以纵向或横向拍摄照片。由于项目的需要,我不能让设备方向自动旋转,但需要支持旋转。

当使用以下定位方法时:

override func shouldAutorotate() -> Bool {
    return true
}

override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
    if self.orientation == .Landscape {
        return UIInterfaceOrientationMask.LandscapeRight
    } else {
        return UIInterfaceOrientationMask.Portrait
    }
}

override func preferredInterfaceOrientationForPresentation() -> UIInterfaceOrientation {
    if self.orientation == .Landscape {
        return UIInterfaceOrientation.LandscapeRight
    } else {
        return UIInterfaceOrientation.Portrait
    }
}

我能够在启动时正确设置旋转。通过更改方向值并调用UIViewController.attemptRotationToDeviceOrientation(),我能够支持旋转到所需的新界面。 但是,这种旋转仅在用户实际移动设备时发生。我需要它自动发生。

我可以调用:UIDevice.currentDevice().setValue(targetOrientation.rawValue, forKey: "orientation") 来强制更改,但这会导致其他副作用,因为UIDevice.currentDevice().orientation 从那时起只返回 setValue。 (而且非常脏)

我有什么遗漏吗?我已经研究过关闭并启动一个新的视图控制器,但这还有其他问题,例如在关闭并立即呈现新视图时出现持续的 UI 故障控制器。

编辑:

以下方法对我不起作用:

编辑 2:

对潜在解决方案的思考:

  1. 直接设置方向(使用setValue)并处理这在 iOS 9 上出现的所有副作用(不可接受)

  2. 我可以使用当前解决方案并指示用户需要旋转设备。设备物理旋转后,UI 会旋转,然后正确锁定到位。 (糟糕的用户界面)

  3. 我可以找到一种解决方案,它可以强制刷新方向并在没有物理动作的情况下旋转。 (我正在询问和寻找的内容)

  4. 全部手动完成。我可以将界面锁定为纵向或横向,并手动旋转和调整容器视图的大小。这是“肮脏的”,因为它放弃了所有尺寸类自动布局功能并导致代码更重。我正在努力避免这种情况。

【问题讨论】:

  • 这是一个基于 Objective-C 的答案:*.com/questions/2689598/…,但我相信 Swift 的 API 是一样的;它对你有用吗?
  • 应该有,但是这些方法在 iOS 9 中已弃用。曾经存在很多解决方案,我很难找到仍然有效的解决方案。我们的需求是非常明确的,并且是有道理的,但它们与当前的设计模式略有不同(有道理)。在这种情况下,让用户选择方向是不可行的。
  • 哈,现在才看到弃用,真可惜。您的 VC 是否包含在导航控制器或选项卡栏中? Korey Hinton 在这里的答案:*.com/questions/26357162/… 试图解决这个具体案例,但我还没有尝试过。
  • 不,它是完全独立的。我考虑过手动旋转整个界面,但如果可能的话,我想避免这种情况。这是一个比我想要的更肮脏的解决方案。
  • 我认为不清楚:您所说的“非常肮脏”是什么意思。您是否参考旋转后的边界尺寸?请用一些屏幕截图解释您的问题,以便任何人都可以帮助您。陷入“过于宽泛”的问题或“有多种解决方案”的问题的风险非常高。

标签: ios iphone uiinterfaceorientation


【解决方案1】:

我能够在这个答案的帮助下找到解决方案:Programmatic interface orientation change not working for iOS

我的基本定位逻辑如下:

// Local variable to tracking allowed orientation. I have specific landscape and
// portrait targets and did not want to remember which I was supporting
enum MyOrientations {
    case Landscape
    case Portrait
}
var orientation: MyOrientations = .Landscape

// MARK: - Orientation Methods

override func shouldAutorotate() -> Bool {
    return true

}

override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
    if self.orientation == .Landscape {
        return UIInterfaceOrientationMask.LandscapeRight
    } else {
        return UIInterfaceOrientationMask.Portrait
    }
}

override func preferredInterfaceOrientationForPresentation() -> UIInterfaceOrientation {
    if self.orientation == .Landscape {
        return UIInterfaceOrientation.LandscapeRight
    } else {
        return UIInterfaceOrientation.Portrait
    }
}

// Called on region and delegate setters
func refreshOrientation() {
    if let newOrientation = self.delegate?.getOrientation() {
        self.orientation = newOrientation
    }
}

然后当我想刷新方向时,我执行以下操作:

// Correct Orientation
let oldOrientation = self.orientation
self.refreshOrientation()
if self.orientation != oldOrientation {
    dispatch_async(dispatch_get_main_queue(), {
        self.orientationRefreshing = true

        let vc = UIViewController()

        UIViewController.attemptRotationToDeviceOrientation()

        self.presentViewController(vc, animated: false, completion: nil)

        UIView.animateWithDuration(0.3, animations: {
            vc.dismissViewControllerAnimated(false, completion: nil)
        })
    })
}

此解决方案的副作用是导致view[Will/Did]Appearview[Will/Did]Disappear 一次全部触发。我正在使用本地 orientationRefreshing 变量来管理这些方法的哪些方面被再次调用。

【讨论】:

    【解决方案2】:

    我自己过去也遇到过这个确切的问题。我能够使用一个简单的解决方法(和GPUImage)来解决它。我的代码是用 Objective-C 编写的,但我相信你可以毫无问题地将它翻译成 Swift。

    我首先将项目支持的旋转设置为我希望支持的所有旋转,然后覆盖相同的 UIViewController 方法:

    -(BOOL)shouldAutorotate { 
        return TRUE; 
    }
    
    -(NSUInteger)supportedInterfaceOrientations {
        return UIInterfaceOrientationMaskPortrait;
    }
    

    这允许设备旋转,但会在纵向模式下保持不变。然后开始观察设备旋转:

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(adjustForRotation:)
                                                 name:UIDeviceOrientationDidChangeNotification
                                               object:nil];
    

    如果设备处于纵向模式或横向模式,则更新 UI:

    -(void)adjustForRotation:(NSNotification*)notification
    {
        UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
    
        switch (orientation) {
            case UIDeviceOrientationLandscapeLeft:
            {
                // UPDATE UI
            }
                break;
            case UIDeviceOrientationLandscapeRight:
            {
                // UPDATE UI
            }
                break;
            default: // All other orientations - Portrait, Upside Down, Unknown
            {
                // UPDATE UI
            }
                break;
        }
    } 
    

    最后,GPUImage 根据设备的方向渲染图像。

    [_gpuImageStillCamera capturePhotoAsImageProcessedUpToFilter:last_f
                                                  withCompletionHandler:^(UIImage *processedImage, NSError *error) { 
        // Process the processedImage 
    }];
    

    【讨论】:

    • 根据您的经验,您是如何处理 UI 更新的?您是否手动缩放和旋转容器 UI?
    • 我会在 UI 元素 @NathanTornquist 上应用 CGAffineTransform,所以旋转
    • 另外,我强烈推荐使用 GPUImage。这是迄今为止对开源 iOS 生态系统的最大贡献之一!如果您对使用这种方法很认真,我很乐意更详细地概述我的解决方案。
    • 我之前没有在整个视图上使用 CGAffineTransformations。如果我理解正确,我将不得不旋转并调整视图大小,然后推入目标大小类,以便我仍然可以在界面中使用自动布局约束?你有这方面的经验/知道它是否有效吗?
    • 我应该更清楚。我在视图的子视图上使用了 CGAffineTransformations。也有 UIView.frame 调整的可能性。这样想,UIViewController 仍会认为它处于纵向模式,但您自己修改代码中的子视图内容以实现横向。它对我来说很顺利。
    【解决方案3】:

    所以我查看了UIDevice 的私有标头,似乎有两个setter 和两个属性定义,用于定位,目前让我感到困惑。这是我看到的……

    @property (nonatomic) int orientation;
    @property (nonatomic, readonly) int orientation; 
    
    - (void)setOrientation:(int)arg1;
    - (void)setOrientation:(int)arg1 animated:(BOOL)arg2;
    

    所以当我看到你使用setValue:forKey: 时,我想看看有一个合成的 setter 和 getter,老实说,我不能 100% 确定哪个正在设置,哪个正在被设备确认...我尝试在演示应用程序中使用 setValue:forKey: 无济于事,但使用了我过去的一个应用程序中的这个技巧,它马上就成功了 :) 我希望这会有所帮助

    UIDevice.currentDevice().performSelector(Selector("setOrientation:"), withObject: UIInterfaceOrientation.Portrait.rawValue)
    

    【讨论】:

    • 虽然可行,但它是一个私有 API,也是让我的应用被拒绝的一种非常可靠的方法。
    • 大约三年前,在 TMGGO 强制视频播放器进行自我定位时,我实际上已经对这个审查过程感到不满。从技术上讲,setValue:forKey:,实际上是在后台调用setOrientation:,所以我很惊讶它会被拒绝,因为你在技术上做同样的事情,奇怪的是结果不同:/