Как сохранить и загрузить ARWorldMap в приложении SwiftUI?

Я следил за учебником YouTube о том, как размещать виртуальные модели в SwiftUI.

Теперь, когда я могу разместить свои модели, я хотел бы сохранить и загрузить положение моделей.

Я уже сделал 2 кнопки для сохранения и загрузки, но я не знаю правильный код для сохранения и загрузки сущностей и якорей.

Следующий код находится внутри моей функции updateUIView:

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)            
    }
}

Я попробовал это с помощью оператора else, так как в начале у меня было if. Но из этого ничего не вышло. Я создал переменную состояния (тип Boolean), которая срабатывает, когда я нажимаю кнопку сохранения или загрузки. но я не могу правильно подключиться к функции updateUIView.

Обновление 1:

Я реализовал функции сохранения и загрузки внутри моей функции updateUIView следующим образом:

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:
        }

    
}

Написав код непосредственно в функции updateUIView, я мог бы использовать uiView вместо arView. Только константа конфигурации должна быть вне функции makrUIView. Логическое значение для загрузки и сохранения установлено в false, но будет истинным, когда я нажму соответствующие кнопки:

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)
                        }
                        
                    }
    }
}

Я пока не получаю сообщений об ошибках, но мой код, похоже, не работает. Я могу нажимать кнопки, но модели и якоря не перезагружаются после перезапуска приложения.


person User_404    schedule 03.09.2020    source источник
comment
Посмотрите на пятый подход в этом посте - stackoverflow.com/questions/56900601/   -  person Andy Fedoroff    schedule 10.10.2020
comment
так что я просто использую функции записи и загрузки внутри своих кнопок? Или внутри структуры ARViewContainer?   -  person User_404    schedule 11.10.2020


Ответы (1)


Ваш код может выглядеть так:

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()
            }
        }
    }
}

Содержание методов saveWorldMap() и loadWorldMap() может быть таким:

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)
        }
    }
}
person Andy Fedoroff    schedule 11.10.2020
comment
Привет, Энди, когда я перемещаю свой let arView = ARView (frame: .zero) за пределы функции makeUIView или updateUIView (как в вашем коде), мои объекты привязаны к камере, а не к привязке в реальном мире. Поэтому, когда я перемещаю камеру, объекты тоже будут двигаться. Но мне нужен arView вне функции, поэтому я могу использовать его для функций сохранения и загрузки. Или есть другой способ использовать arView внутри функций загрузки и сохранения? - person User_404; 14.10.2020
comment
Привет, отвечу, как только смогу. - person Andy Fedoroff; 14.10.2020