如何从URL加载RealityKit中的ModelEntity?
从URL加载RealityKit ModelEntity的修复方案
问题分析
原代码核心问题在于直接在主线程用同步方法加载远程URL:RealityKit的ModelEntity.load(contentsOf:)同步方法不适合处理网络资源,会阻塞主线程且极易加载失败;另外冗余的强制类型转换也存在崩溃风险。
修复步骤
- 异步下载远程USDZ模型到本地临时目录
- 从本地临时文件路径加载ModelEntity
- 在主线程安全更新AR场景
- 移除冗余的强制类型转换
- 完善下载与加载全流程的错误处理
修改后的完整代码
import SwiftUI import RealityKit import ARKit import FocusEntity struct ARScreen : View { @StateObject var arScreenVM = ArScreenViewModel() @State private var isPlacementEnabled = false @State private var selectedModel = "sitdown.usdz" @State private var modelConfirmedForPlacement: String? init() { let navBarAppearance = UINavigationBar.appearance() navBarAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.white] navBarAppearance.tintColor = .black } var body: some View { NavigationView { ZStack(alignment: .bottom) { ARViewContainer(modelConfirmedForPlacement: $modelConfirmedForPlacement) PlacementButtonView(isPlacementEnabled: $isPlacementEnabled, selectedModel: selectedModel, modelConfirmedForPlacement: $modelConfirmedForPlacement) } .background(Color("Yellow").ignoresSafeArea()) } .navigationBarTitle("", displayMode: .inline) } struct ARViewContainer: UIViewRepresentable { @Binding var modelConfirmedForPlacement: String? func makeUIView(context: Context) -> ARView { CustomARView(frame: .zero) } func updateUIView(_ uiView: ARView, context: Context) { guard let _ = modelConfirmedForPlacement else { return } print("Debug: 开始加载远程模型") guard let modelURL = URL(string: "https://developer.apple.com/augmented-reality/quick-look/models/vintagerobot2k/robot_walk_idle.usdz") else { print("无效的模型URL") modelConfirmedForPlacement = nil return } // 异步下载模型到临时目录 Task { do { let (tempURL, _) = try await URLSession.shared.download(from: modelURL) // 加载本地临时文件 let modelEntity = try ModelEntity.load(contentsOf: tempURL) // 主线程更新AR场景 await MainActor.run { let anchorEntity = AnchorEntity(plane: .any) anchorEntity.addChild(modelEntity) uiView.scene.addAnchor(anchorEntity) // 播放模型动画(先判断是否有动画) if !modelEntity.availableAnimations.isEmpty { modelEntity.playAnimation(modelEntity.availableAnimations[0].repeat(), transitionDuration: 0.5, startsPaused: false) } modelConfirmedForPlacement = nil } } catch { print("加载模型失败: \(error.localizedDescription)") await MainActor.run { modelConfirmedForPlacement = nil } } } } } class CustomARView: ARView, FEDelegate { let focusSquare = FESquare() required init(frame frameRect: CGRect) { super.init(frame: frameRect) focusSquare.viewDelegate = self focusSquare.delegate = self focusSquare.setAutoUpdate(to: true) setupARView() } func setupARView() { let config = ARWorldTrackingConfiguration() config.planeDetection = [.horizontal, .vertical] config.environmentTexturing = .automatic if ARWorldTrackingConfiguration.supportsSceneReconstruction(.mesh) { config.sceneReconstruction = .mesh } session.run(config) } @MainActor required dynamic init?(coder decoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } } struct PlacementButtonView: View { @Binding var isPlacementEnabled: Bool var selectedModel: String? @Binding var modelConfirmedForPlacement: String? var body: some View { Button(action: { print("Debug: 确认放置模型") modelConfirmedForPlacement = selectedModel }) { Image(systemName: "checkmark") .frame(width: 60, height: 60) .font(.title) .background(Color.white.opacity(0.75)) .cornerRadius(30) .padding(20) } } } // 补充原代码缺失的ViewModel实现 class ArScreenViewModel: ObservableObject {} #if DEBUG struct ContentView_Previews : PreviewProvider { static var previews: some View { ARScreen() } } #endif
关键修改点说明
- 用
URLSession.shared.download(from:)异步下载模型,彻底避免主线程阻塞 - 下载完成后从本地临时URL加载模型,符合RealityKit的资源加载规范
- 通过
Task和await MainActor.run确保AR场景更新操作在主线程执行 - 增加动画存在性判断,防止数组越界崩溃
- 修复导航栏外观设置的冗余代码
- 补充原代码缺失的
ArScreenViewModel实现
内容的提问来源于stack exchange,提问作者Glebzzzik
相关产品推荐
相关产品推荐

