自定义QLPreviewController的ARQuickLook切换USDZ模型异常问题求解
问题根因
你遇到的问题本质是QLPreviewController不支持子类化修改视图层级、也不支持热重载当前预览的USDZ模型,调用reloadData/refreshCurrentPreviewItem会强制中断内部AR会话,切回物体预览模式,甚至出现会话泄露导致后续AR功能失效。
可行解决方案
方案1:使用RealityKit的ARView自主实现AR预览(最稳定)
苹果官方不推荐子类化QLPreviewController,如果需要自定义AR预览页的UI和交互,直接用ARView加载USDZ是最优解,全程AR会话不会中断,完全可控:
- 新建普通UIViewController,视图层级最底层放
ARView作为USDZ渲染载体,上层直接叠加你需要的collectionView、按钮等自定义UI,无需修改任何系统组件的视图结构 - 切换商品颜色时直接替换
ARView场景内的模型即可,无需重启AR会话
示例代码如下:
import RealityKit import ARKit class CustomARViewController: UIViewController { let arView = ARView(frame: .zero) var currentModelEntity: Entity? override func viewDidLoad() { super.viewDidLoad() // 配置AR基础能力 arView.frame = view.bounds arView.autoresizingMask = [.flexibleWidth, .flexibleHeight] view.addSubview(arView) // 开启原生QuickLook同款的模型自动放置、缩放/旋转/平移手势 arView.automaticallyConfigureSession = true arView.installGestures([.rotation, .scale, .translation], for: nil) // 添加自定义UI组件 setupCustomUIElements() // 加载默认颜色的USDZ模型 loadUSDZModel(url: getDefaultProductUrl()) } private func loadUSDZModel(url: URL) { // 移除旧模型 currentModelEntity?.removeFromParent() do { // 加载新USDZ模型并添加到场景 let newModel = try Entity.load(contentsOf: url) let anchor = AnchorEntity(plane: .horizontal) anchor.addChild(newModel) arView.scene.addAnchor(anchor) currentModelEntity = newModel } catch { print("USDZ模型加载失败:\(error.localizedDescription)") } } // collectionView选中颜色时调用 func didSelectColor(at index: Int) { let targetModelUrl = getProductUrl(colorIndex: index) loadUSDZModel(url: targetModelUrl) } }
方案2:必须保留QLPreviewController的适配方案
如果一定要用QLPreviewController的原生能力,按如下方式修改即可解决90%以上的异常:
- 不要子类化QLPreviewController添加自定义视图,而是把你的collectionView、按钮等UI加到当前的keyWindow或者QLPreviewController的presentingViewController的view上,避免修改系统组件的视图层级
- 不要调用
reloadData/refreshCurrentPreviewItem重载模型,提前把所有颜色对应的USDZ URL预存到数组中,让numberOfPreviewItems返回数组的总长度 - 用户切换颜色时,直接调用
setCurrentPreviewItemIndex(_:animated:)方法切换到对应索引的预览项即可
注意:该方案在部分旧iOS版本上仍可能出现AR会话中断的问题,兼容性不如方案1
额外注意事项
- 所有USDZ文件要提前下载到本地沙盒再传入预览组件,不要直接加载远程URL
- AR会话异常时可以调用
ARSession.shared().reset()手动重置,避免必须重启APP才能恢复的问题
内容的提问来源于stack exchange,提问作者gstream
相关产品推荐
相关产品推荐

