如何在AVCaptureVideoPreviewLayer中处理自动旋转?

8
我的应用程序支持除PortraitUpsideDown之外的所有方向。 在我的视图层次结构中,我有一个AVCaptureVideoPreviewLayer作为UIImageView的子层。然后,在它下面是几个覆盖视图显示控件。
覆盖视图能够正确地处理方向变化,但我不知道如何处理这个AVCaptureVideoPreviewLayer。我希望它的行为与相机应用程序相同,使预览层保持静止,并且控件可以平滑地重新组织。现在,由于主视图在方向改变时进行了旋转,我的预览层也被旋转,这意味着在横向视图中,它仍然保持纵向视图,只占屏幕的一部分,并且摄像头拍摄的图片也会旋转90度。我已经成功手动旋转了预览层,但这样会出现方向变化动画,导致在动画期间看到背景。
那么,在使预览层保持静止的同时自动旋转控件的正确方法是什么?

1
曾经遇到过类似的问题。很遗憾,有一个“显而易见”的解决方案却不可用:我希望能够使用KVO观察视图层的演示层的transform,但是没有成功——在动画运行时无法使用键值观察。最终采用了与您可能相同的解决方案:将预览层嵌入到一个UIView中并手动旋转它。 - Rok Jarc
2
是的,我最终也采用手动旋转包含预览层的视图。结果并不是很复杂。我使用CGAffineTransformMakeRotation(),然后更改框架到正确的大小。但现在我拥有了我想要的完美行为。 - BartoNaz
@BartoNaz 我已经苦恼了几天,尝试了很多方法都无法实现像camera.app那样,AVCaptureVideoPreviewLayer锁定在一个视图上并且不进行动画旋转的效果。您能否提供您的可行代码片段作为这个问题的答案呢?谢谢。 - Sebastian Dwornik
BartoNaz,我也卡在了同一个问题上,请在下面提供您的解决方案作为答案。 - trapper
3个回答

5
在我的实现中,我为想要旋转的视图子类化了UIView,并为该视图创建了一个类似于viewController的对象,它只是NSObject的子类。
在这个viewController中,我进行所有与方向变化相关的检查,决定是否应该更改目标视图的方向,如果是,则调用我的视图方法来更改其方向。
首先,我们需要将整个应用程序界面的方向固定为竖屏模式,以便我们的ACCaptureVideoPreviewLayer始终保持静止。这是在MainViewController.h文件中完成的:
(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation`
{
    return interfaceOrientation==UIInterfaceOrientationPortrait;
}

除了纵向模式,它会对所有方向返回NO。

为了使我们的自定义视图控制器能够跟踪设备方向的变化,我们需要将其设置为相应通知的观察者:

[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(orientationChanged) name:UIDeviceOrientationDidChangeNotification object:nil];

我将这些行放在我的视图控制器的 (void)awakeFromNib 方法中。因此,每次设备方向更改时,将调用viewController的方法 orientationChanged
它的目的是检查设备的新方向,上一次设备的方向,并决定是否更改方向。这是实现方法:
if(UIInterfaceOrientationPortraitUpsideDown==[UIDevice currentDevice].orientation ||
    lastOrientation==(UIInterfaceOrientation)[UIDevice currentDevice].orientation) 
    return;
    [[UIApplication sharedApplication]setStatusBarOrientation:[UIDevice currentDevice].orientation animated:NO];

    lastOrientation=[UIApplication sharedApplication].statusBarOrientation;
    [resultView orientationChanged];

如果方向与之前相同或为“竖直向下”,则不执行任何操作。否则,它会将状态栏方向设置为正确的方向,以便在有来电或 ossification 时,它将显示在屏幕的正确侧面。然后我还要调用目标视图中的方法,以执行所有新方向相关的对应更改,如旋转、调整大小、移动此视图中其他接口元素的位置等。
以下是目标视图中 orientationChanged 的实现:
Float32 angle=0.f;
UIInterfaceOrientation orientation=[UIApplication sharedApplication].statusBarOrientation;

switch (orientation) {
    case UIInterfaceOrientationLandscapeLeft: 
        angle=-90.f*M_PI/180.f;
        break;
    case UIInterfaceOrientationLandscapeRight: 
            angle=90.f*M_PI/180.f;
        break;
    default: angle=0.f;
        break;
}   
if(angle==0 && CGAffineTransformIsIdentity(self.transform)) return;

CGAffineTransform transform=CGAffineTransformMakeRotation(angle);

[UIView beginAnimations:@"rotateView" context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
[UIView setAnimationDuration:0.35f];

self.transform=transform;

[UIView commitAnimations];

当然,您可以添加任何其他更改,例如翻译、缩放界面的不同视图以响应新方向并对其进行动画处理。
此外,您可能不需要viewController来完成此操作,而是在视图类中完成所有操作。希望这个总体思路清晰明了。
同时,在不需要时,请勿忘记停止获取有关方向更改的通知,例如:
[[NSNotificationCenter defaultCenter]removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
[[UIDevice currentDevice]endGeneratingDeviceOrientationNotifications];

1
iOS 8解决方案:
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
    if ([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationLandscapeLeft) {
        self.layer.connection.videoOrientation = AVCaptureVideoOrientationLandscapeLeft;
    } else {
        self.layer.connection.videoOrientation = AVCaptureVideoOrientationLandscapeRight;
    }
}

在你的设置代码中:
self.layer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session];
self.layer.videoGravity = AVLayerVideoGravityResizeAspectFill;
if ([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationLandscapeLeft) {
    self.layer.connection.videoOrientation = AVCaptureVideoOrientationLandscapeLeft;
} else {
    self.layer.connection.videoOrientation = AVCaptureVideoOrientationLandscapeRight;
}

0
主要问题是当我收到通知时,状态栏尚未旋转,因此检查当前值实际上是旋转之前的值。因此,在调用检查状态栏方向并旋转我的子视图的方法之前,我添加了一点延迟(这里是2秒):
-(void) handleNotification:(NSNotification *) notification
{    
    (void) [NSTimer scheduledTimerWithTimeInterval:(2.0)
                                           target:self
                                          selector:@selector(orientationChanged)
                                          userInfo:nil
                                           repeats:NO ] ;
}

其余的代码来自BartoNaz。


我不确定这是否正确。这个方法的确切想法就是状态栏不会自动旋转。而且你不需要检查状态栏的当前方向。你正在检查设备的方向,并将其与上次调用此方法时的方向进行比较。当你的方法决定改变方向时,它会手动进行更改,通过调用[[UIApplication sharedApplication]setStatusBarOrientation:[UIDevice currentDevice].orientation animated:NO]; - BartoNaz
在这里,你应该使用设备方向而不是状态栏方向。 - Yuchen

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