Search code examples
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))
        }
  • The expected result is that box1!.contains(pointInScene) only returns true if the draggingNode is actually above box1.
  • The actual result is that box1!.contains(pointInScene) always returns true, no matter if the draggingNode is dragged above box1 or not.

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

  • If I add the draggingNode as a child of box2, the results are reversed: box2!.contains(pointInScene) now always returns true and box1!.contains(pointInScene) works correctly.
  • If I add the draggingNode as a child of the SKScene itself, everything works as expected.

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.