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.
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 CALayer
s 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];
}
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];
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With