How to set camera view to fill it's parent view - ios

I don't know if this problem is something about auto layout or I am doing something wrong.
I have a UIViewController and one UIView inside of it.
And I am using AVCaptureSession to put camera view inside of it.
Problem is that when I camera view loads inside of view it doesn't fill that view so I have gaps on the left and right side.
What I am trying to do is to fill whole UIView with camera.
This is my code:
...
#IBOutlet weak var camView: UIView!
var previewLayer : AVCaptureVideoPreviewLayer!
override func viewDidLoad() {
...
previewLayer = AVCaptureVideoPreviewLayer.layerWithSession(session) as! AVCaptureVideoPreviewLayer
previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
previewLayer.frame = self.camView.layer.bounds
self.camView.layer.addSublayer(previewLayer)
session.startRunning()

You need to conduct the setup after your subviews are layed out. In viewDidLayoutSubviews
-(void)viewDidLayoutSubviews
{
[self setupAVCapture];
}
- (void)setupAVCapture {
NSError *error = nil;
AVCaptureSession *session = [AVCaptureSession new];
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
[session setSessionPreset:AVCaptureSessionPreset640x480];
else
[session setSessionPreset:AVCaptureSessionPresetPhoto];
// Select a video device, make an input
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if ([session canAddInput:deviceInput])
[session addInput:deviceInput];
AVCaptureVideoPreviewLayer *previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:session];
[previewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
previewLayer.frame = self.camView.bounds;
[self.camView.layer addSublayer:previewLayer];
[session startRunning];
}
What was likely happening to you is that the bounds were not accurately set yet in your viewDidLoad as the subviews have yet to be laid out according to their constraints. When you instead setup the frame for the previewLayer in viewDidLayoutSubviews it will adopt the correct orientation. Be careful though, you will also need to adjust the preview on rotation and what not and modify the code I used. Sorry it isn't in swift, but that shouldn't matter. Just move your instantiation and setup into viewDidLayoutSubviews
So for your code, you should do something like this:
override func viewDidLayoutSubviews() {
...
previewLayer = AVCaptureVideoPreviewLayer.layerWithSession(session) as! AVCaptureVideoPreviewLayer
previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
previewLayer.frame = self.camView.layer.bounds
self.camView.layer.addSublayer(previewLayer)
session.startRunning()

To set priviewLayer constraints as like below code so it will occupied whole screen.
Leading Space = 0 from mainView
Top Space = 0 from mainView
Bottom Space = 0 from mainView
Trailing Space = 0 from mainView
Hope this help you.

This worked for me in Swift 4.2
Just set your AVCaptureVideoPreviewLayer's instance frame to Parent view's frame like below in viewDidLayoutSubviews() function :
override func viewDidLayoutSubviews(){
super.viewDidLayoutSubviews()
avCaptureVideoPreviewLayerInstance.frame = self.parentView.bounds
}

If you want your layer to fill the UIView properly, you might as well define constraints for it :
self.camView.layer.addSublayer(previewLayer)
self.camView.layer.layoutManager = CAConstraintLayoutManager.layoutManager();
self.camView.layer.addConstraint(CAConstraint.constraintWithAttribute(CAConstraintAttribute.MinX, relativeTo: self.camView.layer, attribute: CAConstraintAttribute.MinX, scale: 1, offset: 0));
self.camView.layer.addConstraint(CAConstraint.constraintWithAttribute(CAConstraintAttribute.MaxX, relativeTo: self.camView.layer, attribute: CAConstraintAttribute.MaxX, scale: 1, offset: 0));
self.camView.layer.addConstraint(CAConstraint.constraintWithAttribute(CAConstraintAttribute.Width, relativeTo: self.camView.layer, attribute: CAConstraintAttribute.Width, scale: 1, offset: 0));
self.camView.layer.addConstraint(CAConstraint.constraintWithAttribute(CAConstraintAttribute.Height, relativeTo: self.camView.layer, attribute: CAConstraintAttribute.Height, scale: 1, offset: 0));
session.startRunning()

If you are using autolayout then just do:
Set your camera view x=0 and with to screen size
Apply leading and trailing constraint to superView
Hope this will help you.

Related

iPhone Xs - Why is there a huge padding between the top border of my UIView and the top border of the safe area when using AVCaptureVideoPreviewLayer?

I am developing a custom camera on the iphone Xs Max. My layout is below. The only UIView's top, left, right, and bottom borders are anchored to the safe area. Yet, what I am seeing is a huge black space between the top border of my video capture output and the top border of the safe view. What is this black space and how do i calculate its height?
Layout:
UIView constraints:
code:
class NewCapturViewController: UIViewController, UIImagePickerControllerDelegate,AVCaptureVideoDataOutputSampleBufferDelegate {
var previewLayer = AVCaptureVideoPreviewLayer.init()
var captureSession: AVCaptureSession!
override func viewWillAppear(_ animated: Bool) {
startAVCaptureSession()
}
func startAVCaptureSession() {
print("START CAPTURE SESSION!!")
// Setting Up a Capture Session
self.captureSession = AVCaptureSession()
captureSession.beginConfiguration()
// Configure input
let videoDevice = AVCaptureDevice.default(for: .video)
guard
let videoDeviceInput = try? AVCaptureDeviceInput.init(device: videoDevice!) as AVCaptureInput,
self.captureSession.canAddInput(videoDeviceInput)else {return}
self.captureSession.addInput(videoDeviceInput)
// Capture video output
let videoOutput = AVCaptureVideoDataOutput.init()
guard self.captureSession.canAddOutput(videoOutput) else {return}
videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue.init(label: "videoQueue"))
self.captureSession.addOutput(videoOutput)
// start
self.captureSession.commitConfiguration()
self.captureSession.startRunning()
// Display camera preview
self.previewLayer = AVCaptureVideoPreviewLayer.init(session: self.captureSession)
// Use 'insertSublayer' to enable button to be viewable
self.camViewOutlet.layer.insertSublayer(self.previewLayer, at: 0)
self.previewLayer.frame = self.camViewOutlet.frame
self.previewFrame = previewLayer.frame
print("previewLayer.frame: \(previewLayer.frame)")
}
}
It's because you have set the Align Top to the Safe Area. Just remember that this phone has a notch - so the safe area will be lower.
If you change the phone in your Storyboard to the iPhone Xs (as mentioned in the question), you will see that you have this gap. All you need to do is set the top constraint to the Superview rather than the Safe Area.
You should set layer frames in viewDidLayoutSubviews, not in viewDidLoad. Note that sizes of views change but layers added by you remain static because they are not updated automatically with their container views:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.previewLayer.frame = self.camViewOutlet.bounds
self.previewFrame = previewLayer.frame
}
Also note the difference between frame and bounds. camViewOutlet.frame is relative to its superview (self.view) but previewLayer is put inside camViewOutlet, therefore you have to use camViewOutlet.bounds. Basically, because camViewOutlet is put X points below the top of the screen (the height of the safe area), previewLayer is also put X points below the top of camViewOutlet.
There are other small issues in your code.
Note that viewWillAppear must call super.viewWillAppear and it can be called multiple times, therefore you should never add views and layers inside it.
You should probably also not start capturing before viewDidAppear has been called.

overlay View over AVCaptureVideoPreviewLayer

Aim : To create an Overlay view over AVCaptureVideoPreviewLayer in LandscapeRight orientation
Problem : Unable to fit the overlay view exactly over AVCaptureVideoPreviewLayer.
The width of the overlay that is being drawn is two times that of the screen.
This is my overlay View that i want to be drawn over the camera view
This is what i am able to achieve after doing the following.
My Code
let previewLayer: AVCaptureVideoPreviewLayer = AVCaptureVideoPreviewLayer(session: self.avCaptureSession)
previewLayer.frame = self.view.layer.frame
previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
self.view.layer.addSublayer(previewLayer)
let cameraOverlay = CameraOverlay(nibName:"CameraOverlay",bundle: nil)
let cameraOverlayView:CameraOverlayView = cameraOverlay.view as! CameraOverlayView
let previewView = UIView(frame: view.frame)
self.view.addSubview(previewView)
previewView.layer.addSublayer(previewLayer)
self.view.addSubview(cameraOverlayView)
self.avCaptureSession?.startRunning()

Partial-screen video preview distorted regardless of UIViewContentMode selection

I'm trying to make a simple app which will show in the top half of the iphone screen a raw preview of what the back camera sees, while in the bottom half the same preview but with various filters applied.
I first got the raw preview part working, not too hard thanks to several SO and blog posts. The UIImageView I'm displaying to takes up the entire screen for that part.
To get a half-screen view I just divide the image view's height by two, then set its contentMode to show everything while keeping the same aspect ratio:
imageView = UIImageView(frame: CGRectMake(0,0, self.view.frame.size.width, self.view.frame.size.height/2))
imageView.contentMode = UIViewContentMode.ScaleAspectFit
The height reduction works, but the image in the view is compressed vertically (e.g. a coin viewed straight-on looks like a horizontal oval). I don't think it's a coincidence that the appearance of the preview looks like the contentMode default ScaleToFill, but nothing I've tried changes the mode.
The complete code is below - the project has one scene with one view controller class; everything's done programatically.
Thanks!
import UIKit
import AVFoundation
class ViewController: UIViewController, AVCaptureVideoDataOutputSampleBufferDelegate
{
var imageView : UIImageView!
override func viewDidLoad()
{
super.viewDidLoad()
imageView = UIImageView(frame: CGRectMake(0,0, self.view.frame.size.width, self.view.frame.size.height/2))
imageView.contentMode = UIViewContentMode.ScaleAspectFit
view.addSubview(imageView)
let captureSession = AVCaptureSession()
captureSession.sessionPreset = AVCaptureSessionPresetHigh
let backCamera = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
do
{
let input = try AVCaptureDeviceInput(device: backCamera)
captureSession.addInput(input)
}
catch
{
print("Camera not available")
return
}
// Unused but required for AVCaptureVideoDataOutputSampleBufferDelegate:captureOutput() events to be fired
let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
view.layer.addSublayer(previewLayer)
let videoOutput = AVCaptureVideoDataOutput()
videoOutput.setSampleBufferDelegate(self, queue: dispatch_queue_create("SampleBufferDelegate", DISPATCH_QUEUE_SERIAL))
if captureSession.canAddOutput(videoOutput)
{
captureSession.addOutput(videoOutput)
}
videoOutput.connectionWithMediaType(AVMediaTypeVideo).videoOrientation = AVCaptureVideoOrientation.Portrait
captureSession.startRunning()
}
func captureOutput(captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, fromConnection connection: AVCaptureConnection!)
{
let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
let cameraImage = CIImage(CVPixelBuffer: pixelBuffer!)
dispatch_async(dispatch_get_main_queue())
{
self.imageView.image = UIImage(CIImage: cameraImage)
}
}
}
I guess this line has problem because it is in viewDidLoad() method.
Not sure it is related to your issue but I feel this is important note to you.
imageView = UIImageView(frame: CGRectMake(0,0, self.view.frame.size.width, self.view.frame.size.height/2))
You can not get correct view's size in viewDidLoad() function.
Furthermore, from iOS 10, I found controls are initially sized (0, 0, 1000, 1000) from storyboard before they are correctly laid out by iOS layout engine.
Also, the size you get in viewDidLoad() can be size of view controller in storyboard. So if you laid out controls in 4 Inch screen, it will return size of 4 inch screen in viewDidLoad() method even you run the app on iPhone6 or bigger screens.
Also, please set imageView.layer.maskToBounds property to true to prevent any out bounding of image.
One more thing is that you should place code laying out your image view appropriately when the view bounds changes (Like rotation of screen).

AVCaptureVideoPreviewLayer isn't showing centre of preview

I'm trying to display a video preview for a custom camera view in my app, however at the moment the preview is very much off centre - especially noticeable when using the front camera.
I'm positioning it like so:
if let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) {
previewLayer.bounds = scanningView.bounds
previewLayer.position = CGPointMake(CGRectGetMidX(scanningView.bounds), CGRectGetMidY(scanningView.bounds))
previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
scanningView.layer.insertSublayer(previewLayer, atIndex: 0)
}
However the centre of the preview is not in the centre of the view it is in. How can I make this happen?
Not sure you need the CGRectGetMidX/Y just place give the preview layer a frame equal to the hosting view.
previewLayer.videoGravity = AVLayerVideoGravityResizeAspect
scanningView.layer.addSublayer(previewLayer)
Also, you using auto layout? If so you might want adjust the frame when viewWillLayoutSubviews gets called
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
previewLayer.frame = scanningView.bounds
}
Instead of adding a layer and setting frame it seems better to use custom UIView and assign its subLayer class to AVCaptureVideoPreviewLayer. See the example below
final class PreviewView: UIView {
// 1
override class var layerClass: AnyClass {
return AVCaptureVideoPreviewLayer.self
}
.
.
I found the solution this way.

How should I properly add constraints to an AVCaptureVideoPreviewLayer?

I have a Navigation Bar at the top of my view. I'm adding an AVCaptureVideoPreviewLayer but the way it gets positioned, there is a gap between the bottom of the Nav Bar and the top of the Preview Layer.
self.view.backgroundColor = UIColor.redColor()
var navBarFrame = CGRectMake(0, 0, self.view.frame.width, 64.0)
var navBar = UINavigationBar(frame: navBarFrame)
var navItem = UINavigationItem()
navItem.title = "zzzz"
navBar.pushNavigationItem(navItem, animated: false)
self.view.addSubview(navBar)
// init device input
var error: NSErrorPointer!
var deviceInput: AVCaptureInput = AVCaptureDeviceInput.deviceInputWithDevice(captureDevice, error: error) as AVCaptureInput
self.stillImageOutput = AVCaptureStillImageOutput()
// init session
self.session = AVCaptureSession()
self.session.sessionPreset = AVCaptureSessionPresetPhoto
self.session.addInput(deviceInput as AVCaptureInput)
self.session.addOutput(self.stillImageOutput)
// layer for preview
var previewLayer: AVCaptureVideoPreviewLayer = AVCaptureVideoPreviewLayer.layerWithSession(self.session) as AVCaptureVideoPreviewLayer
previewLayer.frame = self.view.bounds
self.view.layer.addSublayer(previewLayer)
How can I ensure that the Preview Layer is constrained to the bottom of the Nav Bar?
Design a view in IB with the constraints you want. Add the preview layer to this view instead, you also need to update your layer size when the view size changes, use the layoutSubviews method from your view controller to get updates when the size changes.

Resources