SwiftUI中UIImagePickerController关闭后相机仍被占用问题求助
问题根因
UIImagePickerController被声明为VideoRecordingView的存储属性,且被Coordinator强持有,dismiss后实例无法释放,内部AVCapture会话持续运行,导致相机占用指示灯常亮- Coordinator初始化时添加的通知中心观察者未做移除操作,且通知闭包强持有了parent实例,造成循环引用,进一步导致Coordinator和
UIImagePickerController整体内存泄漏 - 切后台时仅执行了
stopVideoCapture,你注释了dismiss代码,此时UIImagePickerController仍处于活跃状态,返回前台后会自动恢复相机访问 - dismiss选择器时未手动清空
UIImagePickerController的delegate、sourceType等属性,系统内部可能仍保留相机资源引用
修复方案
- 不要把
UIImagePickerController作为结构体存储属性,改为在makeUIViewController方法内局部创建,避免不必要的强持有 - 修复Coordinator循环引用问题,通知闭包添加弱引用标记,deinit时移除通知观察者
- 恢复切后台时的dismiss逻辑,确保后台时picker被正常关闭
- dismiss前主动清理picker的相关属性,强制释放相机资源
修复后完整代码
import SwiftUI import AVFoundation import Photos struct VideoRecordingView: UIViewControllerRepresentable { @Binding var videoURL: URL? @Environment(\.viewController) private var viewControllerHolder: UIViewController? init(videoURL: Binding<URL?>) { self._videoURL = videoURL } func makeUIViewController(context: Context) -> UIImagePickerController { // 改为局部创建,不做存储属性 let imagePickerController = UIImagePickerController() imagePickerController.allowsEditing = true imagePickerController.sourceType = .camera imagePickerController.mediaTypes = ["public.movie"] imagePickerController.videoMaximumDuration = .infinity if #available(iOS 14.0, *) { imagePickerController.videoQuality = .typeHigh } imagePickerController.delegate = context.coordinator return imagePickerController } func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {} func makeCoordinator() -> Coordinator { Coordinator(self) } final class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate { weak var parent: VideoRecordingView? // 改为弱引用避免循环持有 private let notificationObserver: NSObjectProtocol? init(_ parent: VideoRecordingView) { self.parent = parent // 保存观察者引用方便后续移除 notificationObserver = NotificationCenter.default.addObserver( forName: UIApplication.willResignActiveNotification, object: nil, queue: .main ) { [weak self] notification in guard let self = self, let picker = self.parent?.viewControllerHolder?.presentedViewController as? UIImagePickerController else { return } picker.stopVideoCapture() // 恢复dismiss逻辑,后台时直接关闭picker self.parent?.viewControllerHolder?.dismiss(animated: true) } } deinit { // 移除通知观察者 if let observer = notificationObserver { NotificationCenter.default.removeObserver(observer) } } func requestAuthorizationToPhotos(completionHandler: @escaping (Bool) -> Void) { guard PHPhotoLibrary.authorizationStatus() != .authorized else { completionHandler(true) return } PHPhotoLibrary.requestAuthorization { status in completionHandler(status == .authorized) } } func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { picker.stopVideoCapture() cleanUpPicker(picker) parent?.viewControllerHolder?.dismiss(animated: true) } func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { picker.stopVideoCapture() if let videoURL = info[.mediaURL] as? URL { DispatchQueue.global(qos: .background).async { [weak self] in let fileURL = videoURL.copyFileToTempDirectory() DispatchQueue.main.async { self?.parent?.videoURL = fileURL } self?.requestAuthorizationToPhotos { granted in if granted, UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(videoURL.path) { UISaveVideoAtPathToSavedPhotosAlbum(videoURL.path, nil, nil, nil) } } } } cleanUpPicker(picker) parent?.viewControllerHolder?.dismiss(animated: true) } // 新增picker资源清理方法 private func cleanUpPicker(_ picker: UIImagePickerController) { picker.delegate = nil picker.sourceType = .photoLibrary // 切换源类型强制释放相机资源 } } }
内容的提问来源于stack exchange,提问作者Ehsan Askari
相关产品推荐
相关产品推荐

