AR视图向.sheet传递变量时应用冻结或数据失效
解决RealityKit视频平面点击Sheet显示异常问题
问题1:@Binding传递变量时Sheet显示空值
核心原因是RealityKit的触摸回调默认在后台队列执行,直接更新@Binding变量不会立刻触发SwiftUI视图刷新,导致Sheet弹出时变量还未同步。
解决步骤:
- 用
DispatchQueue.main.async包裹状态更新操作,确保在主线程修改SwiftUI状态 - 分开控制Sheet显示的Bool变量和名称变量,先更新名称再触发Sheet
示例代码:
struct ARViewContainer: UIViewRepresentable { @Binding var showSheet: Bool @Binding var tappedEntityName: String func makeUIView(context: Context) -> ARView { let arView = ARView(frame: .zero) // 创建带VideoMaterial的平面实体 let videoURL = Bundle.main.url(forResource: "video", withExtension: "mp4")! let videoMaterial = VideoMaterial(avPlayer: AVPlayer(url: videoURL)) let plane = ModelEntity(mesh: .generatePlane(width: 1, height: 0.5), materials: [videoMaterial]) plane.name = "VideoPlane" // 添加触摸手势 let tapGesture = UITapGestureRecognizer(target: context.coordinator, action: #selector(context.coordinator.handleTap(_:))) arView.addGestureRecognizer(tapGesture) arView.scene.addAnchor(plane) return arView } func updateUIView(_ uiView: ARView, context: Context) {} func makeCoordinator() -> Coordinator { Coordinator(parent: self) } class Coordinator: NSObject { let parent: ARViewContainer init(parent: ARViewContainer) { self.parent = parent } @objc func handleTap(_ gesture: UITapGestureRecognizer) { let arView = gesture.view as! ARView let location = gesture.location(in: arView) if let entity = arView.entity(at: location) { // 强制在主线程更新SwiftUI状态 DispatchQueue.main.async { self.parent.tappedEntityName = entity.name self.parent.showSheet = true } print("Tapped entity: \(entity.name)") } } } } // 主视图 struct ContentView: View { @State private var showSheet = false @State private var entityName = "Unknown entity tapped" var body: some View { ZStack { ARViewContainer(showSheet: $showSheet, tappedEntityName: $entityName) .ignoresSafeArea() } .sheet(isPresented: $showSheet) { Text("Tapped Entity: \(entityName)") .padding() } } }
问题2:@Published变量时Sheet显示正常但应用无响应
出现[CAMetalLayer nextDrawable] returning nil错误的核心原因有两个:
- @Published变量更新未在主线程,导致UI队列阻塞
- VideoMaterial的AVPlayer持续占用Metal资源,Sheet弹出时未暂停播放,导致资源耗尽
解决步骤:
- 所有@Published变量的更新必须在主线程执行
- 弹出Sheet时暂停视频播放,关闭时恢复播放
- 确保ObservableObject无循环引用(必要时用weak引用相关对象)
示例代码:
class ARViewModel: ObservableObject { @Published var showSheet = false @Published var tappedEntityName = "Unknown entity tapped" var videoPlayer: AVPlayer? // 暂停/恢复视频,释放Metal资源 func pauseVideo() { videoPlayer?.pause() } func resumeVideo() { videoPlayer?.play() } } struct ARViewContainer: UIViewRepresentable { @ObservedObject var viewModel: ARViewModel func makeUIView(context: Context) -> ARView { let arView = ARView(frame: .zero) // 初始化播放器并绑定到ViewModel let videoURL = Bundle.main.url(forResource: "video", withExtension: "mp4")! let player = AVPlayer(url: videoURL) viewModel.videoPlayer = player let videoMaterial = VideoMaterial(avPlayer: player) let plane = ModelEntity(mesh: .generatePlane(width: 1, height: 0.5), materials: [videoMaterial]) plane.name = "VideoPlane" let tapGesture = UITapGestureRecognizer(target: context.coordinator, action: #selector(context.coordinator.handleTap(_:))) arView.addGestureRecognizer(tapGesture) arView.scene.addAnchor(plane) player.play() return arView } func updateUIView(_ uiView: ARView, context: Context) {} func makeCoordinator() -> Coordinator { Coordinator(viewModel: viewModel) } class Coordinator: NSObject { @ObservedObject var viewModel: ARViewModel init(viewModel: ARViewModel) { self.viewModel = viewModel } @objc func handleTap(_ gesture: UITapGestureRecognizer) { let arView = gesture.view as! ARView let location = gesture.location(in: arView) if let entity = arView.entity(at: location) { DispatchQueue.main.async { self.viewModel.tappedEntityName = entity.name self.viewModel.showSheet = true self.viewModel.pauseVideo() // 弹出Sheet前暂停视频 } print("Tapped entity: \(entity.name)") } } } } // 主视图 struct ContentView: View { @StateObject var viewModel = ARViewModel() var body: some View { ZStack { ARViewContainer(viewModel: viewModel) .ignoresSafeArea() } .sheet(isPresented: $viewModel.showSheet, onDismiss: { viewModel.resumeVideo() // 关闭Sheet后恢复视频 }) { Text("Tapped Entity: \(viewModel.tappedEntityName)") .padding() } } }
额外注意事项
- 检查视频文件编码格式,避免Metal无法高效解码导致资源占用过高
- 避免重复创建实体或AVPlayer,确保ARView生命周期管理正确
- 若仍出现Metal资源错误,可尝试降低视频分辨率或帧率
内容的提问来源于stack exchange,提问作者user1207524
相关产品推荐
相关产品推荐

