Reputation: 393
GOAL: I am using AVFoundation to a create custom camera that acts similar to Facebook, Instagram, and Snapchats camera image capturing sequence.
Below is my controller with the ideal UX:
Here is my Swift Storyboard of the above
PROBLEM: I am able to get a live video feed to using AVCapturePreviewLayer however once I capture my picture I am unable to transfer the captured UIImage to the second ViewController. I am using a segue that I trigger at the end captureStillImageAsynchronouslyFromConnection completion callback.
Here is the MasterViewController?
class AddPhotoViewController: UIViewController {
@IBOutlet var previewLayerView: UIView!
var captureSession: AVCaptureSession?
var previewLayer: AVCaptureVideoPreviewLayer?
var stillImageOutput: AVCaptureStillImageOutput?
var imageDetail: UIImage?
@IBAction func cancelCameraBtn(sender: AnyObject) {
self.navigationController?.popToRootViewControllerAnimated(true)
}
@IBAction func takePhotoBtn(sender: AnyObject) {
if let videoConnection = stillImageOutput!.connectionWithMediaType(AVMediaTypeVideo) {
videoConnection.videoOrientation = AVCaptureVideoOrientation.Portrait
stillImageOutput?.captureStillImageAsynchronouslyFromConnection(videoConnection, completionHandler: {(sampleBuffer, error) in
if (sampleBuffer != nil) {
let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(sampleBuffer)
let dataProvider = CGDataProviderCreateWithCFData(imageData)
let cgImageRef = CGImageCreateWithJPEGDataProvider(dataProvider, nil, true, CGColorRenderingIntent.RenderingIntentDefault)
self.imageDetail = UIImage(CGImage: cgImageRef!, scale: 1.0, orientation: UIImageOrientation.Right)
self.performSegueWithIdentifier("captureSessionDetailSegue", sender: self)
}
})
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewWillDisappear(animated: Bool) {
captureSession!.stopRunning()
self.navigationController?.setNavigationBarHidden(false, animated: false)
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// display properties
self.navigationController?.setNavigationBarHidden(true, animated: false)
captureSession = AVCaptureSession()
captureSession!.sessionPreset = AVCaptureSessionPresetPhoto
let backCamera = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
var error: NSError?
var input: AVCaptureDeviceInput!
do {
input = try AVCaptureDeviceInput(device: backCamera)
} catch let error1 as NSError {
error = error1
input = nil
}
if error == nil && captureSession!.canAddInput(input) {
captureSession!.addInput(input)
stillImageOutput = AVCaptureStillImageOutput()
stillImageOutput!.outputSettings = [AVVideoCodecKey: AVVideoCodecJPEG]
if captureSession!.canAddOutput(stillImageOutput) {
captureSession!.addOutput(stillImageOutput)
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer!.videoGravity = AVLayerVideoGravityResizeAspectFill
previewLayer!.connection?.videoOrientation = AVCaptureVideoOrientation.Portrait
previewLayerView.layer.addSublayer(previewLayer!)
//previewLayerView.layer.removeAllAnimations()
captureSession!.startRunning()
}
}
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
previewLayer!.frame = previewLayerView.bounds
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
//if segue.identifier == "captureSessionDetailSegue" {
let destination = segue.destinationViewController as! CaptureSessionDetailViewController
destination.capturedImage.image = self.imageDetail
// returns nil propertyfrom here
//destination.navigationController!.setNavigationBarHidden(true, animated: false)
//}
}
}
Here is the DetailViewController?
class CaptureSessionDetailViewController: UIViewController {
@IBOutlet var capturedImage: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
My current code yields fatal error: unexpectedly found nil while unwrapping an Optional value. I think its because of my prepareForSegue method setting something that is not present yet but I do not know how to get the image to the desired DetailViewController.
How can I achieve my desired results?
Upvotes: 2
Views: 2885
Reputation: 393
My solution used design patterns from the above user (Dharmesh Kheni) and the DBCamera custom camera github.
In AddPhotoViewController
@IBAction func takePhotoBtn(sender: AnyObject) {
if let videoConnection = stillImageOutput!.connectionWithMediaType(AVMediaTypeVideo) {
videoConnection.videoOrientation = AVCaptureVideoOrientation.Portrait
stillImageOutput?.captureStillImageAsynchronouslyFromConnection(videoConnection, completionHandler: {(sampleBuffer, error) in
if (sampleBuffer != nil) {
let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(sampleBuffer)
// Setup class variable --> imgMetaData: NSData!
// Assign and transport to destination ViewController
self.imgMetaData = imageData
self.performSegueWithIdentifier("captureSessionDetailSegue", sender: self)
}
})
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "captureSessionDetailSegue" {
let destination = segue.destinationViewController as! CaptureSessionDetailViewController
destination.capturedImageMetaData = self.imgMetaData
}
}
In CaptureSessionDetailViewController
class CaptureSessionDetailViewController: UIViewController {
var capturedImageMetaData: NSData!
@IBOutlet var capturedImage: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
let dataProvider = CGDataProviderCreateWithCFData(capturedImageMetaData)
let cgImageRef = CGImageCreateWithJPEGDataProvider(dataProvider, nil, true, CGColorRenderingIntent.RenderingIntentDefault)
let img = UIImage(CGImage: cgImageRef!, scale: 1.0, orientation: UIImageOrientation.Right)
capturedImage.image = img
// Do any additional setup after loading the view.
}
Image data from the AVCaptureStillImageOutput
was assign to class variable imgMetaData: NSData!
within the AddPhotoViewController
. The data was transferred with prepareForSegue
to the destination view controller CaptureSessionDetailViewController
and stored in capturedImageMEtaData: NSData!
. Then the data was converted to UIImage
in the viewDidLoad
method.
Upvotes: 2
Reputation: 71854
Don't assign image Directly like:
destination.capturedImage.image = self.imageDetail
But declare another instance which will hold your image into CaptureSessionDetailViewController
like shown below:
var capturedImageRef = UIImage()
Now you can assign image to CaptureSessionDetailViewController
from AddPhotoViewController
this way into your Segue
method:
destination.capturedImageRef = self.imageDetail
Now in viewDidLoad
of CaptureSessionDetailViewController
you can assign that image to imageView
:
capturedImage.image = capturedImageRef
Upvotes: 0