如何在SwiftUI视图中获取AVKit计算的亮度值?
解决SwiftUI视图访问AVCapture代理计算的亮度值问题
要让SwiftUI视图获取VideoDelegate中计算出的亮度值,核心是让代理类能把数据传递给VideoStream这个ObservableObject,再通过@Published属性让视图自动响应更新。以下是具体实现方案:
修改步骤与代码
1. 给VideoDelegate添加回调闭包
在代理类中定义一个闭包,用于传递计算好的亮度值,同时优化原代码中的强制解包逻辑避免崩溃:
class VideoDelegate: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate { // 定义回调闭包,用于传递亮度值 var luminosityUpdateHandler: ((Double) -> Void)? func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { // 读取帧的EXIF元数据 let rawMetadata = CMCopyDictionaryOfAttachments(allocator: nil, target: sampleBuffer, attachmentMode: CMAttachmentMode(kCMAttachmentMode_ShouldPropagate)) let metadata = CFDictionaryCreateMutableCopy(nil, 0, rawMetadata) as NSMutableDictionary guard let exifData = metadata.value(forKey: "{Exif}") as? NSMutableDictionary else { return } // 安全解析EXIF参数,替换原强制解包 guard let fNumber = exifData["FNumber"] as? Double, let exposureTime = exifData["ExposureTime"] as? Double, let isoArray = exifData["ISOSpeedRatings"] as? NSArray, let isoSpeed = isoArray.firstObject as? Double else { return } let calibrationConstant: Double = 50 let luminosity = (calibrationConstant * fNumber * fNumber) / (exposureTime * isoSpeed) // 切换到主线程调用回调,保证UI更新安全 DispatchQueue.main.async { self.luminosityUpdateHandler?(luminosity) } } }
2. 在VideoStream中绑定回调并持有代理实例
修改VideoStream,创建代理时绑定闭包更新@Published属性,同时持有代理实例避免被提前释放:
class VideoStream: ObservableObject { @Published var luminosityReading: Double = 0.0 var session: AVCaptureSession! // 持有代理实例,防止被自动释放 private var videoDelegate: VideoDelegate! init() { authorizeCapture() } func authorizeCapture() { // 权限申请逻辑,完成后调用beginCapture() } func beginCapture() { session = AVCaptureSession() session.beginConfiguration() let videoDevice = bestDevice() // 自定义的设备选择逻辑 guard let videoDeviceInput = try? AVCaptureDeviceInput(device: videoDevice), session.canAddInput(videoDeviceInput) else { print("Camera selection failed") return } let videoOutput = AVCaptureVideoDataOutput() guard session.canAddOutput(videoOutput) else { print("Error creating video output") return } session.sessionPreset = .high session.addOutput(videoOutput) let queue = DispatchQueue(label: "VideoFrameQueue") videoDelegate = VideoDelegate() // 绑定回调,更新亮度值到Published属性 videoDelegate.luminosityUpdateHandler = { [weak self] luminosity in self?.luminosityReading = luminosity } videoOutput.setSampleBufferDelegate(videoDelegate, queue: queue) session.commitConfiguration() session.startRunning() } }
3. ContentView保持原有逻辑即可
原视图代码不需要修改,@StateObject会自动监听luminosityReading的变化并更新UI:
struct ContentView: View { @StateObject var videoStream = VideoStream() var body: some View { Text("\(videoStream.luminosityReading) ?? Detecting...") .padding() } }
关键注意事项
- 必须在主线程更新
@Published属性:captureOutput方法在后台队列执行,直接更新会导致UI异常,所以用DispatchQueue.main.async切换线程。 - 持有代理实例:如果不把
VideoDelegate赋值给VideoStream的属性,创建的代理会被立即释放,无法收到回调。 - 安全解析数据:用
guard替换强制解包,避免EXIF数据缺失时崩溃。
内容的提问来源于stack exchange,提问作者John Harrington
相关产品推荐
相关产品推荐

