How should I properly add constraints to an AVCaptureVideoPreviewLayer? - ios

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.

Related

AVPlayer get rid of black bars/background at top & bottom (Swift)

Quick question, does anybody know how i can get rid of the black bars at the top and bottom of my video? I just started using AVPlayer and i'm just removing codes here and there in attempt to remove the black layers. Appreciate those who can help me, Thanks!
UIViewController
import UIKit
import AVKit
import AVFoundation
private var looper: AVPlayerLooper?
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let path = Bundle.main.path(forResource: "Innie-Kiss", ofType:"mp4")
let player = AVQueuePlayer(url: URL(fileURLWithPath: path!))
looper = AVPlayerLooper(player: player, templateItem: AVPlayerItem(asset: AVAsset(url: URL(fileURLWithPath: path!))))
let controller = AVPlayerViewController()
controller.player = player
let screenSize = UIScreen.main.bounds.size
let videoFrame = CGRect(x: 0, y: 200, width: screenSize.width, height: (screenSize.height - 130) / 2)
controller.view.frame = videoFrame
self.view.addSubview(controller.view)
player.play()
}
}
You need to set the videoGravity of the AVLayer. The default value is .resizeAspect which will preserve the aspect ratio with black bars on top/bottom or left/right. What you want is .resize
The AVLayer you need to set the videoGravity on is the layer property of your AVPlayer.
see this for more info: https://developer.apple.com/documentation/avfoundation/avplayerlayer/1388915-videogravity
#import AVKit
var playerController = AVPlayerViewController()
playerController.videoGravity = .resizeAspect
/*playerController.videoGravity = .resizeAspectFill*/
playerController.view.backgroundColor = UIColor.white //set color as per super or custom view.
Note: If you set the videoGravity of the AVPlayerViewController. The default value is .resizeAspect then it is possible to visibility of black color(default) in background if you wants this color would similar to your super view's color then you must set superview's or custom view's color to your playercontroller's view background color.
*Example:Suppose super view's or custom view's background color is white then playerController view's background color must be set as playerController.view.backgroundColor = UIColor.white *
Note :playerController's backgroundColor should not be set as UIColor.white ,always. It must me set as super view' background color.
If you set playerController.videoGravity = .resizeAspectFill then it will fill the player content to super view or custom view, here also no black color would be shown. So choose either .resizeAspect or resizeAspectFill as per your requirements.

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 to set camera view to fill it's parent view

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.

Resources