SwiftUI+SceneKit macOS应用子视图访问ViewModel作用域错误求助
SwiftUI+SceneKit macOS应用:子视图与主视图ViewModel交互的正确实现
问题核心
将主视图的操作按钮移至独立的InterfaceView(作为ZStack覆盖层)后,出现Cannot find 'viewModel' in scope错误,无法操作主视图的ViewModel,核心原因是ViewModel的作用域限制和子视图未接收ViewModel实例。
错误尝试的问题分析
- 使用
ContentView.viewModel前缀:主视图的viewModel是private修饰,无法外部访问,且实例成员不能通过类型调用 - 改为public后:依然无法通过类型调用实例成员,逻辑错误
- 子视图声明
@StateObject var viewModel: ContentViewModel:ContentViewModel定义在ContentView的extension中,子视图无法识别该类型 - 参考方案仍报错:本质还是ViewModel作用域和传递的问题未解决
正确实现步骤
1. 调整ContentViewModel的作用域
将ContentViewModel从ContentView的extension中移出,使其成为全局可访问的类,让所有视图都能识别该类型:
// ContentViewModel.swift import Foundation import SceneKit @MainActor class ContentViewModel: ObservableObject { @Published var scnScene = SCNScene() @Published var sceneView: ScenekitView! @Published var cameraNode = SCNNode() @Published var cameraOrbit: SCNNode! @Published var statusText = ".." @Published var targetNode: SCNNode! // 初始化时创建SceneKit视图实例 init() { self.sceneView = ScenekitView(scene: scnScene) } }
2. 优化ContentView的ViewModel初始化
使用@StateObject替代@ObservedObject(@StateObject更适合持有视图自身创建的ObservableObject实例),并将ViewModel传递给InterfaceView:
// ContentView.swift import SwiftUI import SceneKit struct ContentView: View { @StateObject private var viewModel = ContentViewModel() var body: some View { ZStack { GeometryReader { geometry in viewModel.sceneView .allowsHitTesting(true) // 省略其他自定义代码 } VStack { Spacer() HStack { // 传入ViewModel实例到子视图 InterfaceView(viewModel: viewModel) } .padding() } } .onAppear { viewModel.cameraNode = setupCameraNode(scene: viewModel.scnScene) viewModel.cameraOrbit = setupCameraOrbit(cameraNode: viewModel.cameraNode, scene: viewModel.scnScene) viewModel.scnScene.background.contents = NSColor.black } } // 补全相机节点创建逻辑示例 private func setupCameraNode(scene: SCNScene) -> SCNNode { let node = SCNNode() node.camera = SCNCamera() scene.rootNode.addChildNode(node) return node } private func setupCameraOrbit(cameraNode: SCNNode, scene: SCNScene) -> SCNNode { let orbitNode = SCNNode() orbitNode.addChildNode(cameraNode) scene.rootNode.addChildNode(orbitNode) orbitNode.position = SCNVector3(x: 0, y: 0, z: 10) return orbitNode } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
3. 修改InterfaceView接收ViewModel实例
在InterfaceView中添加@ObservedObject属性接收传入的ViewModel,即可正常访问其属性和方法:
// InterfaceView.swift import SwiftUI import SceneKit struct InterfaceView: View { // 接收主视图传入的ViewModel实例 @ObservedObject var viewModel: ContentViewModel var body: some View { VStack(alignment: .leading, spacing: 5) { HStack { Button("Generate cubes") { addBoxes(scene: viewModel.scnScene) } Button("Random new viewpoint") { viewModel.cameraOrbit.eulerAngles.y += CGFloat.random(in: 0...1000) viewModel.cameraOrbit.eulerAngles.x += CGFloat.random(in: 0...1000) } } } .padding(EdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)) .foregroundColor(Color(.sRGB, red: 0.8, green: 0.8, blue: 0.8, opacity: 0.5)) .background(RoundedRectangle(cornerRadius: 16, style: .circular) .fill(Color.blue)) .padding(EdgeInsets(top: 10, leading: 10, bottom: 0, trailing: 0)) } // 补全创建立方体的逻辑示例 private func addBoxes(scene: SCNScene) { let box = SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0) let boxNode = SCNNode(geometry: box) boxNode.position = SCNVector3( x: Float.random(in: -5...5), y: Float.random(in: -5...5), z: Float.random(in: -5...5) ) scene.rootNode.addChildNode(boxNode) } } struct Interface_Previews: PreviewProvider { static var previews: some View { // 预览时传入ViewModel实例 InterfaceView(viewModel: ContentViewModel()) } }
4. 保持ScenekitView代码不变
原ScenekitView的NSViewRepresentable实现无需修改,确保其能正常绑定场景即可。
内容的提问来源于stack exchange,提问作者user23510612
相关产品推荐
相关产品推荐

