AVCaptureVideoPreviewLayer displays correctly except on iPhone6

1.5k views Asked by At

I'm working on implementing a view that consists of an AVCaptureVideoPreviewLayer. The view is supposed to take up the entirety of the screen, except for the navigation bar at the top (the view is embedded in a navigation controller).

My code below shows how I've set up a capture session and added the front camera as input if it's available.

//Set up capture Session
session = [[AVCaptureSession alloc]init];
session.sessionPreset = AVCaptureSessionPresetPhoto;

//Add a camera as input if we have a front-facing camera available
NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
NSError *error;

for (AVCaptureDevice *device in devices) {
    if ([device position] == AVCaptureDevicePositionBack) {
        //Set our input
        AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
        [session addInput:input];
        if (!input) {
            NSLog(@"No Input");
        }
    }
}

Below is where the output is configured, and the camera output view is added as a subview, with the frame determined by the view's bounds.

//Output
AVCaptureVideoDataOutput *output = [[AVCaptureVideoDataOutput alloc] init];
[session addOutput:output];
output.videoSettings = @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA) };

//Preview Layer
AVCaptureVideoPreviewLayer *previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:session];
previewLayer.frame = self.view.bounds;
self.view.translatesAutoresizingMaskIntoConstraints = NO;

previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
[self.view.layer addSublayer:previewLayer];

As you can see in the screenshot below when running on an iPhone 6, the preview layer does not take up the entirety of the screen (the white space to the right of the camera view and under the navigation bar should be filled up by the preview layer):

enter image description here

However, it does fill up the screen appropriately on the iPhone 5 screenshot below (as well as the iPhone 4, 4S, and 5S). Does anyone have any insight into why?

enter image description here

2

There are 2 answers

4
TylerP On BEST ANSWER

Assuming you're creating this video preview layer in viewDidLoad, the view controller's view has not been laid out yet, and as such its frame and bounds are not necessarily what they will be once the view is shown on screen. You'll have to wait until the view is laid out to set the correct frame of your preview layer, which you can do by implementing the viewDidLayoutSubviews method of your view controller, like so:

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    self.previewLayer.frame = self.view.bounds;
}

This ensures that any time your view controller's view changes in size, your preview layer also changes in size.

0
TylerP On

You can use an AVCaptureVideoPreviewLayer as the backing layer of a UIView subclass, and then add an instance of this view as a subview to your view controller's view. This way you can use constraints to position your video capture preview however you want, as you would with any other view.

Here's an example of how you could do that:

// MyCaptureVideoPreviewView.h

#import <AVFoundation/AVFoundation.h>

@interface MyCaptureVideoPreviewView : UIView

@property (nonatomic, readonly) AVCaptureVideoPreviewLayer *layer;
@property (nonatomic) AVCaptureSession *session;

- (instancetype)initWithFrame:(CGRect)frame session:(AVCaptureSession *)session;

@end


// MyCaptureVideoPreviewView.m

@implementation MyCaptureVideoPreviewView

@dynamic layer;

+ (Class)layerClass {
    return [AVCaptureVideoPreviewLayer class];
}

- (AVCaptureSession *)session {
    return self.layer.session;
}

- (void)setSession:(AVCaptureSession *)session {
    self.layer.session = session;
}

- (instancetype)initWithFrame:(CGRect)frame session:(AVCaptureSession *)session {
    self = [super initWithFrame:frame];
    if (self) {
        self.session = session;
    }
    return self;
}

@end

And then somewhere in your view controller's implementation:

MyCaptureVideoPreviewView *previewView = [[MyCaptureVideoPreviewView alloc] initWithFrame:self.view.bounds
                                                                                  session:session];
previewView.layer.videoGravity = AVLayerVideoGravityResizeAspectFill;
previewView.translatesAutoresizingMaskIntoConstraints = NO;

[self.view addSubview:previewView];

// Add whatever constraints you want. Here we just match the superview's bounds:
NSArray *vConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[previewView]|" options:kNilOptions
                                                                metrics:nil views:NSDictionaryOfVariableBindings(previewView)];
NSArray *hConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[previewView]|" options:kNilOptions
                                                                metrics:nil views:NSDictionaryOfVariableBindings(previewView)];

[self.view addConstraints:vConstraints];
[self.view addConstraints:hConstraints];