I'm getting started taking simple photos with my iPhone using Xcode and swift. I've boiled down a couple of sample projects to a simple class below, derived from AVCapturePhotoCaptureDelegate, with Initialize, TakePhoto methods, and a callback photoOutput -- i.e., dead simple. To test, I created a simple app with "Initialize" and "Take Photo" buttons.
When I take the photo and the "photoOutput.capturePhoto" is called, it halts with an exception "No active and enabled video connection."
Apparently I've missed something fundamental in the boil down. One thing I left out from the sample project is the preview layer... does this require an active preview to function? Is that what it means by lacking a "video" connection? why would it require that?
Or is there something else I'm missing?
Thanks very much.
import UIKit
import AVFoundation
class cPhotoCaptureNormal: NSObject, AVCapturePhotoCaptureDelegate
{
private let photoOutput = AVCapturePhotoOutput()
public func Initialize()
{
let captureSession = AVCaptureSession()
if let captureDevice = AVCaptureDevice.default(for: AVMediaType.video) {
do {
let input = try AVCaptureDeviceInput(device: captureDevice)
if captureSession.canAddInput(input) {
captureSession.addInput(input)
}
} catch let error {
print("Failed to set input device with error: \(error)")
}
if captureSession.canAddOutput(photoOutput) {
captureSession.addOutput(photoOutput)
}
else
{
print("Failed to add output device")
}
captureSession.startRunning()
}
}
public func TakePhoto()
{
let photoSettings = AVCapturePhotoSettings()
if let photoPreviewType = photoSettings.availablePreviewPhotoPixelFormatTypes.first {
photoSettings.previewPhotoFormat = [kCVPixelBufferPixelFormatTypeKey as String: photoPreviewType]
// EXCEPTION OCCURS ON FOLLOwING LINE
photoOutput.capturePhoto(with: photoSettings, delegate: self)
}
}
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
guard let imageData = photo.fileDataRepresentation() else { return }
let previewImage = UIImage(data: imageData)
// ... post process and save UIImage
}
}
Assuming you are calling your Initialize()
function prior to calling TakePhoto()
, the most likely cause of your issue that your AVCaptureSession
is going out of scope at the end of Initialize
. Make captureSession
a property instead of a local variable. This will keep the session alive and allow you take a photo.
You should also call captureSession.stopRunning()
at some point.
class cPhotoCaptureNormal: NSObject, AVCapturePhotoCaptureDelegate {
private let photoOutput = AVCapturePhotoOutput()
private let captureSession = AVCaptureSession()
public func initialize() {
if let captureDevice = AVCaptureDevice.default(for: AVMediaType.video) {
do {
let input = try AVCaptureDeviceInput(device: captureDevice)
if captureSession.canAddInput(input) {
captureSession.addInput(input)
}
} catch let error {
print("Failed to set input device with error: \(error)")
}
if captureSession.canAddOutput(photoOutput) {
captureSession.addOutput(photoOutput)
} else {
print("Failed to add output device")
}
captureSession.startRunning()
}
}
public func takePhoto() {
let photoSettings = AVCapturePhotoSettings()
if let photoPreviewType = photoSettings.availablePreviewPhotoPixelFormatTypes.first {
photoSettings.previewPhotoFormat = [kCVPixelBufferPixelFormatTypeKey as String: photoPreviewType]
photoOutput.capturePhoto(with: photoSettings, delegate: self)
}
}
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
guard let imageData = photo.fileDataRepresentation() else { return }
let previewImage = UIImage(data: imageData)
// ... post process and save UIImage
}
}
On an unrelated note, it is standard practice in Swift to begin function names with lowercase letters. Only class, struct, and enum names start with uppercase letters.