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

如何让Swift KVO对AVCaptureDevice静态成员portraitEffectEnabled生效?

问题:监听AVCaptureDevice的portraitEffectEnabled变化时KVO未触发

我有一个UIViewController,代码如下。希望监听控制中心内人像效果(portrait effect)值的变化,尝试了AVCaptureDevice.isPortraitEffectEnabled和.portraitEffectEnabled两种方式,但都遇到相同问题:observeValue()从未被调用。我已确认该值确实会变化,且文档表明该属性支持KVO。

测试时,我通过调用AVCaptureDevice.showSystemUserInterface(.videoEffects)来切换portraitEffectEnabled的开关,期望KVO能触发。

@objc class EventSettingsCaptureViewController : UIViewController, ... {

    required init(...) {
        super.init(nibName: nil, bundle: nil)

        if #available(iOS 15.0, *) {
            AVCaptureDevice.self.addObserver(self, forKeyPath: "portraitEffectEnabled", options: [.new], context: nil)
        }
    }

    deinit {
        if #available(iOS 15.0, *) {
            AVCaptureDevice.self.removeObserver(self, forKeyPath: "portraitEffectEnabled", context: nil)
        }
    }

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {

        // Breakpoint set here: never hits
        if keyPath == "portraitEffectEnabled" {
            guard let object = object as? AVCaptureDevice.Type else { return }

            if #available(iOS 15.0, *) {
                WLog("isPortraitEffectEnabled changed: \(object.isPortraitEffectEnabled)")
            }

        } else {
            super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        }
    }
}

问题原因与解决方案

问题核心在于:你监听的是AVCaptureDevice类本身,但portraitEffectEnabled的KVO通知是发送给具体的AVCaptureDevice实例的,类对象不会触发这个属性的变更通知。

正确的做法是获取实际的视频捕获设备实例(比如前置摄像头,人像效果通常用于前置),然后给这个实例添加KVO监听,具体步骤如下:

  • 获取当前用于视频捕获的设备实例(以前置摄像头为例)
  • 给该实例添加KVO监听,而非AVCaptureDevice.self
  • 在deinit中移除该实例的监听

修改后的代码示例:

@objc class EventSettingsCaptureViewController : UIViewController {

    private var videoDevice: AVCaptureDevice?

    required init(...) {
        super.init(nibName: nil, bundle: nil)
        
        if #available(iOS 15.0, *) {
            // 获取前置摄像头设备
            videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front)
            // 给设备实例添加监听
            videoDevice?.addObserver(self, forKeyPath: "portraitEffectEnabled", options: [.new], context: nil)
        }
    }

    deinit {
        if #available(iOS 15.0, *) {
            // 移除设备实例的监听
            videoDevice?.removeObserver(self, forKeyPath: "portraitEffectEnabled", context: nil)
        }
    }

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if keyPath == "portraitEffectEnabled" {
            guard let device = object as? AVCaptureDevice else { return }
            
            if #available(iOS 15.0, *) {
                WLog("portraitEffectEnabled changed: \(device.portraitEffectEnabled)")
            }
        } else {
            super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        }
    }
}

额外注意事项

  • 确保获取到的videoDevice不为nil,否则监听不会生效
  • 如果场景中存在设备切换(比如前后摄像头切换),需要在切换前移除旧设备的监听,再给新设备添加监听

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 09:57:18