2016-04-28 16 views
8

Il codice seguente cattura uno screenshot di SCNView1, ma non funziona.Cattura immagine/schermata di SCNView con SCNNode nascosto: drawViewHierarchyInRect afterScreenUpdate non funziona

SCNView1 contiene Nodo1. L'obiettivo è acquisire SCNView1 senza Node1.

Tuttavia, l'impostazione di afterScreenUpdates su true (o false) non aiuta: lo screenshot contiene Node1 indipendentemente da cosa.

Qual è il problema?

Node1.hidden = true 
let screenshot = turnViewToImage(SCNView1, opaque: false, afterUpdates: true) 
// Save screenshot to disk 

func turnViewToImage(targetView: UIView, opaque: Bool, afterUpdates: Bool = false) -> UIImage { 
    UIGraphicsBeginImageContextWithOptions(targetView.bounds.size, opaque, UIScreen.mainScreen().scale) 
    let context = UIGraphicsGetCurrentContext() 
    CGContextSetInterpolationQuality(context, CGInterpolationQuality.High) 
    targetView.drawViewHierarchyInRect(targetView.bounds, afterScreenUpdates: afterUpdates) 
    let image = UIGraphicsGetImageFromCurrentImageContext() 
    UIGraphicsEndImageContext() 
    return image 
} 

risposta

9

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.

+0

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

+0

btw hai riscontrato un problema simile a questo prima? http: // StackOverflow.it/questions/36728704/delay-when-using-instantiateviewcontrollerwithidentifier-but-not-performseguewit – Crashalot

+0

sperava che qualcun altro postasse una soluzione che non richiedesse una variabile flag, ma almeno questo funziona. Grazie per l'aiuto! – Crashalot

Problemi correlati