RealityKit: caricamento delle scene di Reality Composer con SwiftUI
Sto cercando di caricare diversi modelli sul viso usando SwiftUI, RealityKit e ARKit.
struct AugmentedRealityView: UIViewRepresentable {
@Binding var modelName: String
func makeUIView(context: Context) -> ARView {
let arView = ARView(frame: .zero)
let configuration = ARFaceTrackingConfiguration()
arView.session.run(configuration, options: [.removeExistingAnchors,
.resetTracking])
loadModel(name: modelName, arView: arView)
return arView
}
func updateUIView(_ uiView: ARView, context: Context) { }
private func loadModel(name: String, arView: ARView) {
var cancellable: AnyCancellable? = nil
cancellable = ModelEntity.loadAsync(named: name).sink(
receiveCompletion: { loadCompletion in
if case let .failure(error) = loadCompletion {
print("Unable to load model: \(error.localizedDescription)")
}
cancellable?.cancel()
},
receiveValue: { model in
let faceAnchor = AnchorEntity(.face)
arView.scene.addAnchor(faceAnchor)
faceAnchor.addChild(model)
model.scale = [1, 1, 1]
})
}
}
È così che li carico, ma quando la vista della telecamera si apre e carica un modello, gli altri modelli non verranno caricati. Qualcuno mi può aiutare?
Risposte
Quando il valore delle tue Binding
modifiche, SwiftUI chiama la tua updateUIView(_:,context:)
implementazione, il che non viene notato.
Inoltre, non stai memorizzando il file AnyCancellable
. Quando il token restituito da sink
viene deallocato, la richiesta verrà annullata. Ciò potrebbe causare errori imprevisti durante il tentativo di caricare modelli più grandi.
Per risolvere entrambi questi problemi, utilizzare un file Coordinator
. import UIKit import RealityKit import SwiftUI import Combina import ARKit
struct AugmentedRealityView: UIViewRepresentable {
class Coordinator {
private var token: AnyCancellable?
private var currentModelName: String?
fileprivate func loadModel(_ name: String, into arView: ARView) {
// Only load model if the name is different from the previous one
guard name != currentModelName else {
return
}
currentModelName = name
// This is optional
// When the token gets overwritten
// the request gets cancelled
// automatically
token?.cancel()
token = ModelEntity.loadAsync(named: name).sink(
receiveCompletion: { loadCompletion in
if case let .failure(error) = loadCompletion {
print("Unable to load model: \(error.localizedDescription)")
}
},
receiveValue: { model in
let faceAnchor = AnchorEntity(.camera)
arView.scene.addAnchor(faceAnchor)
faceAnchor.addChild(model)
model.scale = [1, 1, 1]
})
}
fileprivate func cancelRequest() {
token?.cancel()
}
}
@Binding var modelName: String
func makeCoordinator() -> Coordinator {
Coordinator()
}
static func dismantleUIView(_ uiView: ARView, coordinator: Coordinator) {
coordinator.cancelRequest()
}
func makeUIView(context: Context) -> ARView {
let arView = ARView(frame: .zero)
let configuration = ARFaceTrackingConfiguration()
arView.session.run(configuration, options: [.removeExistingAnchors,
.resetTracking])
context.coordinator.loadModel(modelName, into: arView)
return arView
}
func updateUIView(_ uiView: ARView, context: Context) {
context.coordinator.loadModel(modelName, into: uiView)
}
}
Creiamo una Coordinator
classe nidificata che contiene il AnyCancellable
token e spostiamo la loadModel
funzione nel file Coordinator
. Oltre a SwiftUI View
, Coordinator
è un class
che vive mentre la tua vista è visibile (ricorda sempre che SwiftUI potrebbe crearti e distruggerti View
a piacimento, il suo ciclo di vita non è correlato alla "vista" effettiva che viene mostrata sullo schermo).
In out loadModel
class controlliamo due volte che il valore di our Binding
sia effettivamente cambiato in modo da non annullare una richiesta in corso per lo stesso modello quando SwiftUI aggiorna il nostro View
, ad esempio a causa di un cambiamento nell'ambiente.
Quindi implementiamo la makeCoordinator
funzione per costruire uno dei nostri Coordinator
oggetti. Sia in makeUIView
che in updateUIView
chiamiamo la loadModel
funzione sul nostro Coordinator
.
Il dimantleUIView
metodo è facoltativo. Quando Coordinator
viene decostruito token
, anche il nostro viene rilasciato, il che farà sì che Combine annulli le richieste in corso.