Abbiamo a che fare con due sistemi differenti qui che non necessariamente comunicare: il motore di rendering SceneKit
e il sistema di disegno UIKit
/CoreGraphics
che sta cercando di catturare lo screenshot. Quando si nasconde uno SCNNode
, non viene immediatamente invalidata la visualizzazione per ridisegnare come nascondere una sottoview UIView
, pertanto l'impostazione di afterScreenUpdates
a true
non ha alcun effetto in questo momento. Dopo che il nodo è stato contrassegnato come nascosto, dopo che il ciclo di rendering SceneKit
è stato completato una volta e dopo che la vista è pronta per essere ridisegnata sullo schermo, possiamo quindi catturare lo screenshot. Possiamo ascoltare gli eventi nel ciclo di rendering creando uno SCNSceneRendererDelegate
per lo SCNView
(SCNSceneRendererDelegate). Possiamo implementare il metodo delegato renderer:didRenderScene:atTime:
.
Assegnare un delegato alla vista scena (scnView.delegate = self
). Quando si vuole fare la tua cattura dello schermo, nascondere il nodo e impostare un flag booleano che è nella stessa portata del delegato al true
:
Node1.hidden = true
screenCaptureFlag = true // screenCaptureFlag is a controller property
Allora ti implementare il delegato:
func renderer(renderer: SCNSceneRenderer, didRenderScene scene: SCNScene, atTime time: NSTimeInterval) {
if screenCaptureFlag {
screenCaptureFlag = false // unflag
let scnView = self.view as! SCNView
// Dispatch asynchronously to main queue
// Will run once SCNView is ready to be redrawn on screen
// Also avoids SceneKit rendering freeze
dispatch_async(dispatch_get_main_queue()) {
// Get your screenshot the simple way
let screenshot = scnView.snapshot()
// Or use your function
// let screenshot = self.turnViewToImage(scnView, opaque: false, afterUpdates: true)
// Then save the screenshot, or do whatever you want
let urlPath = NSURL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first!).URLByAppendingPathComponent("Image.png")
try! UIImagePNGRepresentation(screenshot)!.writeToURL(urlPath, options: .AtomicWrite)
}
}
}
Questo metodo delegato non è perfetto per noi perché anche se la scena è renderizzata, non è ancora pronta per ridisegnare la visualizzazione sullo schermo perché questo metodo delegato ci offre la possibilità di eseguire qualsiasi rendering personalizzato che vogliamo. Inoltre, se provi a chiamare drawViewHierarchyInRect:afterScreenUpdates:
o snapshot
qui, il rendering di SceneKit si interromperà completamente (sia nel simulatore che sul dispositivo per iOS 9.3), che potrebbe essere un bug di SceneKit. Non abbiamo un'opzione migliore per i metodi delegate, ma la mia soluzione è di inviare una chiamata asincrona sulla coda principale che verrà eseguita dopo che il ciclo di rendering di SceneKit è stato completato e l'immagine renderizzata è pronta per essere ridisegnata sullo schermo. Se si utilizza drawViewHierarchyInRect:afterScreenUpdates:
, assicurarsi che afterScreenUpdates
sia impostato su true
perché sembra che a questo punto il disegno effettivo non sia ancora stato ancora eseguito. E come sono sicuro di sapere, assicurati che la chiamata asincrona funzioni sul thread principale, perché gli oggetti UIKit
non devono mai essere toccati da un thread diverso.
A proposito, ho replicato il problema e ho trovato la mia soluzione utilizzando il modello di progetto SceneKit predefinito fornito da Xcode 7.3 (quello con la nave che gira). Attivo/disattiva la proprietà nascosta della nave ogni volta che viene toccata la vista scena e quindi imposta la bandiera per catturare uno screenshot. Possiamo usare questo modello come base per ulteriori discussioni, se necessario.
ok grazie mille. l'altra opzione che verrà esplorata sta introducendo una certa latenza (ad es. 100 ms) prima di catturare lo screenshot. inelegante ma potrebbe funzionare poiché il flusso utente contiene già latenza tramite una chiamata di rete. – Crashalot
btw hai riscontrato un problema simile a questo prima? http: // StackOverflow.it/questions/36728704/delay-when-using-instantiateviewcontrollerwithidentifier-but-not-performseguewit – Crashalot
sperava che qualcun altro postasse una soluzione che non richiedesse una variabile flag, ma almeno questo funziona. Grazie per l'aiuto! – Crashalot