SwiftUI集成带AVSampleBufferDisplayLayer的UIViewRepresentable黑屏问题排查
解决visionOS中SwiftUI下AVSampleBufferDisplayLayer黑屏问题
问题描述
在visionOS开发中,尝试脱离UIViewController架构,改用SwiftUI结合RealityKit/VideoPlayerLayer方案,当前原型阶段需要让AVSampleBufferDisplayLayer正常工作,但加载视图时仅显示黑屏。相同逻辑的UIKit(Storyboard+UIViewController)版本可在iOS/AVP设备上正常运行。
现有代码
SwiftUI版本(存在黑屏问题)
struct MirrorView: View { var body: some View { VStack { LayerView() } } } struct LayerView: UIViewRepresentable { func makeUIView(context: Context) -> UIView { print("LayerUIView is being created") return LayerUIView() } func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<LayerView>) { print("LayerUIView is being updated") } } class LayerUIView: UIView { private let networking = Networking() private let displayLayer = AVSampleBufferDisplayLayer() private var subscriptions = Set<AnyCancellable>() private var sampleBufferTask: Task<Void, Never>? override init(frame: CGRect) { super.init(frame: frame) print("LayerUIView initialized") setupVideoLayer() setupNetworking() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() layer.frame = bounds // 错误点:未更新视频层frame } private func setupVideoLayer() { displayLayer.frame = bounds displayLayer.videoGravity = .resizeAspect layer.addSublayer(displayLayer) NotificationCenter.default.addObserver( self, selector: #selector(handleFailedToDecodeNotification(_:)), name: .AVSampleBufferDisplayLayerFailedToDecode, object: displayLayer ) } @objc private func handleFailedToDecodeNotification(_ notification: Notification) { if let error = notification.userInfo?[AVSampleBufferDisplayLayerFailedToDecodeNotificationErrorKey] { print("Failed to decode sample buffer. Error: \(error)") } else { print("Failed to decode sample buffer. No error information available.") } } private func setupNetworking() { networking.startAdvertising() print("Networking is connected: \(networking.isConnected)") startSampleBufferTask() } // MARK: - Task Management private func startSampleBufferTask() { sampleBufferTask = Task { for await sampleBuffer in networking.sampleBuffers { let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer) print("Format Description: \(String(describing: formatDescription))") let presentationTimeStamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer) print("Presentation Timestamp: \(presentationTimeStamp)") let duration = CMSampleBufferGetDuration(sampleBuffer) print("Duration: \(duration)") DispatchQueue.main.async { self.displayLayer.sampleBufferRenderer.enqueue(sampleBuffer) } } } } private func stopSampleBufferTask() { sampleBufferTask?.cancel() sampleBufferTask = nil } } #Preview { MirrorView() }
UIKit版本(正常运行)
class ViewController: UIViewController { // MARK: - Properties private let networking = Networking() private let displayLayer = AVSampleBufferDisplayLayer() private var subscriptions = Set<AnyCancellable>() private var sampleBufferTask: Task<Void, Never>? // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() setupVideoLayer() setupNetworking() } override var supportedInterfaceOrientations: UIInterfaceOrientationMask { return .allButUpsideDown } override var shouldAutorotate: Bool { return true } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) coordinator.animate(alongsideTransition: { _ in // Adjust layout for new orientation self.displayLayer.frame = self.view.bounds }, completion: nil) } // MARK: - Setup Methods private func setupVideoLayer() { displayLayer.frame = view.bounds displayLayer.videoGravity = .resizeAspect displayLayer.backgroundColor = UIColor.black.cgColor view.layer.addSublayer(displayLayer) NotificationCenter.default.addObserver( self, selector: #selector(handleFailedToDecodeNotification(_:)), name: .AVSampleBufferDisplayLayerFailedToDecode, object: displayLayer ) } @objc private func handleFailedToDecodeNotification(_ notification: Notification) { if let error = notification.userInfo?[AVSampleBufferDisplayLayerFailedToDecodeNotificationErrorKey] { print("Failed to decode sample buffer. Error: \(error)") } else { print("Failed to decode sample buffer. No error information available.") } } private func setupNetworking() { networking.startAdvertising() print("Networking is connected: \(networking.isConnected)") startSampleBufferTask() } // MARK: - Task Management private func startSampleBufferTask() { sampleBufferTask = Task { for await sampleBuffer in networking.sampleBuffers { let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer) print("Format Description: \(String(describing: formatDescription))") let presentationTimeStamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer) print("Presentation Timestamp: \(presentationTimeStamp)") let duration = CMSampleBufferGetDuration(sampleBuffer) print("Duration: \(duration)") DispatchQueue.main.async { self.displayLayer.sampleBufferRenderer.enqueue(sampleBuffer) } } } } private func stopSampleBufferTask() { sampleBufferTask?.cancel() sampleBufferTask = nil } }
问题排查与修复
对比两份代码,SwiftUI版本的核心问题在于视频层的布局同步错误:
修正layoutSubviews方法:
原代码中layoutSubviews更新的是自身layer.frame = bounds,但需要同步更新displayLayer的frame,确保视频层始终匹配父视图的尺寸。初始化时bounds可能为CGRect.zero,后续视图布局时必须更新视频层的frame才能正常显示。添加displayLayer背景色(可选):
和UIKit版本保持一致,设置displayLayer.backgroundColor = UIColor.black.cgColor,避免视频未加载时显示透明背景。
修正后的LayerUIView关键代码
class LayerUIView: UIView { // ... 其他属性保持不变 ... override func layoutSubviews() { super.layoutSubviews() displayLayer.frame = bounds // 替换原有的layer.frame = bounds } private func setupVideoLayer() { displayLayer.frame = bounds displayLayer.videoGravity = .resizeAspect displayLayer.backgroundColor = UIColor.black.cgColor // 添加背景色 layer.addSublayer(displayLayer) NotificationCenter.default.addObserver( self, selector: #selector(handleFailedToDecodeNotification(_:)), name: .AVSampleBufferDisplayLayerFailedToDecode, object: displayLayer ) } // ... 其他方法保持不变 ... }
额外检查点
如果修复后仍黑屏,可检查以下内容:
- 确认
networking.sampleBuffers输出的CMSampleBuffer格式正确,包含有效的格式描述和时间戳 - 检查
AVSampleBufferDisplayLayerFailedToDecode通知是否触发,排查解码错误 - 确保
sampleBufferRenderer.enqueue调用在主线程(当前代码已满足)
内容的提问来源于stack exchange,提问作者TJ_SpectiveLabs
相关产品推荐
相关产品推荐

