How to access photo in gallery and access camera in iOS swiftUI?

By | February 23, 2025

Here is a comprehensive implementation for accessing both the camera and photo gallery in SwiftUI. Here’s a breakdown of the key components:

Main Features

  1. Access to photo gallery using PhotosPicker (modern API)
  2. Camera access with AVFoundation
  3. Permission handling for both camera and photo library.
  4. User-friendly UI with permission denied screens.
  5. Flash control for camera.
  6. Preview of captured/selected images.
import SwiftUI
import PhotosUI
import AVFoundation

// MARK: - Main View
struct ContentView: View {
    @StateObject private var cameraManager = CameraManager()
    @State private var showImagePicker = false
    @State private var showCameraView = false
    @State private var selectedImage: UIImage?
    
    var body: some View {
        VStack(spacing: 20) {
            if let selectedImage {
                Image(uiImage: selectedImage)
                    .resizable()
                    .scaledToFit()
                    .frame(height: 300)
                    .clipShape(RoundedRectangle(cornerRadius: 10))
            } else {
                Image(systemName: "photo.fill")
                    .resizable()
                    .scaledToFit()
                    .frame(height: 200)
                    .foregroundColor(.gray.opacity(0.5))
            }
            
            HStack(spacing: 30) {
                Button {
                    showImagePicker = true
                } label: {
                    VStack {
                        Image(systemName: "photo.on.rectangle")
                            .font(.system(size: 30))
                        Text("Gallery")
                    }
                    .frame(width: 120)
                    .padding()
                    .background(Color.blue.opacity(0.1))
                    .cornerRadius(10)
                }
                
                Button {
                    cameraManager.checkPermission { granted in
                        if granted {
                            showCameraView = true
                        }
                    }
                } label: {
                    VStack {
                        Image(systemName: "camera.fill")
                            .font(.system(size: 30))
                        Text("Camera")
                    }
                    .frame(width: 120)
                    .padding()
                    .background(Color.blue.opacity(0.1))
                    .cornerRadius(10)
                }
            }
        }
        .padding()
        .sheet(isPresented: $showImagePicker) {
            PhotoPickerView(selectedImage: $selectedImage)
        }
        .sheet(isPresented: $showCameraView) {
            CameraView(image: $selectedImage, cameraManager: cameraManager)
        }
    }
}

// MARK: - Photo Gallery Access
struct PhotoPickerView: View {
    @Binding var selectedImage: UIImage?
    @Environment(\.dismiss) var dismiss
    @State private var photosPermission: Bool?
    
    var body: some View {
        NavigationView {
            VStack {
                if let photosPermission, !photosPermission {
                    PermissionDeniedView(
                        message: "Access to your photo library is needed to select photos.",
                        icon: "photo.fill.on.rectangle.fill"
                    )
                } else {
                    PhotosPicker(selection: $selectedImageItem, matching: .images) {
                        Text("Browse Photos")
                            .font(.headline)
                            .frame(maxWidth: .infinity)
                            .padding()
                            .background(Color.blue)
                            .foregroundColor(.white)
                            .cornerRadius(10)
                            .padding()
                    }
                }
            }
            .navigationTitle("Select a Photo")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button("Cancel") {
                        dismiss()
                    }
                }
            }
            .onAppear {
                checkPhotosPermission()
            }
        }
    }
    
    @State private var selectedImageItem: PhotosPickerItem?
    
    private func checkPhotosPermission() {
        let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)
        
        switch status {
        case .authorized, .limited:
            photosPermission = true
        case .denied, .restricted:
            photosPermission = false
        case .notDetermined:
            PHPhotoLibrary.requestAuthorization(for: .readWrite) { status in
                DispatchQueue.main.async {
                    photosPermission = status == .authorized || status == .limited
                }
            }
        @unknown default:
            photosPermission = false
        }
    }
    
    private var imageSelection: Binding<PhotosPickerItem?> {
        Binding {
            selectedImageItem
        } set: { newValue in
            selectedImageItem = newValue
            
            if let newValue {
                Task {
                    if let data = try? await newValue.loadTransferable(type: Data.self),
                       let uiImage = UIImage(data: data) {
                        DispatchQueue.main.async {
                            selectedImage = uiImage
                            dismiss()
                        }
                    }
                }
            }
        }
    }
}

// MARK: - Camera Manager
class CameraManager: NSObject, ObservableObject, AVCapturePhotoCaptureDelegate {
    @Published var session = AVCaptureSession()
    @Published var output = AVCapturePhotoOutput()
    @Published var preview: AVCaptureVideoPreviewLayer?
    @Published var isTaken = false
    @Published var flashMode: AVCaptureDevice.FlashMode = .off
    
    var completionHandler: ((UIImage?) -> Void)?
    
    override init() {
        super.init()
        checkPermission { _ in }
    }
    
    func checkPermission(completion: @escaping (Bool) -> Void) {
        switch AVCaptureDevice.authorizationStatus(for: .video) {
        case .authorized:
            setupSession()
            completion(true)
        case .notDetermined:
            AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in
                if granted {
                    DispatchQueue.main.async {
                        self?.setupSession()
                        completion(true)
                    }
                } else {
                    DispatchQueue.main.async {
                        completion(false)
                    }
                }
            }
        case .denied, .restricted:
            completion(false)
        @unknown default:
            completion(false)
        }
    }
    
    func setupSession() {
        do {
            self.session.beginConfiguration()
            
            guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else {
                return
            }
            
            let input = try AVCaptureDeviceInput(device: device)
            
            if self.session.canAddInput(input) {
                self.session.addInput(input)
            }
            
            if self.session.canAddOutput(self.output) {
                self.session.addOutput(self.output)
            }
            
            self.session.commitConfiguration()
        } catch {
            print("Error setting up camera: \(error.localizedDescription)")
        }
    }
    
    func takePicture(completion: @escaping (UIImage?) -> Void) {
        self.completionHandler = completion
        
        let settings = AVCapturePhotoSettings()
        settings.flashMode = self.flashMode
        
        self.output.capturePhoto(with: settings, delegate: self)
        
        DispatchQueue.main.async {
            withAnimation {
                self.isTaken = true
            }
        }
    }
    
    func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
        if let error = error {
            print("Error capturing photo: \(error.localizedDescription)")
            self.completionHandler?(nil)
            return
        }
        
        guard let imageData = photo.fileDataRepresentation(),
              let image = UIImage(data: imageData) else {
            self.completionHandler?(nil)
            return
        }
        
        self.completionHandler?(image)
    }
    
    func resetCamera() {
        DispatchQueue.main.async {
            withAnimation {
                self.isTaken = false
            }
        }
    }
    
    func toggleFlash() {
        self.flashMode = self.flashMode == .off ? .on : .off
    }
}

// MARK: - Camera View
struct CameraView: View {
    @Binding var image: UIImage?
    @ObservedObject var cameraManager: CameraManager
    @Environment(\.dismiss) var dismiss
    @State private var showFlash = false
    
    var body: some View {
        ZStack {
            if let preview = cameraManager.preview {
                CameraPreview(preview: preview)
                    .ignoresSafeArea()
            } else {
                Color.black
                    .ignoresSafeArea()
                    .onAppear {
                        setupPreview()
                    }
            }
            
            VStack {
                Spacer()
                
                HStack {
                    Button {
                        cameraManager.toggleFlash()
                        showFlash = true
                        
                        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                            showFlash = false
                        }
                    } label: {
                        Image(systemName: cameraManager.flashMode == .on ? "bolt.fill" : "bolt.slash.fill")
                            .font(.system(size: 24))
                            .foregroundColor(.white)
                            .padding()
                    }
                    
                    Spacer()
                    
                    Button {
                        cameraManager.takePicture { capturedImage in
                            if let capturedImage = capturedImage {
                                image = capturedImage
                                dismiss()
                            }
                        }
                    } label: {
                        Circle()
                            .strokeBorder(Color.white, lineWidth: 3)
                            .frame(width: 70, height: 70)
                            .background(Circle().fill(Color.white.opacity(0.2)))
                    }
                    
                    Spacer()
                    
                    Button {
                        dismiss()
                    } label: {
                        Image(systemName: "xmark")
                            .font(.system(size: 24))
                            .foregroundColor(.white)
                            .padding()
                    }
                }
                .padding(.bottom, 30)
            }
            
            if showFlash {
                VStack {
                    Text(cameraManager.flashMode == .on ? "Flash: On" : "Flash: Off")
                        .foregroundColor(.white)
                        .padding(8)
                        .background(Color.black.opacity(0.7))
                        .cornerRadius(8)
                    
                    Spacer()
                }
                .padding(.top, 50)
            }
        }
        .onAppear {
            DispatchQueue.global(qos: .background).async {
                cameraManager.session.startRunning()
            }
        }
        .onDisappear {
            cameraManager.session.stopRunning()
        }
    }
    
    func setupPreview() {
        let preview = AVCaptureVideoPreviewLayer(session: cameraManager.session)
        preview.videoGravity = .resizeAspectFill
        cameraManager.preview = preview
    }
}

// MARK: - UIViewRepresentable for Camera Preview
struct CameraPreview: UIViewRepresentable {
    var preview: AVCaptureVideoPreviewLayer
    
    func makeUIView(context: Context) -> UIView {
        let view = UIView(frame: UIScreen.main.bounds)
        preview.frame = view.frame
        view.layer.addSublayer(preview)
        return view
    }
    
    func updateUIView(_ uiView: UIView, context: Context) {}
}

// MARK: - Permission Denied View
struct PermissionDeniedView: View {
    let message: String
    let icon: String
    
    var body: some View {
        VStack(spacing: 20) {
            Image(systemName: icon)
                .font(.system(size: 60))
                .foregroundColor(.gray)
            
            Text("Permission Required")
                .font(.title2)
                .fontWeight(.bold)
            
            Text(message)
                .multilineTextAlignment(.center)
                .foregroundColor(.secondary)
            
            Button {
                if let url = URL(string: UIApplication.openSettingsURLString) {
                    UIApplication.shared.open(url)
                }
            } label: {
                Text("Open Settings")
                    .fontWeight(.semibold)
                    .frame(maxWidth: .infinity)
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            }
            .padding(.horizontal)
        }
        .padding()
    }
}

// MARK: - Preview
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Permission Texts in Info.plist

You’ll need to add these permission strings to your Info.plist file:

<!-- Camera Permission -->
<key>NSCameraUsageDescription</key>
<string>This app needs camera access to take photos.</string>

<!-- Photo Library Permission -->
<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs access to your photo library to select photos.</string>

Implementation Details

  1. ContentView: The main view with buttons for accessing camera or gallery
  2. PhotoPickerView: Handles photo library access using PhotosPicker API.
  3. CameraManager: Manages camera setup, permissions, and photo capture.
  4. CameraView: UI for the camera interface with capture button and flash toggle.
  5. Permission UI: Dedicated views for permission-denied scenarios.

Key Techniques Used

  • PHPhotoLibrary.requestAuthorization for photo library permissions.
  • AVCaptureDevice.requestAccess for camera permissions.
  • AVCaptureSession for managing the camera capture process.
  • SwiftUI’s .sheet modifier for presenting modals.
  • PhotosPicker for modern photo selection.
  • Environment values for dismissing sheets.
  • Error handling for both permission and capture processes.

This implementation follows best practices for iOS development, including proper permission handling, clean architecture separation, and modern SwiftUI APIs where available.

Thanks for reading!

Leave a Reply

Your email address will not be published. Required fields are marked *