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

6
我正在开发一款摄影应用程序,可以在纵向或横向拍摄照片。由于项目需求,我不能让设备方向自动旋转,但需要支持旋转。
当使用以下方向方法时:
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将旋转并正确锁定在原位。(UI效果差)

  3. 我可以找到一种解决方案,强制刷新方向并在没有物理操作的情况下旋转。(这就是我的问题,并且正在寻找解决方案)

  4. 手动完成所有操作。我可以将界面锁定为纵向或横向,并手动旋转和调整容器视图的大小。这是“繁琐”的,因为它放弃了所有的尺寸类别自动布局功能,并导致代码更加冗长。我试着避免这种情况。


1
这是一个基于Objective-C的答案:https://dev59.com/oU3Sa4cB1Zd3GeqPuVZI?rq=1,但我相信Swift的API是相同的;它会对你有用吗? - fullofsquirrels
1
然而,这些方法在iOS 9中已被弃用。曾经存在许多解决方案,但我现在很难找到一个仍然有效的。我们的需求非常明确且合理,但它们略微违反了当前的设计模式(有原因)。在这种情况下,让用户选择方向是不可行的。 - Nathan Tornquist
哈,刚看到弃用警告,糟糕。你的VC是否包含在Nav Controller或Tab Bar中?Korey Hinton在这里给出的答案:https://dev59.com/318d5IYBdhLWcg3w3FRk试图解决这个特定情况,但我还没有尝试过。 - fullofsquirrels
不,它完全独立。我考虑过手动旋转整个界面,但如果可能的话,我想避免这样做。这比我想要的解决方案更加粗糙。 - Nathan Tornquist
我认为不太清楚:你所说的“极其肮脏”是什么意思?你是指旋转后的边界尺寸吗?请用一些截图解释你的问题,这样任何人都可以帮助你。陷入“过于宽泛”的问题或“有多个解决方案”的问题的风险非常高。 - Alessandro Ornano
我可以:1/ 直接设置方向并处理在iOS 9上出现的所有副作用(不可接受)2/ 我可以使用当前解决方案并指示用户需要旋转设备(界面较差)3/ 我可以找到一种解决方案,强制刷新方向(这就是我所询问的)4/ 手动完成所有操作。我可以锁定界面为纵向或横向,并手动旋转和调整容器视图的大小。这是“肮脏”的,因为它放弃了所有的尺寸类自动布局功能,并导致代码更加繁重。我正在尝试避免这种情况。 - Nathan Tornquist
3个回答

4
我能够在这个回答的帮助下找到解决方案: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 变量来管理再次调用这些方法的哪些方面。

3
我曾经也遇到过这个问题。我使用了一个简单的解决方法(和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? - Nathan Tornquist
我会在UI元素上应用CGAffineTransform,@NathanTornquist,以进行旋转。 - esreli
我应该更清楚地表达。我在视图的子视图上使用了CGAffineTransformations。还有UIView.frame调整的可能性。这样想,UIViewController仍然认为它处于纵向模式,但你可以自己在代码中修改横向方向的子视图内容。对我来说,这很顺利。 - esreli
假设我想从这个视图控制器中呈现UIActivityViewController,在设备的横向模式下,您不认为这会造成问题吗?因为视图控制器本身仍然只处于纵向模式。 - mayur
@mayur,你说得对,但这个答案是根据问题的需求量身定制的,不是吗? - esreli
显示剩余2条评论

0

所以我查看了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,并且老实说我不确定哪个被设置,哪个被设备确认... 我在演示应用程序中尝试使用setValue:forKey:但没有成功,但是使用了我过去应用程序中的这个技巧,它立即解决了问题:) 希望这可以帮助到你。
UIDevice.currentDevice().performSelector(Selector("setOrientation:"), withObject: UIInterfaceOrientation.Portrait.rawValue)

虽然这样做可以实现目的,但它是一个私有API,也是让我的应用被拒绝的一个非常可靠的方法。 - Nathan Tornquist
实际上,我之前在TMGGO上通过了这个审查过程,大约三年前,当时我强制一个视频播放器定位。从技术上讲,setValue:forKey:实际上是在后台调用setOrientation:,所以我会感到惊讶,因为你从技术上讲正在做同样的事情,但结果却不同 :/ - AntonTheDev

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