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

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错误的核心原因有两个:

  1. @Published变量更新未在主线程,导致UI队列阻塞
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 07:14:58