You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI/UIKit实现BeReal式前后摄像头拍照功能遇阻求助

问题描述

我正在开发一款仿BeReal的待办事项应用,核心特色是任务24小时后自动消失。应用需要支持前后摄像头自动拍照,目前已实现前置摄像头拍摄,照片能正常显示在屏幕左上角,但选图按钮无响应。我知道AVFoundation可以实现双摄像头同时拍照,但对这个框架不熟悉,尝试用if语句拍完前置后切换后置摄像头,结果在拍摄完成后的选图环节卡住了。

SwiftUI 视图代码

import SwiftUI

struct CameraViewOne: View {
    @State private var isImagePickerPresented: Bool = false
    @State private var frontImage: UIImage? = nil
    @State private var backImage: UIImage? = nil
    
    var body: some View {
        ZStack {
            // 显示后置摄像头照片
            if let backImage = backImage {
                Image(uiImage: backImage)
                    .resizable()
                    .scaledToFit()
            }
            
            VStack {
                HStack {
                    // 显示前置摄像头照片在左上角
                    if let frontImage = frontImage {
                        Image(uiImage: frontImage)
                            .resizable()
                            .scaledToFit()
                            .frame(width: 100, height: 100)
                    }
                    Spacer()
                }
                Spacer()
            }
        }
        .sheet(isPresented: $isImagePickerPresented, content: {
            ImagePicker(isPresented: $isImagePickerPresented, frontImage: $frontImage, backImage: $backImage)
        })
        .onAppear {
            self.isImagePickerPresented = true
        }
    }
}

struct CameraViewOne_Previews: PreviewProvider {
    static var previews: some View {
        CameraViewOne()
    }
}

Swift(UIKit)代码

import SwiftUI
import UIKit

struct ImagePicker: UIViewControllerRepresentable {
    @Binding var isPresented: Bool
    @Binding var frontImage: UIImage?
    @Binding var backImage: UIImage?
    
    func makeUIViewController(context: Context) -> UIImagePickerController {
        let picker = UIImagePickerController()
        picker.sourceType = .camera
        picker.cameraDevice = .front
        picker.delegate = context.coordinator
        return picker
    }
    
    func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
    
    func makeCoordinator() -> Coordinator {
        Coordinator(parent: self)
    }
    
    class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
        var parent: ImagePicker
        var usingFrontCamera = true
        
        init(parent: ImagePicker) {
            self.parent = parent
        }
        
        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            if let uiImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
                if usingFrontCamera {
                    parent.frontImage = uiImage
                } else {
                    parent.backImage = uiImage
                }
            }
            
            if usingFrontCamera {
                picker.cameraDevice = .rear
                usingFrontCamera = false
            } else {
                picker.cameraDevice = .front
                usingFrontCamera = true
                parent.isPresented = false
            }
        }
        
        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
            parent.isPresented = false
        }
    }
}
解决方案

1. 选图按钮无响应的原因及修复

你当前的代码在拍完前置照片后直接切换摄像头,没有让UIImagePickerController完成didFinishPickingMediaWithInfo的流程——UIImagePickerController在调用这个方法后会自动dismiss控制器,导致切换摄像头的操作无效,同时打断选图按钮的逻辑。

修复方案:分两次触发拍照流程

修改ImagePicker支持指定摄像头,拍完前置后自动触发后置拍摄:

// 修改后的ImagePicker,支持指定拍摄摄像头
struct ImagePicker: UIViewControllerRepresentable {
    @Binding var isPresented: Bool
    @Binding var capturedImage: UIImage?
    let cameraDevice: UIImagePickerController.CameraDevice
    
    func makeUIViewController(context: Context) -> UIImagePickerController {
        let picker = UIImagePickerController()
        picker.sourceType = .camera
        picker.cameraDevice = cameraDevice
        picker.delegate = context.coordinator
        return picker
    }
    
    func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
    
    func makeCoordinator() -> Coordinator {
        Coordinator(parent: self)
    }
    
    class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
        var parent: ImagePicker
        
        init(parent: ImagePicker) {
            self.parent = parent
        }
        
        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            if let uiImage = info[.originalImage] as? UIImage {
                parent.capturedImage = uiImage
            }
            parent.isPresented = false
        }
        
        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
            parent.isPresented = false
        }
    }
}

// 修改后的CameraViewOne
struct CameraViewOne: View {
    @State private var showFrontCamera = false
    @State private var showBackCamera = false
    @State private var frontImage: UIImage? = nil
    @State private var backImage: UIImage? = nil
    
    var body: some View {
        ZStack {
            if let backImage = backImage {
                Image(uiImage: backImage)
                    .resizable()
                    .scaledToFit()
            }
            
            VStack {
                HStack {
                    if let frontImage = frontImage {
                        Image(uiImage: frontImage)
                            .resizable()
                            .scaledToFit()
                            .frame(width: 100, height: 100)
                    }
                    Spacer()
                }
                Spacer()
            }
        }
        .sheet(isPresented: $showFrontCamera) {
            ImagePicker(isPresented: $showFrontCamera, capturedImage: $frontImage, cameraDevice: .front)
        }
        .sheet(isPresented: $showBackCamera) {
            ImagePicker(isPresented: $showBackCamera, capturedImage: $backImage, cameraDevice: .rear)
        }
        .onAppear {
            showFrontCamera = true
        }
        .onChange(of: frontImage) { _ in
            // 前置拍完自动弹出后置摄像头
            if frontImage != nil {
                showBackCamera = true
            }
        }
    }
}

2. 用AVFoundation实现双摄像头同时拍照(贴近BeReal体验)

UIImagePickerController无法实现真正的同步拍照,要实现前后摄像头同时拍摄,必须使用AVFoundation框架,以下是核心实现:

步骤1:创建双摄像头管理类

import AVFoundation
import SwiftUI

class DualCameraManager: NSObject, ObservableObject {
    @Published var frontImage: UIImage?
    @Published var backImage: UIImage?
    private let session = AVCaptureSession()
    private var frontCamera: AVCaptureDevice?
    private var backCamera: AVCaptureDevice?
    private var frontOutput: AVCapturePhotoOutput?
    private var backOutput: AVCapturePhotoOutput?
    
    override init() {
        super.init()
        setupSession()
    }
    
    private func setupSession() {
        session.sessionPreset = .photo
        
        // 获取前后置摄像头
        frontCamera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front)
        backCamera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)
        
        // 添加摄像头输入到会话
        if let frontCam = frontCamera, let input = try? AVCaptureDeviceInput(device: frontCam) {
            if session.canAddInput(input) {
                session.addInput(input)
            }
        }
        
        if let backCam = backCamera, let input = try? AVCaptureDeviceInput(device: backCam) {
            if session.canAddInput(input) {
                session.addInput(input)
            }
        }
        
        // 创建照片输出对象
        frontOutput = AVCapturePhotoOutput()
        if let frontOut = frontOutput, session.canAddOutput(frontOut) {
            session.addOutput(frontOut)
        }
        
        backOutput = AVCapturePhotoOutput()
        if let backOut = backOutput, session.canAddOutput(backOut) {
            session.addOutput(backOut)
        }
        
        // 后台启动会话
        DispatchQueue.global(qos: .background).async {
            self.session.startRunning()
        }
    }
    
    // 同步拍摄前后照片
    func captureBothPhotos() {
        let settings = AVCapturePhotoSettings()
        frontOutput?.capturePhoto(with: settings, delegate: self)
        backOutput?.capturePhoto(with: settings, delegate: self)
    }
}

extension DualCameraManager: AVCapturePhotoCaptureDelegate {
    func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
        guard let imageData = photo.fileDataRepresentation(), let image = UIImage(data: imageData) else { return }
        
        if output == frontOutput {
            frontImage = image
        } else if output == backOutput {
            backImage = image
        }
    }
}

步骤2:在SwiftUI视图中使用

struct DualCameraView: View {
    @StateObject private var cameraManager = DualCameraManager()
    
    var body: some View {
        ZStack {
            // 显示后置照片
            if let backImage = cameraManager.backImage {
                Image(uiImage: backImage)
                    .resizable()
                    .scaledToFit()
            }
            
            VStack {
                HStack {
                    // 显示前置照片
                    if let frontImage = cameraManager.frontImage {
                        Image(uiImage: frontImage)
                            .resizable()
                            .scaledToFit()
                            .frame(width: 100, height: 100)
                    }
                    Spacer()
                }
                Spacer()
                
                Button("同时拍照") {
                    cameraManager.captureBothPhotos()
                }
                .foregroundColor(.white)
                .padding()
                .background(Color.blue)
                .cornerRadius(10)
            }
        }
        .onAppear {
            // 可选:自动触发拍照
            DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                cameraManager.captureBothPhotos()
            }
        }
    }
}

关键注意事项

  • 必须在Info.plist中添加相机权限申请:NSCameraUsageDescription,否则应用会崩溃。
  • AVCaptureSession需要在后台线程启动,避免阻塞主线程。
  • 双摄像头同步拍摄需要设备硬件支持,部分旧设备可能无法实现。

内容的提问来源于stack exchange,提问作者Joseph Garcia

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 15:09:52