iOS CAlayer Orientation AVCaptureVideoPreviewLayer doesn't rotate
Asked Answered
R

8

6

enter image description here Summary: I can't force the CALayer to respond correctly to orientation changes. Whenever I try to use cgaffinetransform I am getting weird results (layer is not centered). Any help will be appreciated! thanks,

Process I am adding a preview of video using AVCaptureVideoPreviewLayer subclass. When device is in a portrait orientation everything looks fine. The problem appears when device is rotated to landscape orientation (left or right) or portrait upside down.

I am adding a preview of video using AVCaptureVideoPreviewLayer subclass. When device is in a portrait orientation everything looks fine. The problem appears when device is rotated to landscape orientation (left or right) or portrait upside down.

I am adding a preview layer using the following code:

CGRect layerRect = [[[self view] layer] bounds];
[[[self captureManager] previewLayer] setBounds:layerRect];
[[[self captureManager] previewLayer]setFrame:CGRectMake(0, height, width, height)];
[[[self captureManager] previewLayer] setPosition:CGPointMake(CGRectGetMidX(layerRect),CGRectGetMidY(layerRect))];

And it is displayed properly in portrait mode. When I try to rotate the device, preview layer behaves weird. It seems like it doesn't resize itself, and it doesn't rotate correctly.

I tried to fix it by adding the following method

-(void)rotateLayer{
CALayer * stuckview = [[self captureManager] previewLayer];
CGRect layerRect = [[[self view] layer] bounds];

UIDeviceOrientation orientation =[[UIDevice currentDevice]orientation];

switch (orientation) {
    case UIDeviceOrientationLandscapeLeft:
        stuckview.affineTransform = CGAffineTransformMakeRotation(M_PI+ M_PI_2); // 270 degress
        NSLog(@"Landscape Left");
        [stuckview setPosition: CGPointMake(self.view.bounds.size.width /2.0, self.view.bounds.size.height /2.0)];

        break;
    case UIDeviceOrientationLandscapeRight:
        stuckview.affineTransform = CGAffineTransformMakeRotation(M_PI_2); // 90 degrees
        NSLog(@"Landscape Right");
        [stuckview setPosition: CGPointMake(self.view.bounds.size.width /2.0, self.view.bounds.size.height /2.0)];
        break;
    case UIDeviceOrientationPortraitUpsideDown:
        stuckview.affineTransform = CGAffineTransformMakeRotation(M_PI); // 180 degrees
        NSLog(@"Landscape Upside down");
        break;
    default:
        stuckview.affineTransform = CGAffineTransformMakeRotation(0.0);
        break;
}
 float h1 = stuckview.frame.size.height;
   float w1 = stuckview.frame.size.width;

if(UIDeviceOrientationIsPortrait(orientation))
{
  stuckview.position =CGPointMake(h1/2.0, w1/2.0);
    NSLog(@"Portrait");
}
else{
  stuckview.position =CGPointMake(w1/2.0, h1/2.0);
    NSLog(@"Portrait");
}

}

After adding the method above I can see a progress. Now layer rotates correctly reflecting current device orientation and it's displayed correctly in landscape mode, but NOT in portrait mode.

The layer is not positioned correctly, it isn't centered on the screen (look at the screenshot). To see what's happening I added following debug statements:

CALayer * stuckview = [[self captureManager] previewLayer];
CGRect layerRect = [[[self view] layer] bounds];
float h = stuckview.bounds.size.height;
float w = stuckview.bounds.size.width;

float x = stuckview.bounds.origin.x;
float y = stuckview.bounds.origin.y;

float h1 = stuckview.frame.size.height;
float w1 = stuckview.frame.size.width;

float x1 = stuckview.frame.origin.x;
float y1 = stuckview.frame.origin.y;

NSLog(@"%f %f %f %f ", h,w,x,y );
NSLog(@"%f %f %f %f ", h1,w1,x1,y1 );

NSLog(@"Anchor Point: %f  %f",stuckview.anchorPoint.x, stuckview.anchorPoint.y);
NSLog(@"Position: %f  %f",stuckview.position.x, stuckview.position.y);

CGAffineTransform at = stuckview.affineTransform;

NSLog(@"Affine Transform After : %f %f %f %f %f %f %f", at.a,at.b, at.c, at.d, at.tx,at.tx, at.ty);

And get the following output:

2012-09-30 13:25:12.067 RotatePreviewLayer[2776:907] 1024.000000 768.000000 0.000000 0.000000 

2012-09-30 RotatePreviewLayer[2776:907] 1024.000000 768.000000 128.000000 -128.000000

2012-09-30 13:25:12.070 RotatePreviewLayer[2776:907] Portrait
2012-09-30 13:25:12.072 RotatePreviewLayer[2776:907] Anchor Point: 0.500000  0.500000
2012-09-30 13:25:12.074 RotatePreviewLayer[2776:907] Position: 512.000000  384.000000
2012-09-30 13:25:12.076 RotatePreviewLayer[2776:907] Affine Transform after: -1.000000 0.000000 -0.000000 -1.000000 0.000000 0.000000 0.000000

Notice the second line of the debug output. The frame of the preview layer is moved by 128,-128. Can anyone explain me why is this happening and how to fix the orientation issues with the preview layer? thank you, Janusz

Rutland answered 30/9, 2012 at 18:38 Comment(0)
R
5

I am still not sure what's causing the problem but I managed to fix it. Here is how I did it:

in viewDidLoad I am adding a layer:

CGRect layerRect = [[[self view] layer] bounds];
    [[[self captureManager] previewLayer] setBounds:layerRect];
    [[[self captureManager] previewLayer] setPosition:CGPointMake(CGRectGetMidX(layerRect),CGRectGetMidY(layerRect))];
    [[[self view] layer] addSublayer:[[self captureManager] previewLayer]];

Then I am adding call to the rotateLayer method to didRotate

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{
    [self rotateLayer];
}

and finally the rotateLayer method looks like:

-(void)rotateLayer{
    CALayer * stuckview = [[self captureManager] previewLayer];
    CGRect layerRect = [[[self view] layer] bounds];

    UIDeviceOrientation orientation =[[UIDevice currentDevice]orientation];

    switch (orientation) {
        case UIDeviceOrientationLandscapeLeft:
            stuckview.affineTransform = CGAffineTransformMakeRotation(M_PI+ M_PI_2); // 270 degress

            break;
        case UIDeviceOrientationLandscapeRight:
            stuckview.affineTransform = CGAffineTransformMakeRotation(M_PI_2); // 90 degrees
            break;
        case UIDeviceOrientationPortraitUpsideDown:
            stuckview.affineTransform = CGAffineTransformMakeRotation(M_PI); // 180 degrees
            break;
        default:
            stuckview.affineTransform = CGAffineTransformMakeRotation(0.0);
            [stuckview setBounds:layerRect];
            break;
    }
    [stuckview setPosition:CGPointMake(CGRectGetMidX(layerRect),CGRectGetMidY(layerRect))];
}

I still don't understand why it works in this way. If anyone can explain it will be great.

Rutland answered 2/10, 2012 at 15:30 Comment(1)
When I work with your method I need to do a -M_PI_2 with UIDeviceOrientationLandscapeRight.Mosier
G
9

What about this?

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    _videoPreviewLayer.connection.videoOrientation = toInterfaceOrientation;
}
Galahad answered 22/10, 2013 at 5:26 Comment(1)
absolutelyeeeesss, easy and simpleElenor
E
6

This the method I use in the view controller to maintain the orientation of the capture layer so that it is always right-side-up:

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
    if (_previewLayer.connection.supportsVideoOrientation)
    {
    switch (toInterfaceOrientation)
        {
        case UIInterfaceOrientationPortrait:
            {
            _previewLayer.connection.videoOrientation = AVCaptureVideoOrientationPortrait;
            break;
            }
        case UIInterfaceOrientationPortraitUpsideDown:
            {
            _previewLayer.connection.videoOrientation = AVCaptureVideoOrientationPortraitUpsideDown;
            break;
            }
        case UIInterfaceOrientationLandscapeLeft:
            {
            _previewLayer.connection.videoOrientation = AVCaptureVideoOrientationLandscapeLeft;
            break;
            }
        case UIInterfaceOrientationLandscapeRight:
            {
            _previewLayer.connection.videoOrientation = AVCaptureVideoOrientationLandscapeRight;
            break;
            }
        }
    }

A little wordy, but safe even if either enumeration ever changes in the future.

Eckman answered 3/9, 2013 at 20:22 Comment(1)
It is common (programming) sense to convert between different enums.Involuntary
R
5

I am still not sure what's causing the problem but I managed to fix it. Here is how I did it:

in viewDidLoad I am adding a layer:

CGRect layerRect = [[[self view] layer] bounds];
    [[[self captureManager] previewLayer] setBounds:layerRect];
    [[[self captureManager] previewLayer] setPosition:CGPointMake(CGRectGetMidX(layerRect),CGRectGetMidY(layerRect))];
    [[[self view] layer] addSublayer:[[self captureManager] previewLayer]];

Then I am adding call to the rotateLayer method to didRotate

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{
    [self rotateLayer];
}

and finally the rotateLayer method looks like:

-(void)rotateLayer{
    CALayer * stuckview = [[self captureManager] previewLayer];
    CGRect layerRect = [[[self view] layer] bounds];

    UIDeviceOrientation orientation =[[UIDevice currentDevice]orientation];

    switch (orientation) {
        case UIDeviceOrientationLandscapeLeft:
            stuckview.affineTransform = CGAffineTransformMakeRotation(M_PI+ M_PI_2); // 270 degress

            break;
        case UIDeviceOrientationLandscapeRight:
            stuckview.affineTransform = CGAffineTransformMakeRotation(M_PI_2); // 90 degrees
            break;
        case UIDeviceOrientationPortraitUpsideDown:
            stuckview.affineTransform = CGAffineTransformMakeRotation(M_PI); // 180 degrees
            break;
        default:
            stuckview.affineTransform = CGAffineTransformMakeRotation(0.0);
            [stuckview setBounds:layerRect];
            break;
    }
    [stuckview setPosition:CGPointMake(CGRectGetMidX(layerRect),CGRectGetMidY(layerRect))];
}

I still don't understand why it works in this way. If anyone can explain it will be great.

Rutland answered 2/10, 2012 at 15:30 Comment(1)
When I work with your method I need to do a -M_PI_2 with UIDeviceOrientationLandscapeRight.Mosier
E
1

@Janusz Chudzynski here is the detailed explanation what rotateLayer method does

This method is created after examine how different orientation affect the previewLayer so creater has checked when orientation is in LandscapeLeft then it should be 270 degrees rotated to make it in correct position for that he has used

stuckview.affineTransform = CGAffineTransformMakeRotation(M_PI+ M_PI_2);

     M_PI        3.14159265358979323846264338327950288   // pi     = 180 degree  
                                    +      
     M_PI_2      1.57079632679489661923132169163975144   // pi/2   = 90  degree

                                                         // Total  = 270 degree

so creater has noticed that if I will rotate previewLayer to 270 degrees when its in LandscapeLeft then it will be in correct position just like that he has rotate previewLayer for every rotation possible

Epizoic answered 9/8, 2013 at 6:53 Comment(0)
B
0
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration  
{  
    previewLayer.orientation = toInterfaceOrientation;  
}
Brume answered 4/10, 2012 at 11:13 Comment(2)
this does work, i am using it myself. given that shouldAutorotateToInterfaceOrientation returns YES for the corresponding interface orientation. And this method setOrientation: is deprecated for iOS6. So it is advised not to use this.Brume
Also, changing the orientation of preview layer doesn't change how the picture is captured. For that AVCaptureConnection's setVideoOrientation: method needs to be used.Brume
M
0

I'm with @Siegfault, although I also found that when my view loads in landscape orientation on the iPad initially, the orientation is still in correct. To fix, I call that same delegate method in viewDidAppear: with the current interfaceOrientation:

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [self willRotateToInterfaceOrientation:self.interfaceOrientation duration:0.0];
}
Malvinamalvino answered 6/4, 2015 at 19:7 Comment(0)
A
0

The answer using willRotateToUserInterfaceOrientation works fine, except that that method has been deprecated. So if you're able to use iOS 9, then here's the way to do it, in Swift:

override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
        let newOrientation = UIDevice.currentDevice().orientation
        switch newOrientation {
        case .LandscapeLeft:
            self.capturePreviewLayer?.connection.videoOrientation = .LandscapeLeft
        case .LandscapeRight:
            self.capturePreviewLayer?.connection.videoOrientation = .LandscapeRight
        case .Portrait, .Unknown, .FaceUp, .FaceDown:
            self.capturePreviewLayer?.connection.videoOrientation = .Portrait
        case .PortraitUpsideDown:
            self.capturePreviewLayer?.connection.videoOrientation = .PortraitUpsideDown
        }
        super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
    }
Abstractionist answered 25/4, 2016 at 14:49 Comment(0)
L
0
// layout iOS 8+ animated 
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
    [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context)
    {
         NSString *timingFunc = nil;
         switch ( [context completionCurve] )
         {
             case UIViewAnimationCurveEaseIn:    timingFunc = kCAMediaTimingFunctionEaseIn;          break;
             case UIViewAnimationCurveEaseInOut: timingFunc = kCAMediaTimingFunctionEaseInEaseOut;   break;
             case UIViewAnimationCurveEaseOut:   timingFunc = kCAMediaTimingFunctionEaseOut;         break;
             case UIViewAnimationCurveLinear:    timingFunc = kCAMediaTimingFunctionLinear;          break;
         }

         [CATransaction begin];
         [CATransaction setAnimationDuration:[context transitionDuration]];
         [CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:timingFunc]];
         [self updatePreviewLayer];
         [CATransaction commit];

         UIInterfaceOrientation toOrientation = [[UIApplication sharedApplication] statusBarOrientation];
         // layout ui if needed

    } completion:^(id<UIViewControllerTransitionCoordinatorContext> context)
    {

    }];

    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
}

- (void)updatePreviewLayer
{
    CGAffineTransform transform = CGAffineTransformIdentity;
    switch ( UIDevice.currentDevice.orientation )
    {
        case UIDeviceOrientationLandscapeLeft:
            transform = CGAffineTransformRotate(transform, -M_PI_2);
        break;
        case UIDeviceOrientationLandscapeRight:
            transform = CGAffineTransformRotate(transform, M_PI_2);
        break;
        case UIDeviceOrientationPortraitUpsideDown:
            transform = CGAffineTransformRotate(transform, M_PI);
        break;
        default:
        break;
    }
    preview.affineTransform = transform;
    preview.frame = self.view.bounds;
}
Lowdown answered 5/5, 2016 at 14:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.