Swift Camera App: An IOS Development Tutorial
Hey guys! Ready to dive into creating your own camera app using Swift and iOS? This tutorial will walk you through building a basic camera application, covering everything from setting up the UI to handling camera permissions and capturing images. Let's get started!
Setting Up Your Project
First things first, let's create a new Xcode project. Open Xcode and select "Create a new Xcode project." Choose the "App" template under the iOS tab. Give your project a name, like "SwiftCameraApp," and make sure the interface is set to "Storyboard" and the language is "Swift.” Save the project to your desired location.
Now, let’s jump into the project settings to ensure we have the necessary permissions to access the camera. In the Project navigator, select your project, then select your target. Click on the "Info" tab. Scroll down until you find the "Privacy - Camera Usage Description" key. If it's not there, add it by clicking the "+" button and searching for "Privacy - Camera Usage Description.” For the value, enter a description like "This app needs access to your camera to take photos.” This description is what the user will see when the app asks for camera permission, so make it clear and concise.
Next, let's design the user interface. Open Main.storyboard. Drag and drop a UIImageView to display the camera feed and a UIButton to capture photos. Add constraints to make them responsive on different screen sizes. For the UIImageView, set constraints to fill the view. For the button, add constraints for the bottom and horizontal center. Change the button's title to "Take Photo.” Connect the UIImageView and UIButton to your ViewController.swift file by creating outlets and actions. Right-click on the ViewController in the Document Outline, drag from the circle next to New Referencing Outlet to the UIImageView, and name the outlet imageView. Similarly, drag from the circle next to New Referencing Action to the UIButton, name the action takePhoto, and set the event to Touch Up Inside.
Implementing Camera Functionality
Now for the exciting part – actually making the camera work! Open ViewController.swift. Import the AVFoundation framework at the top of the file. This framework provides the necessary tools for working with the camera.
Inside the ViewController class, declare the following variables:
var captureSession: AVCaptureSession!
var stillImageOutput: AVCapturePhotoOutput!
var videoPreviewLayer: AVCaptureVideoPreviewLayer!
These variables will manage the camera session, handle image output, and display the camera feed.
In the viewDidLoad method, set up the camera session:
override func viewDidLoad() {
    super.viewDidLoad()
    captureSession = AVCaptureSession()
    captureSession.sessionPreset = .medium // or .high for better quality
    guard let backCamera = AVCaptureDevice.default(for: AVMediaType.video)
        else {
            print("Unable to access back camera!")
            return
    }
    do {
        let input = try AVCaptureDeviceInput(device: backCamera)
        stillImageOutput = AVCapturePhotoOutput()
        if captureSession.canAddInput(input) && captureSession.canAddOutput(stillImageOutput) {
            captureSession.addInput(input)
            captureSession.addOutput(stillImageOutput)
            videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
            videoPreviewLayer.frame = imageView.bounds
            imageView.layer.addSublayer(videoPreviewLayer)
            captureSession.startRunning()
        }
    } catch let error {
        print("Error setting up camera input: (error)")
    }
}
This code initializes the capture session, finds the back camera, creates an input from the camera, and adds it to the session. It also creates an output for capturing still images. Finally, it creates a preview layer to display the camera feed in the UIImageView and starts the capture session.
To ensure the preview layer is properly sized, override the viewDidAppear method and update the videoPreviewLayer frame:
override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    videoPreviewLayer.frame = imageView.bounds
}
Now, let's implement the takePhoto action. Add the following code to capture an image when the button is tapped:
@IBAction func takePhoto(_ sender: UIButton) {
    let settings = AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg])
    stillImageOutput.capturePhoto(with: settings, delegate: self)
}
This code creates photo settings and tells the stillImageOutput to capture a photo with those settings. We also need to conform to the AVCapturePhotoCaptureDelegate protocol to handle the captured image. Extend the ViewController class with the following:
extension ViewController: AVCapturePhotoCaptureDelegate {
    func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
        guard let imageData = photo.fileDataRepresentation() else {
            return
        }
        let image = UIImage(data: imageData)
        imageView.image = image
    }
}
This delegate method is called when the photo capture is complete. It converts the image data to a UIImage and displays it in the UIImageView.
Handling Permissions
It's crucial to handle camera permissions gracefully. Add the following code to request camera permission when the view loads:
import AVFoundation
import UIKit
class ViewController: UIViewController {
    @IBOutlet weak var imageView: UIImageView!
    @IBOutlet weak var captureButton: UIButton!
    var captureSession: AVCaptureSession!
    var stillImageOutput: AVCapturePhotoOutput!
    var videoPreviewLayer: AVCaptureVideoPreviewLayer!
    override func viewDidLoad() {
        super.viewDidLoad()
        // Check camera permissions
        checkCameraPermissions()
    }
    func checkCameraPermissions() {
        let cameraAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: .video)
        switch cameraAuthorizationStatus {
        case .notDetermined:
            AVCaptureDevice.requestAccess(for: .video) { granted in
                if granted {
                    DispatchQueue.main.async {
                        self.setupCamera()
                    }
                } else {
                    self.handleNoCameraPermissions()
                }
            }
        case .authorized:
            setupCamera()
        case .denied, .restricted:
            handleNoCameraPermissions()
        @unknown default:
            fatalError()
        }
    }
    func setupCamera() {
        captureSession = AVCaptureSession()
        captureSession.sessionPreset = .medium // or .high for better quality
        guard let backCamera = AVCaptureDevice.default(for: AVMediaType.video)
            else {
                print("Unable to access back camera!")
                return
        }
        do {
            let input = try AVCaptureDeviceInput(device: backCamera)
            stillImageOutput = AVCapturePhotoOutput()
            if captureSession.canAddInput(input) && captureSession.canAddOutput(stillImageOutput) {
                captureSession.addInput(input)
                captureSession.addOutput(stillImageOutput)
                videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
                videoPreviewLayer.frame = imageView.bounds
                imageView.layer.addSublayer(videoPreviewLayer)
                captureSession.startRunning()
            }
        } catch let error {
            print("Error setting up camera input: (error)")
        }
    }
    func handleNoCameraPermissions() {
        // Show an alert or a message to the user indicating that camera access is required
        let alertController = UIAlertController(
            title: "Camera Access Required",
            message: "Please enable camera access in Settings to use this feature.",
            preferredStyle: .alert
        )
        alertController.addAction(UIAlertAction(title: "Open Settings", style: .default) { _ in
            if let settingsURL = URL(string: UIApplication.openSettingsURLString) {
                UIApplication.shared.open(settingsURL, options: [:], completionHandler: nil)
            }
        })
        alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
        present(alertController, animated: true, completion: nil)
    }
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        videoPreviewLayer.frame = imageView.bounds
    }
    @IBAction func takePhoto(_ sender: UIButton) {
        let settings = AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg])
        stillImageOutput.capturePhoto(with: settings, delegate: self)
    }
}
extension ViewController: AVCapturePhotoCaptureDelegate {
    func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
        guard let imageData = photo.fileDataRepresentation() else {
            return
        }
        let image = UIImage(data: imageData)
        imageView.image = image
    }
}
This checkCameraPermissions function checks the current authorization status for the camera. If the user hasn't been asked yet, it requests permission. If the user has denied or restricted access, it displays an alert prompting them to open the Settings app to grant permission. The setupCamera function is called only if the camera access is authorized.
Enhancing Your Camera App
Now that you have a basic camera app, there are tons of ways to enhance it!
Adding Camera Switching
To allow users to switch between the front and back cameras, you'll need to add a button and modify the camera setup code.
First, add a button to your storyboard and create an action in your ViewController. Let’s call the action switchCamera.  Then, modify the setupCamera function to include logic for selecting the appropriate camera.
var currentCamera: AVCaptureDevice?
func setupCamera(position: AVCaptureDevice.Position = .back) {
    captureSession = AVCaptureSession()
    captureSession.sessionPreset = .medium
    if let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: position) {
        currentCamera = device
    } else {
        print("Failed to get the camera device")
        return
    }
    guard let camera = currentCamera else {
        print("Unable to access camera!")
        return
    }
    do {
        let input = try AVCaptureDeviceInput(device: camera)
        stillImageOutput = AVCapturePhotoOutput()
        if captureSession.canAddInput(input) && captureSession.canAddOutput(stillImageOutput) {
            captureSession.addInput(input)
            captureSession.addOutput(stillImageOutput)
            videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
            videoPreviewLayer.frame = imageView.bounds
            imageView.layer.addSublayer(videoPreviewLayer)
            captureSession.startRunning()
        }
    } catch let error {
        print("Error setting up camera input: (error)")
    }
}
@IBAction func switchCamera(_ sender: UIButton) {
    captureSession.stopRunning()
    if currentCamera?.position == .back {
        setupCamera(position: .front)
    } else {
        setupCamera(position: .back)
    }
}
Adding Flash Control
To control the flash, you can add a button to toggle the flash mode. Add a new button to your storyboard and create an action named toggleFlash in your ViewController.  Modify the toggleFlash action as follows:
var flashOn: Bool = false
@IBAction func toggleFlash(_ sender: UIButton) {
    guard let device = currentCamera else { return }
    if device.hasTorch && device.isTorchAvailable {
        do {
            try device.lockForConfiguration()
            if flashOn {
                device.torchMode = .off
                flashOn = false
            } else {
                try device.setTorchModeOn(level: 1.0)
                flashOn = true
            }
            device.unlockForConfiguration()
        } catch {
            print("Torch could not be used")
        }
    } else {
        print("Torch is not available")
    }
}
Saving Photos to the Photo Library
To save captured photos to the user's photo library, you need to import the Photos framework and use its API.
import Photos
extension ViewController: AVCapturePhotoCaptureDelegate {
    func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
        guard let imageData = photo.fileDataRepresentation() else {
            return
        }
        let image = UIImage(data: imageData)
        imageView.image = image
        // Save the image to the photo library
        PHPhotoLibrary.shared().performChanges({
            PHAssetChangeRequest.creationRequestForAsset(from: image!)
        }, completionHandler: { success, error in
            if let error = error {
                print("Error saving photo: (error)")
            } else {
                print("Photo saved successfully!")
            }
        })
    }
}
Remember to add the "Privacy - Photo Library Usage Description" key in your Info.plist file.
Conclusion
And there you have it! You’ve built a basic camera app using Swift and iOS. This tutorial covered setting up the project, handling camera permissions, capturing images, and displaying them in a UIImageView. From here, you can continue to enhance your app by adding features like camera switching, flash control, and saving photos to the photo library. Have fun experimenting and building your own unique camera app!