使用Aespa库时,如何在updateUIViewController中正确更新frame?
解决Aespa相机预览Frame不稳定的问题
问题核心在于原库用DispatchQueue.main.async异步设置previewLayer.frame的方式不可靠——updateUIViewController调用时,UIKit控制器的view bounds还未同步到SwiftUI布局的最终尺寸,而异步任务的执行顺序又受主队列任务影响,导致不同场景下尺寸不一致。
正确的实现方式:监听视图布局完成事件
UIKit中,viewDidLayoutSubviews是专门处理视图布局稳定后的回调,每次视图bounds确定后都会触发,是更新子视图frame的标准时机。我们可以通过自定义托管ViewController来替代原库的默认实现:
1. 自定义预览托管控制器
class PreviewHostViewController: UIViewController { var previewLayer: AVCaptureVideoPreviewLayer! var gravity: AVLayerVideoGravity = .resizeAspect override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() // 布局完成后,直接用当前view的bounds设置预览层尺寸 previewLayer.frame = view.bounds } }
2. 修改UIViewControllerRepresentable实现
替换原库的Preview结构体实现,将预览层的frame更新逻辑移到viewDidLayoutSubviews中:
struct FixedPreview: UIViewControllerRepresentable { let previewLayer: AVCaptureVideoPreviewLayer var gravity: AVLayerVideoGravity = .resizeAspect func makeUIViewController(context: Context) -> PreviewHostViewController { let vc = PreviewHostViewController() vc.previewLayer = previewLayer vc.gravity = gravity vc.view.layer.addSublayer(previewLayer) return vc } func updateUIViewController(_ uiViewController: PreviewHostViewController, context: Context) { // 仅更新视频重力属性,frame交给布局回调处理 uiViewController.previewLayer.videoGravity = gravity } }
3. 替换原有使用方式
如果不想修改Aespa库源码,可以自己封装一层,在ViewModel中返回自定义的FixedPreview:
@Observable class ViewModel { @ObservationIgnored var aespaSession = Aespa.session(with: AespaOption(albumName: nil)) @ObservationIgnored var preview: FixedPreview { let layer = aespaSession.interactivePreviewLayer(...) // 获取原预览层 return FixedPreview(previewLayer: layer, gravity: .resizeAspectFill) } var show = false }
为什么这能解决问题?
viewDidLayoutSubviews会在SwiftUI完成布局传递、UIKit视图bounds稳定后触发,能保证拿到的是最终的尺寸。- 去掉异步派发后,不再依赖主队列的任务执行顺序,无论父视图布局如何变化(比如zIndex调整、屏幕旋转),预览层都会自动适配正确的frame,让
Preview().frame(width: 350, height: 500)的设置稳定生效。
内容的提问来源于stack exchange,提问作者bli00
相关产品推荐
相关产品推荐

