Andriyas  Redel
Andriyas Redel

Reputation: 529

Double Tap or Use Button To Switch Camera From Back to Front (Xcode 8, Swift 3)

So, lately I have been trying to implement the function of switching the camera view from back to front camera in Swift 3. However, with no luck.

Currently, my default view is from the back camera - I can take pictures with it and then retake. But can anyone help me and show how do I either double tap the screen to switch cameras or simply use the assigned button to switch them? Thank you!

import UIKit
import AVFoundation
import FirebaseDatabase

class CameraView: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {

override var prefersStatusBarHidden: Bool {
    return true
}

var captureSession : AVCaptureSession!
var stillImageOutput : AVCaptureStillImageOutput!
var previewLayer : AVCaptureVideoPreviewLayer!

@IBOutlet var cameraView: UIView!
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.
}

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    previewLayer?.frame = cameraView.bounds
}

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    captureSession = AVCaptureSession()
    captureSession?.sessionPreset = AVCaptureSessionPreset1920x1080

    var backCamera = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
    var error : NSError?

    do {
        var input = try! AVCaptureDeviceInput(device: backCamera)
        if (error == nil && captureSession?.canAddInput(input) != nil) {

            captureSession?.addInput(input)

            stillImageOutput = AVCaptureStillImageOutput()
            stillImageOutput.outputSettings = [AVVideoCodecKey: AVVideoCodecJPEG]

            if (captureSession?.canAddOutput(stillImageOutput) != nil) {

                captureSession?.addOutput(stillImageOutput)

                previewLayer = AVCaptureVideoPreviewLayer (session: captureSession)
                previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
                previewLayer.connection.videoOrientation = AVCaptureVideoOrientation.portrait
                cameraView.layer.addSublayer(previewLayer)
                captureSession?.startRunning() }
        }

    } catch {

    }
}

@IBOutlet var tempImageView: UIImageView!
@IBAction func didPressTakePhoto(_ sender: UIButton) {

    if let videoConnection = stillImageOutput.connection(withMediaType: AVMediaTypeVideo) {

        videoConnection.videoOrientation = AVCaptureVideoOrientation.portrait
        stillImageOutput.captureStillImageAsynchronously(from: videoConnection, completionHandler: {

            (sampleBuffer, error) in
            if sampleBuffer != nil {

                var imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(sampleBuffer)
                var dataProvider = CGDataProvider.init(data: imageData as! CFData)
                var cgImageRef = CGImage.init(jpegDataProviderSource: dataProvider!, decode: nil, shouldInterpolate: true, intent: .defaultIntent)

                var image = UIImage (cgImage: cgImageRef!, scale: 1.0, orientation: UIImageOrientation.right)

                self.tempImageView.image = image
                self.tempImageView.isHidden = false

            }

        })

    }
}

var didTakePhoto = Bool()

@IBAction func didPressTakeAnother(_ sender: UIButton) {

    if didTakePhoto == true {

        tempImageView.isHidden = true
        didTakePhoto = false

    } else {

        captureSession?.startRunning()
        didTakePhoto = true

    }
}
}

Upvotes: 12

Views: 10099

Answers (3)

Muhammad Asyraf
Muhammad Asyraf

Reputation: 1808

Xcode Version : Version 10.1 (10B61)
Swift Version : Swift 4.2
Change AVCaptureSession Capture Source

Refers from Stepan Maksymov Solution
we can simplified by replacing captureSession.inputs

First Create an IBAction Outlet and Connect ViewController to Change Camera View

@IBAction private func changeCamera(_ cameraButton: UIButton) {
    usingFrontCamera = !usingFrontCamera
    do{
        captureSession.removeInput(captureSession.inputs.first!)

        if(usingFrontCamera){
            captureDevice = getFrontCamera()
        }else{
            captureDevice = getBackCamera()
        }
        let captureDeviceInput1 = try AVCaptureDeviceInput(device: captureDevice)
        captureSession.addInput(captureDeviceInput1)
    }catch{
        print(error.localizedDescription)
    }
}

Second step Copy simplified AVCaptureDevice Setting || refer Stepan Maksymov

func getFrontCamera() -> AVCaptureDevice?{
    return AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaType.video, position: .front).devices.first
    return nil
}

func getBackCamera() -> AVCaptureDevice?{
    return AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaType.video, position: .back).devices.first
    return nil
}

You can replace @IBAction with function like so.

func changeCamera(){
    usingFrontCamera = !usingFrontCamera
    do{
        captureSession.removeInput(captureSession.inputs.first!)

        if(usingFrontCamera){
            captureDevice = getFrontCamera()
        }else{
            captureDevice = getBackCamera()
        }
        let captureDeviceInput1 = try AVCaptureDeviceInput(device: captureDevice)
        captureSession.addInput(captureDeviceInput1)
    }catch{
        print(error.localizedDescription)
    }
}

Upvotes: 4

lightbyte
lightbyte

Reputation: 565

In addition to Stepan Maksymov post, I sugest to add this function

func stopCaptureSession () {
    self.captureSession.stopRunning()

    if let inputs = captureSession.inputs as? [AVCaptureDeviceInput] {
        for input in inputs {
            self.captureSession.removeInput(input)
        }
    }

}

And call it instead of his post lines:

for i : AVCaptureDeviceInput in (self.captureSession?.inputs as! [AVCaptureDeviceInput]){
    self.captureSession?.removeInput(i)
}

This way the cameras will change quicker.

Upvotes: 2

Stepan Maksymov
Stepan Maksymov

Reputation: 2606

don't see here any problems - here is working solution:

import Foundation
import UIKit
import AVFoundation

class MainViewController: UIViewController {

    var tempImage: UIImageView?

    var captureSession: AVCaptureSession?
    var stillImageOutput: AVCaptureStillImageOutput?
    var videoPreviewLayer: AVCaptureVideoPreviewLayer?
    var currentCaptureDevice: AVCaptureDevice?

    var usingFrontCamera = false


    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        loadCamera()
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        videoPreviewLayer!.frame = self.cameraPreviewSurface.bounds
    }

    @IBAction func switchButtonAction(_ sender: Any) {
        usingFrontCamera = !usingFrontCamera
        loadCamera()
    }


    func getFrontCamera() -> AVCaptureDevice?{
        let videoDevices = AVCaptureDevice.devices(withMediaType: AVMediaTypeVideo)


        for device in videoDevices!{
            let device = device as! AVCaptureDevice
            if device.position == AVCaptureDevicePosition.front {
                return device
            }
        }
        return nil
    }

    func getBackCamera() -> AVCaptureDevice{
        return AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
    }



    func loadCamera() {
        if(captureSession == nil){
            captureSession = AVCaptureSession()
            captureSession!.sessionPreset = AVCaptureSessionPresetPhoto
        }
        var error: NSError?
        var input: AVCaptureDeviceInput!

        currentCaptureDevice = (usingFrontCamera ? getFrontCamera() : getBackCamera())

        do {
            input = try AVCaptureDeviceInput(device: currentCaptureDevice)
        } catch let error1 as NSError {
            error = error1
            input = nil
            print(error!.localizedDescription)
        }

        for i : AVCaptureDeviceInput in (self.captureSession?.inputs as! [AVCaptureDeviceInput]){
            self.captureSession?.removeInput(i)
        }
        if error == nil && captureSession!.canAddInput(input) {
            captureSession!.addInput(input)
            stillImageOutput = AVCaptureStillImageOutput()
            stillImageOutput?.outputSettings = [AVVideoCodecKey: AVVideoCodecJPEG]
            if captureSession!.canAddOutput(stillImageOutput) {
                captureSession!.addOutput(stillImageOutput)
                videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
                videoPreviewLayer!.videoGravity = AVLayerVideoGravityResizeAspectFill
                videoPreviewLayer!.connection?.videoOrientation = AVCaptureVideoOrientation.portrait
                //self.cameraPreviewSurface.layer.sublayers?.forEach { $0.removeFromSuperlayer() }
                self.cameraPreviewSurface.layer.addSublayer(videoPreviewLayer!)
                DispatchQueue.main.async {
                    self.captureSession!.startRunning()
                }


            }
        }



    }
}

some notes:

cameraPreviewSurface - this is your UIView where camera will show

don't reassign the session, don't just add input, but before add new - remove existing ones,

p.s. code done with swift 3.0.1 / xcode 8.1

Cheers )

Upvotes: 18

Related Questions