J'ai pensé que je pourrais vouloir utiliser UIKit pour des objets virtuels sur mon smartphone ou ma vitre AR, j'ai donc essayé d'afficher UIView dans la scène de SceneKit.
Puisqu'il est facile de vérifier l'opération, utilisez WKWebView
comme UIView.
Image complète
Informations de référence: ・ Tweet d'opération dans cette interface utilisateur Swift
① Capturez ʻUIViewet convertissez-le en
MTLTexutre ② ① est défini comme le matériau de
SCNNode` de SceneKit
seulement ça. Cela fonctionne comme ça, mais voici quelques addictions.
Pour faire défiler ou taper dans une WebView qui est un objet virtuel, il est nécessaire de passer un événement tactile à n'importe quelle WebView avec ʻARSCNView devant. Ici, j'ai trouvé une méthode pour recevoir des événements ʻUIResponder
tels que touchesBegan ()
avec ʻARSCNView et les envoyer à
touchesBegan () ʻetc. De n'importe quel WebView, mais le défilement et le tapotement ne fonctionnent pas du tout. .. En premier lieu, est-il possible d'effectuer des actions spécifiques aux parties de l'interface utilisateur telles que le défilement ou le tapotement de bouton lorsque UIEvent est passé? Je me sens comme ça. Manque de connaissances ici. .. .. Si quelqu'un sait, faites-le moi savoir.
Si vous recherchez sur Google comment faire défiler le programme, vous pouvez voir comment le définir avec contentOffset
de ʻUIScrollView`, mais cela ne devrait pas permettre un défilement inertiel confortable.
Donc, à la fin, je n'ai pas trouvé de bonne méthode et je l'ai réalisée par la méthode suivante.
・ Placez WebView dans la même position avec la même taille que ʻARSCNView -Laisser ʻARSCNView
devant et définir ʻisUserInteractionEnabled sur false -Effectuez un test de succès et définissez ʻisUserInteractionEnabled
de WebView au centre de l'écran sur true.
Cela vous permet de faire défiler n'importe quelle WebView. Cependant, cette méthode présente les inconvénients suivants.
-Lors du défilement, WebView ne peut être réglé sur aucun format d'image. Il doit s'adapter à la taille de l'écran. -Puisque la position tapée ne peut pas être corrigée à la position de WebView dans la scène, le traitement d'événement tel que le clic de bouton ne peut pas être effectué (inutile).
De ce qui précède, il semble que les cas où cette méthode peut être utilisée sont limités aux cas où certaines informations sont affichées uniquement et l'opération est un défilement uniquement dans l'objet virtuel à l'écran. Je vais essayer à nouveau comment je peux le faire avec Swift UI comme le tweet ci-dessus.
La synchronisation de la capture est définie sur renderer (_: updateAtTime:)
de SCNSceneRendererDelegate
, mais comme il est nécessaire de passer l'écran à Capture & SceneKit dans le thread principal, le processus de capture est mis en file d'attente dans la file d'attente principale dans cette méthode. .. Si la capture d'écran est assez rapide, cela peut suffire, mais comme il s'agit d'un processus difficile de capture lors du rendu 3D lors du suivi avec AR, la file d'attente principale se développe rapidement et l'écran se fige. J'ai rencontré le phénomène. Mesures pour empêcher la capture suivante jusqu'à ce que la capture soit terminée.
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
//Ne pas capturer si la capture d'écran dans la file d'attente principale n'est pas terminée
//Le processus de capture est lent, donc sans lui, la file d'attente ne se bloquera pas.
guard !isCaptureWaiting else { return }
isCaptureWaiting = true
class ViewController: UIViewController, ARSCNViewDelegate {
@IBOutlet weak var scnView: ARSCNView!
@IBOutlet weak var webView1: WKWebView!
@IBOutlet weak var webView2: WKWebView!
@IBOutlet weak var webView3: WKWebView!
private var device = MTLCreateSystemDefaultDevice()!
private var planeNode1: SCNNode!
private var planeNode2: SCNNode!
private var planeNode3: SCNNode!
private var node1Texture: MTLTexture?
private var node2Texture: MTLTexture?
private var node3Texture: MTLTexture?
private var viewWidth: Int = 0
private var viewHeight: Int = 0
private var isCaptureWaiting = false
override func viewDidLoad() {
super.viewDidLoad()
scnView.scene = SCNScene(named: "art.scnassets/sample.scn")!
planeNode1 = scnView.scene.rootNode.childNode(withName: "plane1", recursively: true)
planeNode2 = scnView.scene.rootNode.childNode(withName: "plane2", recursively: true)
planeNode3 = scnView.scene.rootNode.childNode(withName: "plane3", recursively: true)
//N'acceptez pas les événements de l'interface utilisateur une seule fois
scnView.isUserInteractionEnabled = false
setUIEnable(webView1: false, webView2: false, webView3: false)
//ARSCN View est toujours au top
self.view.bringSubviewToFront(scnView)
//Session AR démarrée
self.scnView.delegate = self
let configuration = ARWorldTrackingConfiguration()
self.scnView.session.run(configuration, options: [.removeExistingAnchors, .resetTracking])
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if node1Texture == nil || node2Texture == nil || node3Texture == nil {
viewWidth = Int(view.bounds.width)
viewHeight = Int(view.bounds.height)
//Tampon de texture sécurisé
//Taille WebView= self.La taille de la vue
let desc = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .rgba8Unorm,
width: viewWidth,
height: viewHeight,
mipmapped: false)
node1Texture = device.makeTexture(descriptor: desc)!
node2Texture = device.makeTexture(descriptor: desc)!
node3Texture = device.makeTexture(descriptor: desc)!
//Charge du site
webView1.load(URLRequest(url: URL(string:"https://qiita.com")!))
webView2.load(URLRequest(url: URL(string:"https://www.apple.com")!))
webView3.load(URLRequest(url: URL(string:"https://stackoverflow.com")!))
}
}
//Paramètre IsUserInteractionEnabled pour chaque WebView
func setUIEnable(webView1: Bool, webView2: Bool, webView3: Bool) {
self.webView1.isUserInteractionEnabled = webView1
self.webView2.isUserInteractionEnabled = webView2
self.webView3.isUserInteractionEnabled = webView3
}
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
guard let _node1Texture = node1Texture, let _node2Texture = node2Texture, let _node3Texture = node3Texture else { return }
//Ne pas capturer si la capture d'écran dans la file d'attente principale n'est pas terminée
//Le processus de capture est lent, donc sans lui, la file d'attente ne se bloquera pas.
guard !isCaptureWaiting else { return }
isCaptureWaiting = true
DispatchQueue.main.async {
//Effectuer un hit test sur le nœud et WebView au centre de l'écran(nœud)Trouver
let bounds = self.scnView.bounds
let screenCenter = CGPoint(x: bounds.midX, y: bounds.midY)
let options: [SCNHitTestOption: Any] = [
.boundingBoxOnly: true, //Testé avec boundingBox
.firstFoundOnly: true //Renvoie uniquement l'objet le plus en avant
]
if let hitResult = self.scnView.hitTest(screenCenter, options: options).first,
let nodeName = hitResult.node.name {
//Définissez isUserInteractionEnabled de la WebView correspondant au nœud au centre de l'écran sur true
switch nodeName {
case "plane1":
self.setUIEnable(webView1: true, webView2: false, webView3: false)
case "plane2":
self.setUIEnable(webView1: false, webView2: true, webView3: false)
case "plane3":
self.setUIEnable(webView1: false, webView2: false, webView3: true)
default:
self.setUIEnable(webView1: false, webView2: false, webView3: false)
}
} else {
self.setUIEnable(webView1: false, webView2: false, webView3: false)
}
//Mettre à jour le matériau du nœud en capturant uniquement la WebView correspondant au nœud au centre de l'écran
let setNodeMaterial: (UIView, SCNNode, MTLTexture) -> () = { captureView, node, texture in
let material = SCNMaterial()
material.diffuse.contents = captureView.takeTextureSnapshot(device: self.device,
textureWidth: self.viewWidth,
textureHeight: self.viewHeight,
textureBuffer: texture)
node.geometry?.materials = [material]
}
if self.webView1.isUserInteractionEnabled {
setNodeMaterial(self.webView1, self.planeNode1, _node1Texture)
} else if self.webView2.isUserInteractionEnabled {
setNodeMaterial(self.webView2, self.planeNode2, _node2Texture)
} else if self.webView3.isUserInteractionEnabled {
setNodeMaterial(self.webView3, self.planeNode3, _node3Texture)
}
self.isCaptureWaiting = false
}
}
}
extension UIView {
//Prenez une capture d'écran de n'importe quel UIView et convertissez-le en MTLTexture
//URL de référence: https://stackoverflow.com/questions/61724043/render-uiview-contents-into-mtltexture
func takeTextureSnapshot(device: MTLDevice, textureWidth: Int, textureHeight: Int, textureBuffer: MTLTexture) -> MTLTexture? {
guard let context = CGContext(data: nil,
width: textureWidth,
height: textureHeight,
bitsPerComponent: 8,
bytesPerRow: 0,
space: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else { return nil }
//Éviter la tête en bas
context.translateBy(x: 0, y: CGFloat(textureHeight))
context.scaleBy(x: 1, y: -1)
guard let data = context.data else { return nil }
layer.render(in: context)
textureBuffer.replace(region: MTLRegionMake2D(0, 0, textureWidth, textureHeight),
mipmapLevel: 0,
withBytes: data,
bytesPerRow: context.bytesPerRow)
return textureBuffer
}
}
Recommended Posts