Skip to content
All essays
iOSMarch 27, 202514 min

Swift Camera and Photos: Capture and Manage Images

Master camera integration and photo management in iOS apps. UIImagePickerController and Photos framework.

Ü
Ümit Uz
Mobile & Full Stack Developer

Camera integration enables users to capture photos and videos directly in your app. Learn to implement camera features and manage photo libraries.

Camera Access

Request Camera Permission

swift
import AVFoundation
import Photos

class CameraPermissionManager: ObservableObject {
    @Published var cameraPermissionStatus: AVAuthorizationStatus = .notDetermined
    @Published var photoLibraryPermissionStatus: PHAuthorizationStatus = .notDetermined

    func requestCameraPermission() {
        AVCaptureDevice.requestAccess(for: .video) { granted in
            DispatchQueue.main.async {
                self.cameraPermissionStatus = granted ? .authorized : .denied
            }
        }
    }

    func requestPhotoLibraryPermission() {
        PHPhotoLibrary.requestAuthorization { status in
            DispatchQueue.main.async {
                self.photoLibraryPermissionStatus = status
            }
        }
    }
}

UIImagePickerController

Basic Camera Integration

swift
import UIKit
import SwiftUI

struct CameraPicker: UIViewControllerRepresentable {
    @Binding var image: UIImage?
    @Binding var isPresented: Bool
    var sourceType: UIImagePickerController.SourceType = .camera

    func makeUIViewController(context: Context) -> UIImagePickerController {
        let picker = UIImagePickerController()
        picker.sourceType = sourceType
        picker.delegate = context.coordinator
        return picker
    }

    func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}

    func makeCoordinator() -> Coordinator {
        Coordinator(image: $image, isPresented: $isPresented)
    }

    class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
        @Binding var image: UIImage?
        @Binding var isPresented: Bool

        init(image: Binding<UIImage?>, isPresented: Binding<Bool>) {
            self._image = image
            self._isPresented = isPresented
        }

        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
            if let uiImage = info[.originalImage] as? UIImage {
                image = uiImage
            }
            isPresented = false
        }

        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
            isPresented = false
        }
    }
}

SwiftUI Camera View

swift
struct CameraView: View {
    @State private var selectedImage: UIImage?
    @State private var showImagePicker = false
    @State private var showCamera = false

    var body: some View {
        VStack {
            if let image = selectedImage {
                Image(uiImage: image)
                    .resizable()
                    .scaledToFit()
                    .frame(height: 300)
                    .cornerRadius(10)
            }

            HStack(spacing: 20) {
                Button("Camera") {
                    showCamera = true
                }
                .padding()
                .background(Color.blue)
                .foregroundColor(.white)
                .cornerRadius(10)

                Button("Photo Library") {
                    showImagePicker = true
                }
                .padding()
                .background(Color.green)
                .foregroundColor(.white)
                .cornerRadius(10)
            }
        }
        .sheet(isPresented: $showCamera) {
            CameraPicker(
                image: $selectedImage,
                isPresented: $showCamera,
                sourceType: .camera
            )
        }
        .sheet(isPresented: $showImagePicker) {
            CameraPicker(
                image: $selectedImage,
                isPresented: $showImagePicker,
                sourceType: .photoLibrary
            )
        }
    }
}

Custom Camera UI

AVFoundation Camera

swift
import AVFoundation
import SwiftUI

struct CustomCameraView: UIViewRepresentable {
    class CameraCoordinator: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate {
        var captureSession: AVCaptureSession?
        var previewLayer: AVCaptureVideoPreviewLayer?
        var photoOutput: AVCapturePhotoOutput?

        func setupCamera() {
            captureSession = AVCaptureSession()

            guard let captureSession = captureSession else { return }

            captureSession.sessionPreset = .photo

            guard let backCamera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else {
                print("Unable to access back camera")
                return
            }

            do {
                let input = try AVCaptureDeviceInput(device: backCamera)

                if captureSession.canAddInput(input) {
                    captureSession.addInput(input)
                }

                photoOutput = AVCapturePhotoOutput()

                if let photoOutput = photoOutput,
                   captureSession.canAddOutput(photoOutput) {
                    captureSession.addOutput(photoOutput)
                }

                DispatchQueue.global(qos: .userInitiated).async {
                    captureSession.startRunning()
                }

            } catch {
                print("Error setting up camera: \(error)")
            }
        }

        func capturePhoto() {
            guard let photoOutput = photoOutput else { return }

            let settings = AVCapturePhotoSettings()
            photoOutput.capturePhoto(with: settings, delegate: self)
        }
    }

    func makeUIView(context: Context) -> UIView {
        let view = UIView()
        context.coordinator.setupCamera()

        if let previewLayer = context.coordinator.previewLayer {
            previewLayer.frame = view.bounds
            view.layer.addSublayer(previewLayer)
        }

        return view
    }

    func updateUIView(_ uiView: UIView, context: Context) {}

    func makeCoordinator() -> CameraCoordinator {
        CameraCoordinator()
    }
}

Photo Library

Fetch Photos

swift
import Photos

class PhotoLibraryManager: ObservableObject {
    @Published var photos: [PHAsset] = []

    func fetchPhotos() {
        let fetchOptions = PHFetchOptions()
        fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
        fetchOptions.fetchLimit = 50

        let assets: PHFetchResult<PHAsset> = PHAsset.fetchAssets(with: .image, options: fetchOptions)

        DispatchQueue.main.async {
            self.photos = assets.objects(at: IndexSet(integersIn: 0..<assets.count))
        }
    }

    func requestImage(for asset: PHAsset, size: CGSize, completion: @escaping (UIImage?) -> Void) {
        let options = PHImageRequestOptions()
        options.deliveryMode = .highQualityFormat
        options.isNetworkAccessAllowed = true

        let targetSize = CGSize(width: size.width * UIScreen.main.scale, height: size.height * UIScreen.main.scale)

        PHImageManager.default().requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFill, options: options) { image, _ in
            completion(image)
        }
    }
}

Save Photos

swift
extension PhotoLibraryManager {
    func saveImage(_ image: UIImage) async throws {
        try await PHPhotoLibrary.shared().performChanges {
            let request = PHAssetChangeRequest.creationRequestForAsset(from: image)
        }
    }

    func saveImageWithMetadata(_ image: UIImage, location: CLLocation?) async throws {
        try await PHPhotoLibrary.shared().performChanges {
            let request = PHAssetChangeRequest.creationRequestForAsset(from: image)

            if let location = location {
                request.location = location
            }
        }
    }
}

Image Processing

Filter Effects

swift
import CoreImage
import CoreImage.CIFilterBuiltins

class ImageProcessor {
    func applyFilter(to image: UIImage, filterName: String) -> UIImage? {
        guard let ciImage = CIImage(image: image) else { return nil }

        let context = CIContext()

        if let filter = CIFilter(name: filterName) {
            filter.setValue(ciImage, forKey: kCIInputImageKey)

            if let outputImage = filter.outputImage,
               let cgImage = context.createCGImage(outputImage, from: outputImage.extent) {
                return UIImage(cgImage: cgImage)
            }
        }

        return nil
    }

    func applySepia(to image: UIImage) -> UIImage? {
        let filter = SepiaTone()
        filter.inputImage = CIImage(image: image)
        filter.intensity = 0.8

        let context = CIContext()

        if let outputImage = filter.outputImage,
           let cgImage = context.createCGImage(outputImage, from: outputImage.extent) {
            return UIImage(cgImage: cgImage)
        }

        return nil
    }

    func adjustBrightness(image: UIImage, value: Double) -> UIImage? {
        let filter = CIFilter.exposureAdjust()
        filter.inputImage = CIImage(image: image)
        filter.ev = value

        let context = CIContext()

        if let outputImage = filter.outputImage,
           let cgImage = context.createCGImage(outputImage, from: outputImage.extent) {
            return UIImage(cgImage: cgImage)
        }

        return nil
    }
}

Video Recording

Record Video

swift
class VideoRecorder: ObservableObject {
    private let captureSession = AVCaptureSession()
    private var videoOutput: AVCaptureMovieFileOutput?
    private var outputFileURL: URL?

    func setupRecording() {
        captureSession.sessionPreset = .high

        guard let backCamera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else { return }

        do {
            let input = try AVCaptureDeviceInput(device: backCamera)

            if captureSession.canAddInput(input) {
                captureSession.addInput(input)
            }

            videoOutput = AVCaptureMovieFileOutput()

            if let videoOutput = videoOutput,
               captureSession.canAddOutput(videoOutput) {
                captureSession.addOutput(videoOutput)
            }

            captureSession.startRunning()

        } catch {
            print("Error setting up video: \(error)")
        }
    }

    func startRecording() {
        let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
        outputFileURL = documentsPath.appendingPathComponent("video.mp4")

        if let videoOutput = videoOutput, let url = outputFileURL {
            videoOutput.startRecording(to: url, recordingDelegate: self)
        }
    }

    func stopRecording() {
        videoOutput?.stopRecording()
    }
}

extension VideoRecorder: AVCaptureFileOutputRecordingDelegate {
    func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
        if let error = error {
            print("Recording error: \(error)")
            return
        }

        print("Video saved to: \(outputFileURL)")
    }
}

Best Practices

  1. 1Permissions: Request permissions appropriately
  2. 2Memory: Manage memory carefully with photos
  3. 3Performance: Optimize image loading
  4. 4Privacy: Respect user privacy
  5. 5Storage: Handle storage limitations
  6. 6Compression: Compress images for storage
  7. 7Metadata: Preserve photo metadata
  8. 8Testing: Test on real devices

Camera and photo integration enhances user experience!

Related essays

Next essay
Swift Metal: High-Performance Graphics Programming