Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AutoLayout and AVCaptureVideoPreviewLayer

I have a UIView

@property (nonatomic, strong) IBOutlet UIView *previewView;

previewView will ultimately display the preview of what is going on in my camera on my iPhone using the following preview layer.

@property (nonatomic, strong) AVCaptureVideoPreviewLayer *previewLayer;

I have constraints set up on my previewView in storyboards and therefore am running into the following issue.

self.previewLayer.frame = self.previewView.bounds; // This doesn't work
self.previewLayer.videoGravity = AVLayerVideoGravityResize;
[self.previewView.layer addSublayer:self.previewLayer];

previewLayer gets added to the previewView subLayer, but it does not show in the right parameters (width and height). I think this is due to using constraints on autolayout.

How would I fix this?

Update: I also tried the following:

self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspect;
CALayer *rootLayer = [previewView layer];
rootLayer.frame = self.previewView.bounds;
[rootLayer setMasksToBounds:YES];
[self.previewLayer setFrame:[rootLayer bounds]];
[rootLayer addSublayer:self.previewLayer];

And it appears to show like this: http://cl.ly/image/3T3o0O1E3U17

Getting close, but still very much off.

like image 455
sdads asdasd Avatar asked Dec 18 '14 18:12

sdads asdasd


2 Answers

Okay, I solved it.

The problem is that when you use autolayout, the frames/bounds of your UIViewController's subviews can change after you have set up your previewLayer, without the previewLayer itself being updated accordingly! This is because the layout of CALayers is solely controlled by one's own code (see also this related answer: https://stackoverflow.com/a/27735617/623685).

To solve this, you have to override the viewDidLayoutSubviews method of your UIViewController to update the previewLayer's position and bounds (and possibly others) every time the subviews' layout changes.


In the following, I will describe how I solved it in detail:

My case was actually a bit more complicated, because I am using the OpenCV iOS framework 2.4.9. Here, the preview layer is created and managed by an OpenCV class called CvVideoCamera. Therefore I had to create a subclass of CvVideoCamera, to which I added the following method:

- (void)updatePreviewLayer
{
    self.customPreviewLayer.bounds = CGRectMake(0, 0, self.parentView.frame.size.width, self.parentView.frame.size.height);
    [self layoutPreviewLayer];
}

Explanation: parentView is a UIView you pass to CvVideoCamera in order to specify where the preview layer should be positioned and how big it should be. customPreviewLayer is the preview layer managed by CvVideoCamera. And layoutPreviewLayer is a method that basically updates the layer's position: https://github.com/Itseez/opencv/blob/2.4/modules/highgui/src/cap_ios_video_camera.mm#L211

Then I simply called this method from my UIViewController like so:

- (void)viewDidLayoutSubviews
{
    NSLog(@"viewDidLayoutSubviews");
    [super viewDidLayoutSubviews];
    [self.myCameraView updatePreviewLayer];
}
like image 145
robert Avatar answered Sep 30 '22 12:09

robert


The main idea is the same - when using Autolayout, the frame of the preview layer needs to be set after all view constraints have been applied. The viewDidLayoutSubviews method is used for this - same as in the original answer but without complications of using any frameworks other than the Apple's AVFoundation.

The following code is based on an Apple's sample (AVCam-iOS: Using AVFoundation to Capture Images and Movies). But the code was modified to use updateViewConstraints method to programmatically set all constraints instead of the Storyboard approach used in the sample. Relevant fragments are shown below:

@interface QRScannerViewController ()

@property (nonatomic) UIView *previewView;

@end

@implementation QRScannerViewController
{
    AVCaptureSession *_captureSession;
    AVCaptureVideoPreviewLayer *_captureVideoPreviewLayer;
    dispatch_queue_t _sessionQueue;
}

#pragma mark - Lifecycle

- (void)viewDidLoad {
    [super viewDidLoad];

    [self.view addSubview:self.previewView];

    _captureSession = [AVCaptureSession new];
    _captureVideoPreviewLayer = [AVCaptureVideoPreviewLayer layerWithSession:_captureSession];
    _captureVideoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;

    _sessionQueue = dispatch_queue_create("av_session_queue", DISPATCH_QUEUE_SERIAL);

    [self.view setNeedsUpdateConstraints];

    dispatch_async(_sessionQueue, ^{
        [self configureCaptureSession];
    });

    [self.previewView.layer addSublayer:_captureVideoPreviewLayer];
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    if (_isCaptureDeviceConfigured) {
        [_captureSession startRunning];
    }
}

- (void)viewDidLayoutSubviews
{
    _captureVideoPreviewLayer.frame = self.previewView.bounds;
}

#pragma mark - getters / initializers

- (UIView *)previewView
{
    if (_previewView) {
        return _previewView;
    }
    _previewView = [UIView new];
    _previewView.backgroundColor = [UIColor blackColor];
    _previewView.translatesAutoresizingMaskIntoConstraints = NO;

    return _previewView;
}

- (void)configureCaptureSession
{
    [_captureSession beginConfiguration];

    // Code omitted, only relevant to the preview layer is included. See the Apple's sample for full code.
    ...
    _captureVideoPreviewLayer.connection.videoOrientation = initialVideoOrientation;
    ...
}

#pragma mark - Autolayout

- (void)updateViewConstraints
{
    [self.previewView.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor constant:0.0].active = YES;
    [self.previewView.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor constant:-40.0].active = YES;
    [self.previewView.widthAnchor constraintEqualToConstant:300].active = YES;
    [self.previewView.heightAnchor constraintEqualToAnchor:self.previewView.widthAnchor constant:0.0].active = YES;

    [super updateViewConstraints];
}
like image 31
Vitali Tchalov Avatar answered Sep 30 '22 11:09

Vitali Tchalov