swiftsprite-kitskspritenode

Swift: SKNode `contains(_:)` not always working


I am having trouble with contains(_:) to check if an SKSpriteNode contains a point. Specifically a point from UIPanGestureRecognizer.location(in:).

Drawing two boxes (red and green) on the screen as SKSpriteNodes, another node (colored yellow) is added and will be dragged across the scene using an UIPanGestureRecognizer.

The result of contains(_:) seems to depend on the parent of the node that is being dragged, which I don't expect. It should simply check if a specific CGPoint lies inside of the coordinates of a node.

    var box1: SKSpriteNode?
    var box2: SKSpriteNode?
    var draggingNode: SKSpriteNode?

    box1 = SKSpriteNode(color: .red, size: CGSize(width: 50, height: 50))
    box1?.position = CGPoint(x: 100, y: 100)
    addChild(box1!)

    box2 = SKSpriteNode(color: .green, size: CGSize(width: 50, height: 50))
    box2?.position = CGPoint(x: 200, y: 200)
    addChild(box2!)
        
    draggingNode = SKSpriteNode(color: .yellow, size: CGSize(width: 25, height:25))
    box1!.addChild(draggingNode!)

As seen above, the yellow draggingNode is a child of the red node box1.

Here is the gesture recogniser code that moves the draggingNode and print outs the results of the test:

    @objc func panned(sender: UIPanGestureRecognizer) {
        let pointInView: CGPoint = sender.location(in: view)
        let pointInScene = convertPoint(fromView: pointInView)

        if sender.state == .changed {
            let translation = sender.translation(in: view)
            draggingNode?.position.x += translation.x
            draggingNode?.position.y -= translation.y
            sender.setTranslation(.zero, in: view)

            print("is in box1: ", box1!.contains(pointInScene))
            print("is in box2: ", box2!.contains(pointInScene))
        }

For box2, of which the draggingNode is not a child, the results are correct.

Question: Why is the parent of draggingNode influencing the result of contains(_:), and how to fix it?


Solution

  • the reason is that if you add a child node to a parent they effectively create a two node system. and this affects the frame size. if you look at box1?.calculateAccumulatedFrame() you will see that box1's frame expands to include wherever the yellow draggingNode position is.

    you are getting strange results because yellow is effectively doing a hit test against a rectangle that it's own position is helping to define.

    solution: add both box1 and draggingNode to a third parent node (let's call it "container"). then box1's accumulated frame will not vary during the pan gesture, and your hit tests will be accurate.