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

iOS 17相机DeferredProcessing使用触发NSInvalidArgumentException异常

解决iOS 17 DeferredProcessing相机拍照崩溃问题

问题场景

在iOS 17相机应用中使用DeferredProcessing新特性,调用拍照方法时触发NSInvalidArgumentException异常导致应用崩溃。

实现代码

import UIKit
import AVFoundation
import Photos

//DeferredProcessing

class ViewController: UIViewController, AVCapturePhotoCaptureDelegate,AVCaptureVideoDataOutputSampleBufferDelegate {
    
    var previewView : UIView!
    let captureButton = UIButton(type: .system)
    let thumbnailImageView = UIImageView()
    
    var boxView:UIView!
    var videoDataOutput: AVCaptureVideoDataOutput!
    var videoDataOutputQueue: DispatchQueue!
    var previewLayer:AVCaptureVideoPreviewLayer!
    var captureDevice : AVCaptureDevice!
    var photoOuput: AVCapturePhotoOutput!
    let session = AVCaptureSession()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        previewView = UIView(frame: view.frame)
        previewView.translatesAutoresizingMaskIntoConstraints = false
        previewView.contentMode = UIView.ContentMode.scaleAspectFit
        view.addSubview(previewView)
        
        boxView = UIView(frame: view.frame)
        view.addSubview(boxView)
        
        // Create the central button at the bottom (rounded button)
        
        captureButton.backgroundColor = .white
        captureButton.layer.cornerRadius = 35 // Rounded with height/2
        captureButton.translatesAutoresizingMaskIntoConstraints = false
        captureButton.addTarget(self, action: #selector(onClickMyButton(sender:)), for: .touchUpInside)
        view.addSubview(captureButton)
        
        // Create the image view at the bottom left (rounded image view)
        thumbnailImageView.backgroundColor = .white
        thumbnailImageView.layer.cornerRadius = 25 // Rounded with height/2
        thumbnailImageView.clipsToBounds = true
        thumbnailImageView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(thumbnailImageView)
        
        NSLayoutConstraint.activate([
            captureButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            captureButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20),
            captureButton.widthAnchor.constraint(equalToConstant: 70),
            captureButton.heightAnchor.constraint(equalToConstant: 70)
        ])
        
        // Set up constraints for the image view at the bottom left
        NSLayoutConstraint.activate([
            thumbnailImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            thumbnailImageView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20),
            thumbnailImageView.widthAnchor.constraint(equalToConstant: 50),
            thumbnailImageView.heightAnchor.constraint(equalToConstant: 50)
        ])
        
        setupAVCapture()
    }
    
    @objc func onClickMyButton(sender: UIButton){
        let settings = AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg])
        photoOuput.capturePhoto(with: settings, delegate: self)
        
    }
    
    func photoOutput(_ output: AVCapturePhotoOutput, didFinishCapturingDeferredPhotoProxy deferredPhotoProxy: AVCaptureDeferredPhotoProxy?, error: (any Error)?) {
        if let error = error {
            print("Error capturing photo \(error)")
            return
        }
        
        let library = PHPhotoLibrary.shared()
        if let data = deferredPhotoProxy?.fileDataRepresentation() {
            library.performChanges {
                let request = PHAssetCreationRequest.forAsset()
                request.addResource(with: .photoProxy, data: data, options: nil)
            } completionHandler: { success, photoError in
                if let photoError = photoError {
                    print("Error \(photoError)")
                    return
                }
                
                debugPrint("Sucess")
                
            }
        }
        
    }
    
    func requestAndHandleImageRepresentations(asset: PHAsset, targetSize: CGSize, contentMode: PHImageContentMode) -> PHImageRequestID {
        let imagemanager = PHImageManager.default()
        let imageRequestOptions = PHImageRequestOptions()
        imageRequestOptions.allowSecondaryDegradedImage = true
        return imagemanager.requestImage(for: asset, targetSize: targetSize, contentMode: contentMode, options: imageRequestOptions) { resultImage, info in
            if let info = info {
                if (info[PHImageResultIsDegradedKey] != nil){
                    
                } else {
                    
                }
            }
            
        }
    }
    
    
    func setupAVCapture(){
        session.beginConfiguration()
        session.sessionPreset = AVCaptureSession.Preset.photo
        guard let device = AVCaptureDevice
            .default(AVCaptureDevice.DeviceType.builtInWideAngleCamera,
                     for: .video,
                     position: AVCaptureDevice.Position.back) else {
            return
        }
        captureDevice = device
        beginSession()
    }
    
    func beginSession(){
        var deviceInput: AVCaptureDeviceInput!
        
        do {
            deviceInput = try AVCaptureDeviceInput(device: captureDevice)
            photoOuput = AVCapturePhotoOutput()
            guard deviceInput != nil else {
                print("error: cant get deviceInput")
                return
            }
            
            if session.canAddInput(deviceInput) && session.canAddOutput(photoOuput){
                session.addInput(deviceInput)
                session.addOutput(photoOuput)
            }
            
            if(photoOuput.isAutoDeferredPhotoDeliverySupported){
                photoOuput.isAutoDeferredPhotoDeliveryEnabled = true
            }
            
            session.commitConfiguration()
            
            videoDataOutput = AVCaptureVideoDataOutput()
            videoDataOutput.alwaysDiscardsLateVideoFrames=true
            videoDataOutputQueue = DispatchQueue(label: "VideoDataOutputQueue")
            videoDataOutput.setSampleBufferDelegate(self, queue:videoDataOutputQueue)
            
            if session.canAddOutput(videoDataOutput){
                session.addOutput(videoDataOutput)
            }
            
            videoDataOutput.connection(with: .video)?.isEnabled = true
            
            previewLayer = AVCaptureVideoPreviewLayer(session: session)
            previewLayer.videoGravity = AVLayerVideoGravity.resizeAspect
            
            let rootLayer = previewView.layer
            rootLayer.masksToBounds=true
            previewLayer.frame = rootLayer.bounds
            rootLayer.addSublayer(previewLayer)
            DispatchQueue.global(qos: .userInitiated).async { //[weak self] in
                self.session.startRunning()
            }
        } catch let error as NSError {
            deviceInput = nil
            print("error: \(error.localizedDescription)")
        }
    }
    
    func stopCamera(){
        session.stopRunning()
    }
    
}

崩溃日志

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[AVCapturePhotoOutput capturePhotoWithSettings:delegate:] If you specify a non-nil format dictionary in your settings, your delegate must respond to the selector captureOutput:didFinishProcessingPhoto:error:, or the deprecated captureOutput:didFinishProcessingPhotoSampleBuffer:previewPhotoSampleBuffer:resolvedSettings:bracketSettings:error:'
*** First throw call stack:
(0x18936cf20 0x181272018 0x1a63d574c 0x10441e9f4 0x10441eae0 0x18b96531c 0x18b9651f4 0x18b965044 0x18be7c080 0x18be7d894 0x18b8a5eac 0x18b8a5954 0x18b7269e0 0x18b7281d4 0x18b730ecc 0x18b62384c 0x18b62176c 0x18b6213b0 0x18b622254 0x18933f834 0x18933f7c8 0x18933d298 0x18933c484 0x18933bcd8 0x1ce1ec1a8 0x18b97490c 0x18ba289d0 0x18bba2384 0x104423994 0x10442390c 0x104423a10 0x1ac9ede4c)
libc++abi: terminating due to uncaught exception of type NSException

问题分析

崩溃日志明确指出:当通过AVCapturePhotoSettings(format:)传入非空格式字典时,代理对象必须实现photoOutput(_:didFinishProcessingPhoto:error:)方法(或已废弃的旧版本方法)。当前代码仅实现了延迟处理的didFinishCapturingDeferredPhotoProxy方法,缺失系统要求的普通照片处理回调,导致系统无法找到对应的处理逻辑,触发异常。

解决方案

提供两种可行的修复方式:

方式一:移除自定义格式参数

如果不需要指定特定编码格式,直接使用默认初始化方法创建AVCapturePhotoSettings:

@objc func onClickMyButton(sender: UIButton){
    let settings = AVCapturePhotoSettings()
    photoOuput.capturePhoto(with: settings, delegate: self)
}

方式二:添加要求的代理方法

即使不需要处理普通照片结果,也需要实现该方法以满足系统要求:

func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
    // 可根据需求添加错误处理或空实现
    if let error = error {
        print("照片处理错误: \(error.localizedDescription)")
    }
}

补充说明

虽然开启了isAutoDeferredPhotoDeliveryEnabled会触发延迟处理的回调,但系统仍要求代理实现基础的照片处理方法,尤其是在自定义AVCapturePhotoSettings配置时。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 12:52:04