2

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.

Andy Jazz
  • 49,178
  • 17
  • 136
  • 220
User_404
  • 91
  • 5
  • 1
    Look at fifth approach in this post - https://stackoverflow.com/questions/56900601/how-to-set-a-known-position-and-orientation-as-a-starting-point-of-arkit/56908516#56908516 – Andy Jazz Oct 10 '20 at 20:49
  • so I just use the write and load functions inside my buttons? Or inside the ARViewContainer structure? – User_404 Oct 10 '20 at 21:19

1 Answers1

2

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)
        }
    }
}
Andy Jazz
  • 49,178
  • 17
  • 136
  • 220
  • Hey Andy, when I move my let arView = ARView(frame: .zero) outside of the makeUIView or updateUIView function (as it is in your code) my Objects are fixed to the camera and not to an anchor in the real world. So when I move my camera, the objects will move too. But I need the arView outside of the function so I can use it for the save and load functions. Or is there another way to use arView inside the load and save functions? – User_404 Oct 14 '20 at 11:50
  • 1
    Hi, I'll answer as soon as I can. – Andy Jazz Oct 14 '20 at 11:53