问题
I followed a YouTube tutorial on how to place virtual Models in SwiftUI.
Now that I can place my Models, I would like to save and load the models position.
I have already made 2 Buttons for save and load, but I don't know the correct code to save and load the Entities and Anchors.
The following code is inside my updateUIView function:
func updateUIView(_ uiView: ARView, context: Context) {
if let name = self.modelName {
print("Modell mit dem Namen \(name) wurde zum plaziert")
let filename = name + ".usdz"
let modelEntity = try! ModelEntity.loadModel(named: filename)
modelEntity.generateCollisionShapes(recursive: true)
uiView.installGestures(.all, for: modelEntity)
let anchorEntity = AnchorEntity(plane: .any)
anchorEntity.addChild(modelEntity)
uiView.scene.addAnchor(anchorEntity)
}
}
I tried it with an "else"-statement, since I had the "if" in the beginning. But this did not work out. I made a state variable (type Boolean), which triggers when I press the save or load button. but I can't get the connection to the updateUIView-function right.
Update 1:
I implemented the save and load functions inside my "updateUIView" function like this:
struct ARViewContainer: UIViewRepresentable {
@Binding var reset: Bool
@Binding var modelSelector: String
@Binding var save: Bool
@Binding var load: Bool
let config = ARWorldTrackingConfiguration()
func makeUIView(context: Context) -> ARView {
// create config for all entities
let arView = ARView(frame: .zero)
config.planeDetection = [.horizontal, .vertical]
config.environmentTexturing = .automatic
config.sceneReconstruction = .mesh
arView.session.run(config)
return arView
}
// App kontinuierlich beobachten
func updateUIView(_ uiView: ARView, context: Context) {
// create anchor and model-Entity
if modelSelector != "default" {
let modelEntity = try! ModelEntity.loadModel(named: "\(modelSelector)")
modelEntity.name = ("\(modelSelector)")
modelEntity.generateCollisionShapes(recursive: true)
uiView.installGestures(.all, for: modelEntity)
let anchor = AnchorEntity(plane: .any)
anchor.addChild(modelEntity)
uiView.scene.addAnchor(anchor)
}
// reset anchor
if reset == true {
uiView.scene.anchors.removeAll()
}
//MARK: saveload
if save == true {
uiView.session.getCurrentWorldMap { (worldMap, _) in
if let map: ARWorldMap = worldMap {
let data = try! NSKeyedArchiver.archivedData(withRootObject: map,
requiringSecureCoding: true)
let savedMap = UserDefaults.standard
savedMap.set(data, forKey: "WorldMap")
savedMap.synchronize()
}
}
}
if load == true {
let storedData = UserDefaults.standard
if let data = storedData.data(forKey: "WorldMap") {
if let unarchiver = try? NSKeyedUnarchiver.unarchivedObject(
ofClasses: [ARWorldMap.classForKeyedUnarchiver()],
from: data),
let worldMap = unarchiver as? ARWorldMap {
config.initialWorldMap = worldMap
uiView.session.run(config)
}
}
}
//MARK:
}
}
By writing the code directly into the updateUIView function I could use the uiView instead of the arView. Only the config constant had to be outside of the makrUIView function. The Boolean for load and save are set to false but will be true, when I press the corresponding buttons:
struct SaveLoadReset: View {
@Binding var reset: Bool
@Binding var save: Bool
@Binding var load: Bool
@Binding var modelSelector: String
var body: some View {
//HStack für den Laden und Speichern Knopf
HStack(spacing: 10){
//save-Button:
Button(action: {
print("DEBUG: saveButton")
self.modelSelector = "default"
self.save = true
print("DEBUG: save = \(self.save)")
})
{
Image(systemName: "square.and.arrow.up")
.padding(20)
.background(Color.white)
.opacity(0.3)
.cornerRadius(20)
.padding(10)
.font(.title)
}
//load-Button:
Button(action: {
print("DEBUG: loadButton")
self.load = true
print("DEBUG: load = \(self.load)")
})
{
Image(systemName: "square.and.arrow.down")
.padding(20)
.background(Color.white)
.opacity(0.3)
.cornerRadius(20)
.font(.title)
}
Spacer()
//reset-Button:
Button(action: {
print("DEBUG: removeButton")
self.reset = true
print("DEBUG: reset = \(self.reset)" )
})
{
Image(systemName: "arrow.clockwise.circle")
.padding(20)
.background(Color.white)
.opacity(0.3)
.cornerRadius(20)
.font(.title)
.padding(10)
}
}
}
}
I don't get any error messages so far, but my code does not seem to work. I can press the buttons, but the models and anchors do not get reloaded after restarting the app.
回答1:
Your code might look like this:
import RealityKit
import SwiftUI
import ARKit
struct ARViewContainer: UIViewRepresentable {
@Binding var saved: Bool
@Binding var loaded: Bool
let anchor = AnchorEntity(world: [0, 0,-2])
let arView = ARView(frame: .zero)
let config = ARWorldTrackingConfiguration()
func makeUIView(context: Context) -> ARView {
let sphere = MeshResource.generateSphere(radius: 1)
let sphereEntity = ModelEntity(mesh: sphere)
sphereEntity.name = "SPHERE"
sphereEntity.setParent(anchor)
arView.scene.anchors.append(anchor)
return arView
}
fileprivate func saveWorldMap() {
print("SAVED")
DispatchQueue.main.async {
saved = false
}
}
fileprivate func loadWorldMap() {
print("LOADED")
DispatchQueue.main.async {
loaded = false
}
}
func updateUIView(_ uiView: ARView, context: Context) {
// Retrieve the entity from your scene
guard let entity = uiView.scene.findEntity(named: "SPHERE")
else { return }
if saved {
self.saveWorldMap()
}
if loaded {
self.loadWorldMap()
}
}
}
struct ContentView : View {
@State private var saver = false
@State private var loader = false
var body: some View {
VStack {
ARViewContainer(saved: $saver, loaded: $loader)
HStack {
Spacer()
Button(action: { self.saver.toggle() }) {
Text("Save")
}
Spacer()
Button(action: { self.loader.toggle() }) {
Text("Load")
}
Spacer()
}
}
}
}
The content of saveWorldMap()
and loadWorldMap()
methods might be like that:
fileprivate func saveWorldMap() {
arView.session.getCurrentWorldMap { (worldMap, _) in
if let map: ARWorldMap = worldMap {
let data = try! NSKeyedArchiver.archivedData(withRootObject: map,
requiringSecureCoding: true)
let savedMap = UserDefaults.standard
savedMap.set(data, forKey: "WorldMap")
savedMap.synchronize()
}
}
}
fileprivate func loadWorldMap() {
let storedData = UserDefaults.standard
if let data = storedData.data(forKey: "WorldMap") {
if let unarchiver = try? NSKeyedUnarchiver.unarchivedObject(
ofClasses: [ARWorldMap.classForKeyedUnarchiver()],
from: data),
let worldMap = unarchiver as? ARWorldMap {
config.initialWorldMap = worldMap
arView.session.run(config)
}
}
}
来源:https://stackoverflow.com/questions/63730554/how-do-i-save-and-load-arworldmap-in-swiftui-app