SwiftUI Camera and Photo Picker Integration

Integrating camera functionality and photo library access into your iOS apps is a common requirement for features like profile picture updates, content creation, and more. SwiftUI, Apple’s declarative UI framework, simplifies this process considerably. In this comprehensive guide, we’ll walk you through the steps to seamlessly integrate camera and photo picker functionalities in your SwiftUI application.

Why Integrate Camera and Photo Picker in SwiftUI?

  • User Engagement: Allows users to create and share content directly from your app.
  • Profile Customization: Enables users to personalize their profiles with custom images.
  • Enhanced Functionality: Supports features like image uploads, scanning, and more.

Setting up the Project

First, create a new Xcode project with the SwiftUI interface. Make sure you have the necessary permissions configured in your Info.plist file to access the camera and photo library.

Step 1: Create a New Xcode Project

Open Xcode and create a new project. Choose the “App” template under the iOS tab and select SwiftUI for the Interface.

Step 2: Configure Info.plist for Permissions

Open Info.plist and add the following keys to request permissions:

  • Privacy – Camera Usage Description: A message explaining why the app needs camera access.
  • Privacy – Photo Library Usage Description: A message explaining why the app needs photo library access.
  • Privacy – Photo Library Additions Usage Description: (Optional) A message if you intend to save photos to the user’s photo library.

Here’s an example snippet:

<key>NSCameraUsageDescription</key>
<string>This app needs camera access to take photos.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs photo library access to select photos.</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>This app needs to save photos to the photo library.</string>

Implementing the Photo Picker

The PhotosPicker in SwiftUI simplifies the integration with the photo library. You can allow users to select one or more photos, depending on your app’s needs.

Step 1: Import Required Modules

Ensure you import the necessary modules in your SwiftUI file:

import SwiftUI
import PhotosUI

Step 2: Define State Variables

Define state variables to manage the selected image and the photo picker presentation:

@State private var selectedImage: UIImage? = nil
@State private var isPickerPresented: Bool = false
@State private var photoPickerItem: PhotosPickerItem? = nil

Step 3: Implement the Photo Picker View

Add a button to trigger the photo picker, and use the PhotosPicker view to handle the photo selection. When an item is selected, load the UIImage from it.

struct ContentView: View {
    @State private var selectedImage: UIImage? = nil
    @State private var isPickerPresented: Bool = false
    @State private var photoPickerItem: PhotosPickerItem? = nil

    var body: some View {
        VStack {
            if let image = selectedImage {
                Image(uiImage: image)
                    .resizable()
                    .scaledToFit()
                    .frame(width: 200, height: 200)
            } else {
                Text("Select an Image")
                    .frame(width: 200, height: 200)
                    .background(Color.gray.opacity(0.3))
            }

            Button("Pick Image") {
                isPickerPresented = true
            }
            .padding()
            .buttonStyle(.borderedProminent)
            .photosPicker(
                isPresented: $isPickerPresented,
                selection: $photoPickerItem,
                matching: .images
            )
            .onChange(of: photoPickerItem) { newItem in
                Task {
                    // Retrieve selected asset in the form of Data
                    if let data = try? await newItem?.loadTransferable(type: Data.self) {
                        if let uiImage = UIImage(data: data) {
                            selectedImage = uiImage
                            return
                        }
                    }
                    print("Failed to load image")
                }
            }
        }
    }
}

Explanation:

  • The photoPicker modifier presents the PhotosPicker when isPickerPresented is true.
  • The selection binding updates photoPickerItem when the user selects an image.
  • The onChange modifier triggers a task to load the selected image from photoPickerItem and set it to selectedImage.

Implementing Camera Integration

Integrating camera functionality requires a bit more effort, as SwiftUI doesn’t provide a direct built-in component. We need to use UIImagePickerController from UIKit, wrapped in a UIViewControllerRepresentable to make it compatible with SwiftUI.

Step 1: Create a UIViewControllerRepresentable Wrapper

Create a struct that conforms to UIViewControllerRepresentable:

import SwiftUI
import UIKit

struct ImagePicker: UIViewControllerRepresentable {
    @Binding var selectedImage: UIImage?
    @Environment(.presentationMode) var presentationMode

    var sourceType: UIImagePickerController.SourceType = .camera

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

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

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
        let parent: ImagePicker

        init(_ parent: ImagePicker) {
            self.parent = parent
        }

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

            parent.presentationMode.wrappedValue.dismiss()
        }

        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
            parent.presentationMode.wrappedValue.dismiss()
        }
    }
}

Key components:

  • ImagePicker: A struct conforming to UIViewControllerRepresentable to wrap UIImagePickerController.
  • selectedImage: A binding to the selected image, allowing updates in the SwiftUI view.
  • sourceType: Determines whether to use the camera or the photo library (for cases where you’d like to select directly with the native image picker instead of PhotosUI’s PhotosPicker)
  • Coordinator: A nested class conforming to UIImagePickerControllerDelegate, handling the image selection and cancellation events.

Step 2: Integrate Camera Access in SwiftUI View

Use the ImagePicker in your SwiftUI view to allow camera access:

struct ContentView: View {
    @State private var selectedImage: UIImage? = nil
    @State private var isCameraPresented: Bool = false

    var body: some View {
        VStack {
            if let image = selectedImage {
                Image(uiImage: image)
                    .resizable()
                    .scaledToFit()
                    .frame(width: 200, height: 200)
            } else {
                Text("Take a Photo")
                    .frame(width: 200, height: 200)
                    .background(Color.gray.opacity(0.3))
            }

            Button("Take Photo") {
                isCameraPresented = true
            }
            .padding()
            .buttonStyle(.borderedProminent)
            .sheet(isPresented: $isCameraPresented) {
                ImagePicker(selectedImage: $selectedImage, sourceType: .camera)
            }
        }
    }
}

Here, a button is added that presents the ImagePicker in a sheet when tapped. The selected image from the camera is then displayed.

Combining Both Photo Picker and Camera

You can easily provide the user with options for both the photo library and camera by presenting an action sheet that prompts them for their selection:

struct ContentView: View {
    @State private var selectedImage: UIImage? = nil
    @State private var showImageSourceSelection: Bool = false
    @State private var isCameraPresented: Bool = false
    @State private var isPhotoLibraryPresented: Bool = false

    var body: some View {
        VStack {
            if let image = selectedImage {
                Image(uiImage: image)
                    .resizable()
                    .scaledToFit()
                    .frame(width: 200, height: 200)
            } else {
                Text("Select or Take a Photo")
                    .frame(width: 200, height: 200)
                    .background(Color.gray.opacity(0.3))
            }

            Button("Select Image Source") {
                showImageSourceSelection = true
            }
            .padding()
            .buttonStyle(.borderedProminent)
            .actionSheet(isPresented: $showImageSourceSelection) {
                ActionSheet(
                    title: Text("Select Image Source"),
                    buttons: [
                        .default(Text("Photo Library"), action: {
                            isPhotoLibraryPresented = true
                        }),
                        .default(Text("Camera"), action: {
                            isCameraPresented = true
                        }),
                        .cancel()
                    ]
                )
            }
            .photosPicker(isPresented: $isPhotoLibraryPresented, selection: $photoPickerItem, matching: .images)
            .sheet(isPresented: $isCameraPresented) {
                ImagePicker(selectedImage: $selectedImage, sourceType: .camera)
            }
            .onChange(of: photoPickerItem) { newItem in
                Task {
                    // Retrieve selected asset in the form of Data
                    if let data = try? await newItem?.loadTransferable(type: Data.self) {
                        if let uiImage = UIImage(data: data) {
                            selectedImage = uiImage
                            return
                        }
                    }
                    print("Failed to load image")
                }
            }
        }
    }
}

Saving Photos to the Photo Library (Optional)

If your app needs to save photos taken by the user to their photo library, use UIImageWriteToSavedPhotosAlbum in the Coordinator‘s imagePickerController method:

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
    if let image = info[.originalImage] as? UIImage {
        parent.selectedImage = image
        UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil) // Save to photo library
    }

    parent.presentationMode.wrappedValue.dismiss()
}

Conclusion

Integrating camera and photo picker functionalities in SwiftUI apps enhances user engagement and functionality. By leveraging PhotosPicker for photo library access and UIViewControllerRepresentable for camera integration, you can seamlessly provide users with the ability to capture and select images within your app. Remember to handle permissions gracefully and consider providing both options – camera and photo library – for the best user experience.