Search code examples
iosswiftios8ios-camera

How to take multiple photos in a sequence (1s delay each), using Swift on iOS8.1?


I am trying to take few photos after a single user click on a Camera preview so I can present them and user can pick one that was timed best or use all in a "film strip" mode. The expected user experience is: "I open a camera, take a picture and then I see 5 pictures taken second by second. I do not have to press the 'take picture' button 5 times, one is just enough to start the sequence".

I am new to iOS and Swift and I base my work on a 'Swift Recipes' book (https://www.safaribooksonline.com/library/view/ios-8-swift/9781491908969/).

The source code to take a single photo is:

controller = UIImagePickerController()
    if let theController = controller{
        theController.sourceType = .Camera
        theController.mediaTypes = [kUTTypeImage as NSString]
        theController.allowsEditing = true
        theController.delegate = self
        presentViewController(theController, animated: true, completion: nil)
    }

plus the relevant image handling (via func imagePickerController). I tried playing with the controller objecct above, but miserably failed :( I believe that this is not the right way to get what I look for, but I struggle to find the right one. Any help will be appreciated.


Solution

  • I don't know if there is a mode for this behavior in UIImagePickerController. If I were you I would use AVFoundation to implement this.

    import UIKit
    import AVFoundation
    
    class ViewController: UIViewController {
    
        var connection: AVCaptureConnection!
        var output: AVCaptureStillImageOutput!
        var videoPreviewLayer: AVCaptureVideoPreviewLayer!
        @IBOutlet var videPreviewView: UIView!
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            self.createCamera()
        }
    
        override func viewDidLayoutSubviews() {
            super.viewDidLayoutSubviews()
    
            self.videoPreviewLayer.bounds = self.videPreviewView.bounds
            self.videoPreviewLayer.position = CGPoint(x: CGRectGetMidX(self.videPreviewView.bounds), y: CGRectGetMidY(self.videPreviewView.bounds))
        }
    
        func createCamera() {
            let captureSession = AVCaptureSession()
    
            if captureSession.canSetSessionPreset(AVCaptureSessionPresetHigh) {
                captureSession.sessionPreset = AVCaptureSessionPresetHigh
            } else {
                println("Error: Couldn't set preset = \(AVCaptureSessionPresetHigh)")
                return
            }
    
            let cameraDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
    
            var error: NSError?
            let inputDevice = AVCaptureDeviceInput.deviceInputWithDevice(cameraDevice, error: &error) as AVCaptureDeviceInput
            if let error = error {
                println("Error: \(error)")
                return
            }
    
            if captureSession.canAddInput(inputDevice) {
                captureSession.addInput(inputDevice)
            } else {
                println("Error: Couldn't add input device")
                return
            }
    
            let imageOutput = AVCaptureStillImageOutput()
            if captureSession.canAddOutput(imageOutput) {
                captureSession.addOutput(imageOutput)
            } else {
                println("Error: Couldn't add output")
                return
            }
    
            // Store imageOutput. We will need it to take photo
            self.output = imageOutput
    
            let connection = imageOutput.connections.first as AVCaptureConnection!
            // Store this connection in property. We will need it when we take image.
            self.connection = connection
            connection.videoOrientation = AVCaptureVideoOrientation.Portrait
    
            captureSession.startRunning()
    
            // This will preview the camera
            let videoLayer = AVCaptureVideoPreviewLayer(session: captureSession)
            videoLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
            videoLayer.contentsScale = UIScreen.mainScreen().scale
    
            self.videPreviewView.layer.addSublayer(videoLayer)
    
            // Store this layer instance in property. We will place it into a view
            self.videoPreviewLayer = videoLayer
        }
    
        func takePhoto(completion: (UIImage?, NSError?) -> Void) {
            self.output.captureStillImageAsynchronouslyFromConnection(self.connection) { buffer, error in
                if let error = error {
                    completion(nil, error)
                } else {
                    let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(buffer)
                    let image = UIImage(data: imageData, scale: UIScreen.mainScreen().scale)
                    completion(image, nil)
                }
            }
        }
    }
    

    Now all you have to do is create an action that calls takePhoto with 3 times. You can use NSTimer to do this.