Fill view with IOS camera preview layer - ios

Working through a tutorial, and I'm trying to make a full screen preview. Currently, my camera seems very square, and I am fairly confident this might be an aspect fit issue. The camera seems to hit the right and left bounds. How can I pin the my preview to the bottom of the nav bar and the bottom of the screen, and the sides?
import UIKit
import AVFoundation
class HomeViewController: UIViewController, AVCaptureVideoDataOutputSampleBufferDelegate {
let captureSession = AVCaptureSession()
var previewLayer:CALayer!
var captureDevice:AVCaptureDevice!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
prepareCamera()
}
func prepareCamera(){
captureSession.sessionPreset = AVCaptureSessionPresetPhoto
if let availableDevices = AVCaptureDeviceDiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaTypeVideo, position: .back).devices {
captureDevice = availableDevices.first
beginSession()
}
}
func beginSession () {
do {
let captureDeviceInput = try AVCaptureDeviceInput(device: captureDevice)
captureSession.addInput(captureDeviceInput)
} catch {
print(error.localizedDescription)
}
if let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) {
self.previewLayer = previewLayer
self.view.layer.addSublayer(self.previewLayer)
self.previewLayer.frame = self.view.layer.frame
//ADD CONSTRAINTS HERE
captureSession.startRunning()
let dataOutput = AVCaptureVideoDataOutput()
dataOutput.videoSettings = [(kCVPixelBufferPixelFormatTypeKey as NSString):NSNumber(value:kCVPixelFormatType_32BGRA)]
dataOutput.alwaysDiscardsLateVideoFrames = true
if captureSession.canAddOutput(dataOutput) {
captureSession.addOutput(dataOutput)
}
captureSession.commitConfiguration()
// let queue = DispatchQueue(label: com.willkie.captureQueue)
// dataOutput.setSampleBufferDelegate(self, queue: queue)
}
}
func captureOutput(_ captureOutput: AVCaptureOutput!, didDrop sampleBuffer: CMSampleBuffer!, from connection: AVCaptureConnection!) {
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

I think probably changing your this particular line should work -
self.previewLayer.frame = self.view.layer.frame
to
self.previewLayer.frame = self.view.layer.bounds
Also you should add your sublayer only after you have adjusted the frame in the above code. Right now you have added before.
self.view.layer.addSublayer(self.previewLayer)
And you also need to add gravity
previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
If it still doesn't work, add a custom view to your viewcontroller and set it to the size on which you want to show the camera preview and then you can use this -
self.previewLayer.frame = self.customViewOutlet.layer.bounds

I solved it with Swift 4 like this:
Don't forget to import AVKit
override func viewDidLoad() {
super.viewDidLoad()
let captureSession = AVCaptureSession()
guard let captureDevice = AVCaptureDevice.default(for: .video),
let input = try? AVCaptureDeviceInput(device: captureDevice) else {return}
captureSession.addInput(input)
captureSession.startRunning()
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer.frame = view.frame
previewLayer.videoGravity = .resizeAspectFill
view.layer.addSublayer(previewLayer)
}

I am not sure this is the best way to go about this, but what worked for me was setting it to the screen size frame. So in that case it would be:
self.previewLayer.frame = UIScreen.main.bounds
Hope this helps

Related

Camera View adjusting

I'm not too experienced with Swift or Xcode so any help would be appreciated!
I have made a separate .swift file for my QR/Camera Controller. I found this tutorial online on how to make a QR Code Reader and I typed in the Code provided and everything is fine except the Camera View isn't appearing properly on the Screen (using iPhone 8). How can I adjust the Video View?
Code:
import UIKit
import AVFoundation
class CameraController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate, AVCapturePhotoCaptureDelegate, AVCaptureMetadataOutputObjectsDelegate {
#IBOutlet weak var previewView: UIView!
#IBOutlet weak var lblOutput: UILabel!
var imageOrientation: AVCaptureVideoOrientation?
var captureSession: AVCaptureSession?
var videoPreviewLayer: AVCaptureVideoPreviewLayer?
var capturePhotoOutput: AVCapturePhotoOutput?
override func viewDidLoad() {
super.viewDidLoad()
// Get an instance of the AVCaptureDevice class to initialize a
// device object and provide the video as the media type parameter
guard let captureDevice = AVCaptureDevice.default(for: AVMediaType.video) else {
fatalError("No video device found")
}
// handler chiamato quando viene cambiato orientamento
self.imageOrientation = AVCaptureVideoOrientation.portrait
do {
// Get an instance of the AVCaptureDeviceInput class using the previous deivce object
let input = try AVCaptureDeviceInput(device: captureDevice)
// Initialize the captureSession object
captureSession = AVCaptureSession()
// Set the input device on the capture session
captureSession?.addInput(input)
// Get an instance of ACCapturePhotoOutput class
capturePhotoOutput = AVCapturePhotoOutput()
capturePhotoOutput?.isHighResolutionCaptureEnabled = true
// Set the output on the capture session
captureSession?.addOutput(capturePhotoOutput!)
captureSession?.sessionPreset = .high
// Initialize a AVCaptureMetadataOutput object and set it as the input device
let captureMetadataOutput = AVCaptureMetadataOutput()
captureSession?.addOutput(captureMetadataOutput)
// Set delegate and use the default dispatch queue to execute the call back
captureMetadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
captureMetadataOutput.metadataObjectTypes = [AVMetadataObject.ObjectType.qr]
//Initialise the video preview layer and add it as a sublayer to the viewPreview view's layer
videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession!)
videoPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
videoPreviewLayer?.frame = view.layer.bounds
previewView.layer.addSublayer(videoPreviewLayer!)
//start video capture
captureSession?.startRunning()
} catch {
//If any error occurs, simply print it out
print(error)
return
}
}
override func viewWillAppear(_ animated: Bool) {
navigationController?.setNavigationBarHidden(true, animated: false)
self.captureSession?.startRunning()
}
// Find a camera with the specified AVCaptureDevicePosition, returning nil if one is not found
func cameraWithPosition(position: AVCaptureDevice.Position) -> AVCaptureDevice? {
let discoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaType.video, position: .unspecified)
for device in discoverySession.devices {
if device.position == position {
return device
}
}
return nil
}
func metadataOutput(_ captureOutput: AVCaptureMetadataOutput,
didOutput metadataObjects: [AVMetadataObject],
from connection: AVCaptureConnection) {
// Check if the metadataObjects array is contains at least one object.
if metadataObjects.count == 0 {
return
}
//self.captureSession?.stopRunning()
// Get the metadata object.
let metadataObj = metadataObjects[0] as! AVMetadataMachineReadableCodeObject
if metadataObj.type == AVMetadataObject.ObjectType.qr {
if let outputString = metadataObj.stringValue {
DispatchQueue.main.async {
print(outputString)
self.lblOutput.text = outputString
}
}
}
}
}
Image of current view:
The highlighted white box is the UIView
The mistake is you use frame of view but add videoPreviewLayer to the previewView which is smaller (like you showed in storyboard).
Replace the line with viewPreviewLayer frame configuration.
//Initialise the video preview layer and add it as a sublayer to the viewPreview view's layer
videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession!)
videoPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
videoPreviewLayer?.frame = view.layer.bounds
previewView.layer.addSublayer(videoPreviewLayer!)
this line
videoPreviewLayer?.frame = view.layer.bounds
to
videoPreviewLayer?.frame = previewView.layer.bounds
You should use NSLayoutConstraint from the storyboard.
Step #1
this is your current state
step #2
add top, leading, trailing and bottom constraint
step #3
final result
I would expect one of the following happens:
- You didn't setup your constraints properly
- Your view resizes
- You used incorrect view to set size of your layer
Setting up constraints is nearly impossible to explain by writing it. There are many ways of setting them up so I made a very short video that explains one way (or two) about setting up constraints.
The second and third can be explained in this snippet:
override func viewDidLoad() {
super.viewDidLoad()
...
videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession!)
videoPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
previewView.layer.addSublayer(videoPreviewLayer!)
updatePreviewLayerFrame()
...
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
updatePreviewLayerFrame()
}
private func updatePreviewLayerFrame() {
videoPreviewLayer?.frame = previewView.bounds
}
Overriding viewDidLayoutSubviews should resize your layer as this method is called whenever the view controller "resizes". It is also called shortly after the viewDidLoad. Also note that a previewView is used to determine the frame: videoPreviewLayer?.frame = previewView.bounds.
Layers do not automatically resize with their parent view. That means your videoPreviewLayer gets the frame from the original (not yet layouted) previewView and never changes it. To update the layer, you can override this method:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// you need to keep a reference for that
self.videoPreviewLayer.frame = self.previewView.bounds
}
Alternatively, and I think that's better, you can check out how the preview view is implemented in Apple's AVCam example app. Resizing will be handled by Auto Layout when using their approach.

iPhoneX: AVCaptureVideoPreviewLayer not occupying the entire screen?

I have made a custom camera in a UIViewController but I am not able to preview the camera output to the entire screen on the iPhoneX. There appears to be a substantial padding between the camera view and the edge of the screen. My view seems to have been even inset from the safe area for sure. Please can anyone advise?
iPhoneXs Max:
my code:
class CaptureImageViewController: UIViewController, UIImagePickerControllerDelegate, AVCaptureVideoDataOutputSampleBufferDelegate {
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
previewLayer = AVCaptureVideoPreviewLayer.init(session: self.captureSession)
// Use 'insertSublayer' to enable button to be viewable
view.layer.insertSublayer(previewLayer, at: 0)
previewLayer.frame = view.frame
previewFrame = previewLayer.frame
}
}
My layout:
Try to change previewLayer.frame = view.frame to
let frame = UIScreen.main.bounds
view.layer.insertSublayer(previewLayer, at: 0)
previewLayer.frame = frame
previewFrame = previewLayer.frame

Camera preview layer makes button invisible

I'm trying to add a button over a camera preview but it doesn't show up when I run the program (I have constraints). I looked into the code and tried to debug but I'm new to swift and Xcode and I'm new to debugging in general. I saw that when I commented out the camera preview layer the button showed up. Thanks!
import UIKit
import AVFoundation
import QuartzCore
class View1: UIViewController , AVCaptureVideoDataOutputSampleBufferDelegate{
let captureSession = AVCaptureSession()
var previewLayer:CALayer!
var captureDevice:AVCaptureDevice!
#IBOutlet weak var cameraView:UIView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
prepareCamera()
}
func prepareCamera() {
captureSession.sessionPreset = AVCaptureSessionPreset1920x1080
if let availableDevices = AVCaptureDeviceDiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaTypeVideo, position: .back).devices {
captureDevice = availableDevices.first
beginSession()
}
}
func beginSession() {
do {
let captureDeviceInput = try AVCaptureDeviceInput(device: captureDevice)
captureSession.addInput(captureDeviceInput)
} catch {
print(error.localizedDescription)
//Figure out what to do here
}
if let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) {
self.previewLayer = previewLayer
self.view.layer.addSublayer(self.previewLayer)
self.previewLayer.frame = self.view.layer.frame
captureSession.startRunning()
let dataOutput = AVCaptureVideoDataOutput()
dataOutput.videoSettings = [(kCVPixelBufferPixelFormatTypeKey as NSString): NSNumber(value: kCVPixelFormatType_32BGRA)]
dataOutput.alwaysDiscardsLateVideoFrames = true
if captureSession.canAddOutput(dataOutput){
captureSession.addOutput(dataOutput)
captureSession.commitConfiguration()
}
let queue = DispatchQueue(label: "com.PhotoAllergy.captureQueue")
dataOutput.setSampleBufferDelegate(self, queue: queue)
}
}
}
Maybe you could try setting the zPosition of the button to 1 or higher. YourButtonName.layer.zPostion = 2
Apple Documentation on ZPosition
You just have to add button as subview to view that is working as a preview for your avcamera.
class RecordVC {
#IBOutlet weak var vwRecordVideo : UIView!
#IBOutlet weak var btnGallary : UIButton!
override func viewDidLoad() {
super.viewDidLoad()
vwRecordVideo.addSubview(cameraButton)
vwRecordVideo.addSubview(btnGallary)
}
}

Can't see my button when I run the program on my iPhone

Why can't I see my button when I run my program but in Xcode 8 I can see it. It's over a view and looks like the button you press to take a photo with in snapchat.
I'm new to Xcode and swift so if there is anything I need to know with Xcode or the storyboard that can help me with these problems please tell me.
The code for the view:
import UIKit
import AVFoundation
import QuartzCore
class View1: UIViewController , AVCaptureVideoDataOutputSampleBufferDelegate{
let captureSession = AVCaptureSession()
var previewLayer: CALayer!
var captureDevice: AVCaptureDevice!
#IBOutlet weak var cameraView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
}
/*
This is a function to prepair the camera
and check that there is a camera.
If there isn't a camera on the device
then get you will get a error.
*/
func prepareCamera()
{
captureSession.sessionPreset = AVCaptureSessionPreset1920x1080
if let availableDevices = AVCaptureDeviceDiscoverySession(deviceTypes: [.builtInWideAngleCamera],
mediaType: AVMediaTypeVideo,
position: .back).devices
{
captureDevice = availableDevices.first
beginSession()
}
}
func beginSession()
{
do
{
let captureDeviceInput = try AVCaptureDeviceInput(device: captureDevice)
captureSession.addInput(captureDeviceInput)
}
catch
{
print(error.localizedDescription)
/*
Figure out what to do here
*/
}
if let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
{
self.previewLayer = previewLayer
self.view.layer.addSublayer(
self.previewLayer)
self.previewLayer.frame = self.view.layer.frame
self.previewLayer.frame.size = self.view.layer.frame.size
captureSession.startRunning()
let dataOutput = AVCaptureVideoDataOutput()
dataOutput.videoSettings = [(kCVPixelBufferPixelFormatTypeKey as NSString
): NSNumber(value: kCVPixelFormatType_32BGRA)]
dataOutput.alwaysDiscardsLateVideoFrames = true
if captureSession.canAddOutput(dataOutput)
{
captureSession.addOutput(dataOutput)
}
captureSession.commitConfiguration()
let queue = DispatchQueue(label: "com.PhotoAllergy.captureQueue")
dataOutput.setSampleBufferDelegate(self, queue: queue)
}
}
//func captureOutput(_ captureOutput: AVCaptureOutput!, didDrop sampleBuffer: CMSampleBuffer!, from connection: AVCaptureConnection!) {
//yeeye
//}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
prepareCamera()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
Add constraints.
Follow these images
Good luck, you can comment here if you have any questions about this.
do the following
select your button in the storyboard view controller and follow the steps in the screen shot
press the button add constraints
run the app
Please make the viewController as the initial view controller as specified in the image below

How to set AVCaptureVideoPreviewLayer as background in SKScene in Sprite Kit Swift 3

So I'm trying to build an augmented reality game with Sprite kit in swift 3. I am able to set the video for my camera as a background but it appears over my nodes. Basically, the nodes are underneath the AVCaptureVideoPreviewLayer. How can I make my nodes appear over the background (sort of similar to Pokémon go)? I have even tried to change the zPosition but this doesn't work. This is my code:
import SpriteKit
import AVFoundation
class GameScene: SKScene {
var captureSession: AVCaptureSession!
var previewLayer: AVCaptureVideoPreviewLayer!
override func didMove(to view: SKView) {
isPaused = false
addPlayer()
scene?.backgroundColor = .clear
captureSession = AVCaptureSession()
let videoCaptureDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
let videoInput: AVCaptureDeviceInput
do {
videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice)
} catch {
return
}
captureSession.startRunning()
if (captureSession.canAddInput(videoInput)) {
captureSession.addInput(videoInput)
} else {
return
}
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer.frame = view.layer.bounds
previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
view.layer.addSublayer(previewLayer)
captureSession.startRunning()
}

Resources