How to handle autorotation in AVCaptureVideoPreviewLayer?
Asked Answered
D

3

8

My application supports all orientations except PortraitUpsideDown. In my view hierarchy I have an AVCaptureVideoPreviewLayer as a sublayer in the top view which is UIImageView. Then below it in view hierarchy are several overlay views showing controls.

Overlay views are working properly with orientation changes, but I don't how to be with this AVCaptureVideoPreviewLayer. I want it to behave like in Camera app, so that previewLayer stays still and controls are smoothly reorganized. Right now since the main view is rotated on orientation change, my preview layer is also rotated, which means that in landscape view it stays in portrait view, taking only part of the screen and the picture from camera being also rotated by 90 degrees. I've managed to rotate the preview layer manually, but then it has this orientation change animation, which leads to the background being seen for a while during the animation.

So what is the proper way to autorotate the controls while making previewLayer stay still?

Digitigrade answered 18/6, 2012 at 9:4 Comment(4)
Had similiar problem. One obvious solution is sadly not available: i was hoping to observe (with KVO) transform of view's layer's presentationLayer without luck - key-value-observing is not available during animation runtime. Ended up using (probably) the same solution as you: embedding preview layer into an UIView an manually rotating it.Parent
Yes, I also ended up with manual rotation of the view containing the previewLayer. It turned out being not very complicated. I apply CGAffineTransformMakeRotation() and then change the frame to the correct size. But now I have exactly the behavior I want.Digitigrade
@Digitigrade I've been struggling with this for a couple days now and everything I try doesn't give me the effect of the camera.app where the AVCaptureVideoPreviewLayer stays locked to a view, and doesn't do its animated rotation. Could you kindly provide your working code snippet as an answer to this question? Thanks.Carnes
BartoNaz I am also stuck on this same problem, please provide your solution as an answer belowGaughan
D
5

In my implementation I have subclassed the UIView for my view which I want to rotate and something like viewController for this view which is just a subclass of NSObject.

In this viewController I do all the the checks related to changes of orientation, make decision if I should change orientation of my target view, and if yes, then I call method of my view for changing its orientation.

First of all we need to fix the orientation of whole application interface to Portrait mode, so that our ACCaptureVideoPreviewLayer always stays still. This is done in the MainViewController.h:

(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation`
{
    return interfaceOrientation==UIInterfaceOrientationPortrait;
}

It returns NO to all orientations except Portrait.

In order to our custom viewController be able to track the changes of device orientation we need to make it an observer of corresponding notifications:

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

I put these lines in the (void)awakeFromNib method of my viewController. So each time the device orientation is changed, the viewController's method orientationChanged will be called.

Its purpose is to check what is the new orientation of device, what was the last orientation of device and decide if to change it. Here is the implementation:

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];

If the orientation is the same as before or in PortraitUpsideDown then do nothing. Else it sets the status bar orientation to the proper one, so that when there is an incoming call or ossification, it will appear on the proper side of the screen. And then I call also method in the target view where all the corresponding changes for new orientation are done, like rotating, resizing, moving the other elements of interface in this view corresponding to the new orientation.

Here is the implementation of the orientationChanged in target view:

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];

Of course here you can add any other changes like translation, scaling of different views of your interface that need to respond to new orientation and animate them.

Also you may not need the viewController for this, but do all just in the class of your view. Hope that the general idea is clear.

Also don't forget to stop getting notification for orientation changes when you don't need them like:

[[NSNotificationCenter defaultCenter]removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
[[UIDevice currentDevice]endGeneratingDeviceOrientationNotifications];
Digitigrade answered 28/10, 2012 at 11:35 Comment(0)
B
1

iOS 8 solution:

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

in your setup code:

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;
}
Bashemeth answered 22/5, 2015 at 10:18 Comment(0)
C
0

THe main problem is that when I get the notification, the statusbar has not yet rotated, so checking the current value give in fact the value before the rotation. So I added a little delay (here 2 seconds) before calling the method that check the statusbarorientation and rotates my subview :

-(void) handleNotification:(NSNotification *) notification
{    
    (void) [NSTimer scheduledTimerWithTimeInterval:(2.0)
                                           target:self
                                          selector:@selector(orientationChanged)
                                          userInfo:nil
                                           repeats:NO ] ;
}

The rest of the code is the one from BartoNaz.

Congius answered 21/12, 2012 at 17:46 Comment(2)
I am not sure if this is correct. That is exactly the idea of this method, that the statusbar doesn't rotate by itself. And you don't check the current orientation of the statusbar. You are checking the orientation of the device and compare it with the orientation from the last time when this method was called. And when your method decides to change the orientation, it does this change manually, by calling [[UIApplication sharedApplication]setStatusBarOrientation:[UIDevice currentDevice].orientation animated:NO];Digitigrade
You should use device orientation instead of status bar orientation here.Teazel

© 2022 - 2024 — McMap. All rights reserved.