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
相关产品推荐
相关产品推荐

