在 iOS 9 中强制视图控制器方向

Force View Controller Orientation in iOS 9

我正在开发一款可以拍摄人像或风景照片的应用程序。由于项目需要,我不能让设备方向自动旋转,但是需要支持旋转。

使用以下定位方法时:

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 只有 returns 从那一点开始的 setValue。 (而且非常脏)

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

编辑:

以下方法对我不起作用:

编辑 2:

关于潜在解决方案的想法:

  1. 直接设置方向(使用setValue)并处理这对iOS 9 带来的所有副作用(不可接受)

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

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

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

我自己过去也遇到过这个问题。我能够使用简单的解决方法(和 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 
}];

于是我查看了UIDevice的私有headers,好像有两个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)

借助这个答案,我找到了解决方案: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 变量来管理再次调用这些方法的哪些方面。