Heriotza Heriotza - 4 months ago 63
Swift Question

SceneKit: slow loading of UIView after contact between two nodes

I just found a really odd behavior with Scene Kit. I created a clean project to see if this would happen, and it did. Consider the following

GameViewController
class:

import UIKit
import QuartzCore
import SceneKit

class GameViewController: UIViewController, SCNPhysicsContactDelegate {

func physicsWorld(world: SCNPhysicsWorld, didBeginContact contact: SCNPhysicsContact) {
let label = UILabel(frame: CGRectMake(0, 0, 100, 100))
label.textColor = .whiteColor()
label.text = "Test"
self.view.addSubview(label)
}

override func viewDidLoad() {

// Controller
super.viewDidLoad()

// Scene
let scene = SCNScene()
scene.physicsWorld.contactDelegate = self

// Scene View
let scnView = self.view as! SCNView
scnView.scene = scene

// Camera
let camera = SCNCamera()
camera.usesOrthographicProjection = true
camera.orthographicScale = 8

// Camera Node
let cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: -3, y: 7.5, z: 10)
cameraNode.eulerAngles = SCNVector3(x: -0.5, y: -0.5, z: 0)
scene.rootNode.addChildNode(cameraNode)

// Light
let light = SCNLight()
light.type = SCNLightTypeDirectional

// Light Node
let lightNode = SCNNode()
lightNode.light = light
lightNode.eulerAngles = SCNVector3Make(-1, -0.5, 0)
scene.rootNode.addChildNode(lightNode)

// Box Shape
let geometry = SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0)
geometry.materials.first?.diffuse.contents = UIColor(red: 255, green: 255, blue: 0, alpha: 1)

// Upper Box
let box = SCNNode(geometry: geometry)
box.physicsBody = SCNPhysicsBody(type: .Dynamic, shape: nil)
box.physicsBody?.categoryBitMask = 1
box.physicsBody?.contactTestBitMask = 2
scene.rootNode.addChildNode(box)

// Bottom Box
let box2 = SCNNode(geometry: geometry)
box2.position.y -= 5
box2.physicsBody = SCNPhysicsBody(type: .Static, shape: nil)
box2.physicsBody?.categoryBitMask = 2
box2.physicsBody?.contactTestBitMask = 1
box2.physicsBody?.affectedByGravity = false
scene.rootNode.addChildNode(box2)

}
}


What happens here is, we create a
SCNScene
, then we add the camera/lightning and create two boxes that will collide. We also set the controller as a delegate for the physics contact delegate.

But the important code is at the top, when the contact between the two objects occurs. We create an
UILabel
and position it at the top left of the screen. But here is the problem: It takes around seven seconds for the label to display. And it seems to always be this time. This seems rather odd, but you can try it yourself: just create a SceneKit project (using Swift), and replace the controller code with the one I provided.

Additional note: if you add
print("a")
in the
physicsWorld()
function, it will run immediately, so the code is being run at the right time, but for some reason the
UILabel
isn't displaying right away.

Answer

At first I thought your scene wasn't being rendered after the contact; hence not displaying the label, so put a scnView.playing = true line in. However, this resulted in the label never being displayed.

Next thought was that you're trying to do something on a thread you shouldn't be. Modifying UIKit views should really only be done on the main thread, and it's not clear what thread calls the SCNPhysicsContactDelegate functions. Given it's relatively easy to ensure it does run on the main thread...

func physicsWorld(world: SCNPhysicsWorld, didBeginContact contact: SCNPhysicsContact) {
    dispatch_async(dispatch_get_main_queue()) {
        let label = UILabel(frame: CGRectMake(0, 0, 100, 100))
        label.textColor = .whiteColor()
        label.text = "Test"
        self.view.addSubview(label)
    }
}

This fixes the delay for me, and also works with scnView.playing = true.