Reinitializing barcode reader when viewWillAppear (again) - ios

I got a barcode reader that correctly initialize the first time the view is loaded. However when I go back to the view the video feed stops working.
The code below shows how is initialized in the viewDidLoad method. Any suggestion on how to modify it so I can call part of it when the viewWillAppear (again)?
Code:
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.
let captureDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
// Get an instance of the AVCaptureDeviceInput class using the previous device object.
var error:NSError?
let input: AnyObject! = AVCaptureDeviceInput.deviceInputWithDevice(captureDevice, error: &error)
if (error != nil) {
// If any error occurs, simply log the description of it and don't continue any more.
println("\(error?.localizedDescription)")
return
}
// Initialize the captureSession object.
captureSession = AVCaptureSession()
// Set the input device on the capture session.
captureSession?.addInput(input as AVCaptureInput)
// Initialize a AVCaptureMetadataOutput object and set it as the output device to the capture session.
let captureMetadataOutput = AVCaptureMetadataOutput()
captureSession?.addOutput(captureMetadataOutput)
// Set delegate and use the default dispatch queue to execute the call back
captureMetadataOutput.setMetadataObjectsDelegate(self, queue: dispatch_get_main_queue())
captureMetadataOutput.metadataObjectTypes = supportedBarCodes
// Initialize the video preview layer and add it as a sublayer to the viewPreview view's layer.
videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
//videoPreviewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill
var tmpbounds=view.layer.bounds;
videoPreviewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill;
videoPreviewLayer?.bounds = tmpbounds
videoPreviewLayer?.position = CGPointMake(CGRectGetMidX(tmpbounds), CGRectGetMidY(tmpbounds))
// videoPreviewLayer?.videoGravity = AVLayerVideoGravityResizeAspect
videoPreviewLayer?.frame = view.layer.bounds
view.layer.addSublayer(videoPreviewLayer)
// Start video capture.
captureSession?.startRunning()
// Move the message label to the top view
view.bringSubviewToFront(messageLabel)
// Initialize QR Code Frame to highlight the QR code
qrCodeFrameView = UIView()
qrCodeFrameView?.layer.borderColor = UIColor.greenColor().CGColor
qrCodeFrameView?.layer.borderWidth = 2
view.addSubview(qrCodeFrameView!)
view.bringSubviewToFront(qrCodeFrameView!)
}

try to execute on viewDidAppear
captureSession?.startRunning()
on viewWillDissapear
captureSession?.stopRunning()
Also register for notification AVCaptureSessionRuntimeErrorNotification and log all errors. I think it will help you to understand what going wrong.

You are setting up the captureSession and the videoPreviewLayer in viewDidLoad. That is only run when the ViewController is created. You need to move some code to viewDidAppear, which will run each time this view appears. Most simply, move all the code from this line:
captureSession = AVCaptureSession()
onwards into viewDidAppear.

Related

Unsupported type found - AVMetadataObject

I'm trying to implement barcode scanning in my iOS app, but for some reason I can't open the camera. I'm using Apple's AVFoundation framework for scanning.
This is my code for launching the camera and implement the scanner:
// Get the back-facing camera for capturing videos
let deviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaType.video, position: .back)
guard let captureDevice = deviceDiscoverySession.devices.first else {
print("Failed to get the camera device")
return
}
do {
// Get an instance of the AVCaptureDeviceInput class using the previous device object.
let input = try AVCaptureDeviceInput(device: captureDevice)
// Set the input device on the capture session.
captureSession?.addInput(input)
// Initialize a AVCaptureMetadataOutput object and set it as the output device to the capture session.
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]
// Initialize 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
view.layer.addSublayer(videoPreviewLayer!)
// Start video capture.
captureSession?.startRunning()
// Initialize QR Code Frame to highlight the QR code
qrCodeFrameView = UIView()
if let qrCodeFrameView = qrCodeFrameView {
qrCodeFrameView.layer.borderColor = UIColor.green.cgColor
qrCodeFrameView.layer.borderWidth = 2
view.addSubview(qrCodeFrameView)
view.bringSubview(toFront: qrCodeFrameView)
}
} catch {
// If any error occurs, simply print it out and don't continue any more.
print(error)
return
}
Immediately as the screen loads, I get the following message printed to the console:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[AVCaptureMetadataOutput setMetadataObjectTypes:] Unsupported type found - use -availableMetadataObjectTypes'
*** First throw call stack:
(0x1824d2d8c 0x18168c5ec 0x18803eb44 0x1013f035c 0x1013f12ec 0x18c23e64c 0x18c35f870 0x18c244700 0x18c37a1a8 0x18c2c19e0 0x18c2b6890 0x18c2b51d0 0x18ca96d1c 0x18ca992c8 0x18ca92368 0x18247b404 0x18247ac2c 0x18247879c 0x182398da8 0x18437d020 0x18c3b5758 0x1013da1d8 0x181e29fc0)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)
I couldn't find any solution on Stack Overflow or on any other website.
Could you please help me fixing this?
So I had the same error and I solved it by making small changes. Hope this helps(if you haven't resolved it already!).
let input = try AVCaptureDeviceInput(device: captureDevice)
//Make sure you have initialized the captureSession object
captureSession = AVCaptureSession()
captureSession?.addInput(input)
videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession!)
videoPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
videoPreviewLayer?.frame = view.layer.bounds
view.layer.addSublayer(videoPreviewLayer!)
let captureMetadataOutput = AVCaptureMetadataOutput()
captureSession?.addOutput(captureMetadataOutput)
// Main Change
captureMetadataOutput.metadataObjectTypes = captureMetadataOutput.availableMetadataObjectTypes
//[AVMetadataObject.ObjectType.qr]
captureMetadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
captureSession?.startRunning()

AVCaptureMetadataOutput Region of Interest for Barcodes

I am looking to display a UIView subclass within a UIStackView. The subclass is called PLBarcodeScannerView and is using AVCaptureMetadataOuput to detect barcodes within the camera's field of view. Because this view is not the entire screen, I need to set the region of interest to be the same as the frame of the PLBarcodeScannerView because the user is only seeing a portion of the camera view and we want to be sure that the barcode in the visible view is the one being scanned.
Issue
I cannot seem to set the metadataOutputRectOfInterest properly, nor does the "zoom level" of the preview layer on this view seem correct, although the aspect ratio is correct. The system does receive barcodes successfully, but they are not always visible within the preview window. Codes are still scanned when they reside outside the visible preview window.
Screenshot:
The colorful photo is the PLBarcodeScannerView. Only codes which are fully visible inside this view should be considered.
Below is the code that initializes the view:
This is called within the init methods of PLBarcodeScannerView:UIView
func setupView() {
session = AVCaptureSession()
let tap = UITapGestureRecognizer(target: self, action: #selector(self.resume))
addGestureRecognizer(tap)
// Set the captureDevice.
let videoCaptureDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
// Create input object.
let videoInput: AVCaptureDeviceInput?
do {
videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice)
} catch {
return
}
// Add input to the session.
if (session!.canAddInput(videoInput)) {
session!.addInput(videoInput)
} else {
scanningNotPossible()
}
// Create output object.
let metadataOutput = AVCaptureMetadataOutput()
// Add output to the session.
if (session!.canAddOutput(metadataOutput)) {
session!.addOutput(metadataOutput)
// Send captured data to the delegate object via a serial queue.
metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
// Set barcode type for which to scan: EAN-13.
metadataOutput.metadataObjectTypes = [
AVMetadataObjectTypeCode128Code
]
} else {
scanningNotPossible()
}
// Determine the size of the region of interest
let x = self.frame.origin.x/UIScreen.main.bounds.width
let y = self.frame.origin.y/UIScreen.main.bounds.height
let width = self.frame.width/UIScreen.main.bounds.height
let height = self.frame.height/UIScreen.main.bounds.height
let scanRectTransformed = CGRect(x: x, y: y, width: 1, height: height)
metadataOutput.metadataOutputRectOfInterest(for: scanRectTransformed)
// Add previewLayer and have it show the video data.
previewLayer = AVCaptureVideoPreviewLayer(session: session)
previewLayer.frame = self.bounds
previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
layer.addSublayer(previewLayer)
// Begin the capture session.
session!.startRunning()
}

How to apply filter to Video real-time using Swift

is possible to apply filter to AVLayer and add it to view as addSublayer? I want to change colors and add some noise to video from camera using Swift and I don't know how.
I thought, that is possible to add filterLayer and previewLayer like this:
self.view.layer.addSublayer(previewLayer)
self.view.layer.addSublayer(filterLayer)
and this can maybe create video with my custom filter, but I think, that is possible to do that more effectively usign AVComposition
So what I need to know:
What is simplest way to apply filter to camera video output realtime?
Is possible to merge AVCaptureVideoPreviewLayer and CALayer?
Thanks for every suggestion..
There's another alternative, use an AVCaptureSession to create instances of CIImage to which you can apply CIFilters (of which there are loads, from blurs to color correction to VFX).
Here's an example using the ComicBook effect. In a nutshell, create an AVCaptureSession:
let captureSession = AVCaptureSession()
captureSession.sessionPreset = AVCaptureSessionPresetPhoto
Create an AVCaptureDevice to represent the camera, here I'm setting the back camera:
let backCamera = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
Then create a concrete implementation of the device and attach it to the session. In Swift 2, instantiating AVCaptureDeviceInput can throw an error, so we need to catch that:
do
{
let input = try AVCaptureDeviceInput(device: backCamera)
captureSession.addInput(input)
}
catch
{
print("can't access camera")
return
}
Now, here's a little 'gotcha': although we don't actually use an AVCaptureVideoPreviewLayer but it's required to get the sample delegate working, so we create one of those:
// although we don't use this, it's required to get captureOutput invoked
let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
view.layer.addSublayer(previewLayer)
Next, we create a video output, AVCaptureVideoDataOutput which we'll use to access the video feed:
let videoOutput = AVCaptureVideoDataOutput()
Ensuring that self implements AVCaptureVideoDataOutputSampleBufferDelegate, we can set the sample buffer delegate on the video output:
videoOutput.setSampleBufferDelegate(self,
queue: dispatch_queue_create("sample buffer delegate", DISPATCH_QUEUE_SERIAL))
The video output is then attached to the capture session:
captureSession.addOutput(videoOutput)
...and, finally, we start the capture session:
captureSession.startRunning()
Because we've set the delegate, captureOutput will be invoked with each frame capture. captureOutput is passed a sample buffer of type CMSampleBuffer and it just takes two lines of code to convert that data to a CIImage for Core Image to handle:
let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
let cameraImage = CIImage(CVPixelBuffer: pixelBuffer!)
...and that image data is passed to our Comic Book effect which, in turn, is used to populate an image view:
let comicEffect = CIFilter(name: "CIComicEffect")
comicEffect!.setValue(cameraImage, forKey: kCIInputImageKey)
let filteredImage = UIImage(CIImage: comicEffect!.valueForKey(kCIOutputImageKey) as! CIImage!)
dispatch_async(dispatch_get_main_queue())
{
self.imageView.image = filteredImage
}
I have the source code for this project available in my GitHub repo here.
If you're using an AVPlayerViewController, you can set the compositingFilter property of the view's layer:
playerController.view.layer.compositingFilter = "multiplyBlendMode"
See here for the compositing filter options you can use. e.g. "multiplyBlendMode", "screenBlendMode", etc.
Example of doing this in a UIViewController:
class ViewController : UIViewController{
override func viewDidLoad() {
//load a movie called my_movie.mp4 that's in your xcode project
let path = Bundle.main.path(forResource: "my_movie", ofType:"mp4")
let player = AVPlayer(url: URL(fileURLWithPath: path!))
//make a movie player and set the filter
let playerController = AVPlayerViewController()
playerController.player = player
playerController.view.layer.compositingFilter = "multiplyBlendMode"
//add the player view controller to this view controller
self.addChild(playerController)
view.addSubview(playerController.view)
playerController.didMove(toParent: self)
//play the movie
player.play()
}
}
For let path = Bundle.main.path(forResource: "my_movie", ofType:"mp4"), make sure you add the .mp4 file to Build Phases > Copy Bundle Resources in your Xcode project. Or check the 'add to target' boxes when you import the file.

Swift 2 Extra argument error in call viewdidload image

i'm updating my app to Swift 2 with Xcode 7. this is my code of a ViewController viewDidLoad.
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.
let captureDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
// Get an instance of the AVCaptureDeviceInput class using the previous device object.
var error:NSError?
let input: AnyObject! = AVCaptureDeviceInput.deviceInputWithDevice(captureDevice, error: &error)
if (error != nil) {
// If any error occurs, simply log the description of it and don't continue any more.
print("\(error?.localizedDescription)")
return
}
// Initialize the captureSession object.
captureSession = AVCaptureSession()
// Set the input device on the capture session.
captureSession?.addInput(input as! AVCaptureInput)
// Initialize a AVCaptureMetadataOutput object and set it as the output device to the capture session.
let captureMetadataOutput = AVCaptureMetadataOutput()
captureSession?.addOutput(captureMetadataOutput)
// Set delegate and use the default dispatch queue to execute the call back
captureMetadataOutput.setMetadataObjectsDelegate(self, queue: dispatch_get_main_queue())
captureMetadataOutput.metadataObjectTypes = supportedBarCodes
// Initialize the video preview layer and add it as a sublayer to the viewPreview view's layer.
videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
videoPreviewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill
videoPreviewLayer?.frame = view.layer.bounds
view.layer.addSublayer(videoPreviewLayer!)
// Start video capture.
captureSession?.startRunning()
// Move the message label to the top view
view.bringSubviewToFront(messageLabel)
// Initialize QR Code Frame to highlight the QR code
qrCodeFrameView = UIView()
qrCodeFrameView?.layer.borderColor = UIColor.greenColor().CGColor
qrCodeFrameView?.layer.borderWidth = 2
view.addSubview(qrCodeFrameView!)
view.bringSubviewToFront(qrCodeFrameView!)
}
on line
let input: AnyObject! = AVCaptureDeviceInput.deviceInputWithDevice(captureDevice, error: &error)
i get the error Extra argument error in call. I already tried with the method do{} and catch{} but it didn't work, i always get that error.
How can i fix that? Thanks
Swift 2 introduced new error handling. To solve the problem you are having, you need to catch the error instead of passing an NSError object to the AVCaptureDevice method:
override func viewDidLoad() {
super.viewDidLoad()
do {
let captureDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
let input = try AVCaptureDeviceInput(device: captureDevice)
// Do the rest of your work...
} catch let error as NSError {
// Handle any errors
print(error)
}
}
For a more in-depth explanation have a look at this article:
Error Handling in Swift 2.0
it doesn't look like that type method exists anymore on AVCaptureDeviceInput, see -> https://developer.apple.com/library/prerelease/ios/documentation/AVFoundation/Reference/AVCaptureDeviceInput_Class/index.html#//apple_ref/swift/cl/c:objc(cs)AVCaptureDeviceInput
(it looks like you probably want to use init(device:))
...as a handy tip: anytime you're browsing the developer library via the web, if you're not sure if you're seeing the latest 'prerelease' version of the documentation check the URL -> add '/prerelease' between 'library' and '/ios' if necessary :)

How to draw camera video as background on IOS using Swift scenekit?

I am trying to develop an augmented reality app using swift and scenekit on ios. Is there a way to draw the video captured by the device camera as a background of the scene?
This worked for me,
I used AVFoundation to capture the video input of the device camera:
let captureSession = AVCaptureSession()
let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
if let videoDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo) {
var err: NSError? = nil
if let videoIn : AVCaptureDeviceInput = AVCaptureDeviceInput.deviceInputWithDevice(videoDevice, error: &err) as? AVCaptureDeviceInput {
if(err == nil){
if (captureSession.canAddInput(videoIn as AVCaptureInput)){
captureSession.addInput(videoIn as AVCaptureDeviceInput)
}
else {
println("Failed to add video input.")
}
}
else {
println("Failed to create video input.")
}
}
else {
println("Failed to create video capture device.")
}
}
captureSession.startRunning()
At this point, based on Apple's documentation of the background property of SCNScene, I expected to add the instance of AVCaptureVideoPreviewLayer to an SCNScene's background.contents, with something like:
previewLayer.frame = sceneView.bounds
sceneView.scene.background.contents = previewLayer
This changed the background color of my scene from default white to black, but offered no video input. This may be an iOS bug?.
So Plan B. Instead, I added the 'AVCaptureVideoPreviewLayer' as a sublayer of a UIView's layer:
previewLayer.frame = self.view.bounds
self.view.layer.addSublayer(previewLayer)
I then set an SCNView as a subview of that same UIView, setting the SCNView's background color to clear:
let sceneView = SCNView()
sceneView.frame = self.view.bounds
sceneView.backgroundColor = UIColor.clearColor()
self.view.addSubview(sceneView)
The device camera's video is now visible as the background of the scene.
I've created a small demo.
As of iOS 11, you can now use an AVCaptureDevice as a material on an object or the scene's background (see "Using Animated Content" here)
Example:
let captureDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)!
scnScene.background.contents = captureDevice
Yes, you can use a AVCaptureVideoPreviewLayer as the contents of a material property (just make sure that you give the layer a bounds).
The scene view has a material property for the background that you can assign the video preview to (assign the layer to the background's contents).
var background: SCNMaterialProperty! { get }

Resources