I have somehow lost the ability to receive touch notifications

Two days ago I was able to receive touchesBegan (moved, canceled) and now I've lost them. So I shortened the code below, created an SKSpriteNode called redSprite within GameScene...and still I don't receive touches.

While I know my redSprite doesn't fulfill all the if's in the touch functions, I'm placing a breakpoint at the start of the touch functions, and it still won't stop at the breakpoint.

However...if I set GameScene's isUserInteractionEnabled to true, then I do get the touches and the debugger breaks.

Code Block
import UIKit
import SpriteKit
var redSprite : SKSpriteNode?
class GameScene: SKScene, SKPhysicsContactDelegate {
override func sceneDidLoad(){
super.sceneDidLoad()
#if DEBUG
print ("GC sceneDidLoad")
#endif
}
override func didMove(to view: SKView){
super.didMove(to: view)
#if DEBUG
print ("GC didMove START")
#endif
self.isHidden = true
self.backgroundColor = mySafeColor
myGlobalVars.backGround = SKSpriteNode(imageNamed: "background")
myGlobalVars.backGround!.zPosition = theZ.backGround
myGlobalVars.safeSceneRect = view.frame
myGlobalVars.gameScene = self
self.isHidden = false
self.isUserInteractionEnabled = false
self.addChild(myGlobalVars.backGround!)
physicsWorld.contactDelegate = self
redSprite = SKSpriteNode(color: .red, size: CGSize(width: 100, height: 100))
redSprite?.anchorPoint = CGPoint(x: 0.5, y:0.5)
redSprite?.isHidden = false
redSprite?.isUserInteractionEnabled = true
redSprite?.color = .orange
redSprite!.name = "test"
redSprite?.zPosition = 10
redSprite?.position = CGPoint(x: (myGlobalVars.safeSceneRect.width/2)-(50), y: myGlobalVars.safeSceneRect.height/2 - 50)
redSprite?.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 100, height: 100), center: CGPoint(x: 0.5, y: 0.5))
redSprite?.physicsBody!.affectedByGravity = false
redSprite?.physicsBody!.restitution = 0.2
redSprite?.physicsBody?.categoryBitMask = bodyMasks.blankMask.rawValue
redSprite?.physicsBody?.contactTestBitMask = bodyMasks.blankMask.rawValue
redSprite?.physicsBody?.collisionBitMask = bodyMasks.blankMask.rawValue
self.addChild(redSprite!)
gameTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(runTimedCode), userInfo: nil, repeats: true)
gameTimer?.tolerance = 0.2
RunLoop.current.add(gameTimer!, forMode: .common)
#if DEBUG
print ("GC didMove END")
#endif
}
override func touchesBegan(_ touches: Set<UITouch>,
with event: UIEvent?){
guard let touch = touches.first else { return }
let location = touch.location(in: self)
let touchedNodes = self.nodes(at: location)
for node in touchedNodes{
if let theNode = node as? MyGem {
if theNode.nodeType == NodeType.gem, theNode.isMoving == false {
theNode.isMoving = true
theNode.zPosition += 1
myGlobalVars.currentGem = theNode
}
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?){
guard touches.first != nil else { return }
if let touch = touches.first, let node = myGlobalVars.currentGem, node.isMoving == true {
let touchLocation = touch.location(in: self)
node.position = touchLocation
node.isMoving = true
node.inSlot = false
//addTrailToTwinkle(theNode: node)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard touches.first != nil else { return }
if let _ = touches.first, let node = myGlobalVars.currentGem, node.isMoving == true {
returnHome(moveThis : node, origNP: node.origPoint)
}
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
guard touches.first != nil else { return }
if let _ = touches.first, let node = myGlobalVars.currentGem, node.isMoving == true {
returnHome(moveThis : node, origNP: node.origPoint)
}
}
@objc func runTimedCode() {
timeLeft -= 1
}
}

Answered by Claude31 in 645859022
When you set to false, you cascade to subviews as well, whatever you set them.

See discussion here:
https://stackoverflow.com/questions/5887305/uiview-user-interaction-enabled-false-on-parent-but-true-on-child


userInteractionEnabled set to NO on a parent view will cascade down to all subviews. If you need some subviews to have interaction enabled, but not others, you can separate your subviews into two parent views: one with userInteractionEnabled = YES and the other NO. 


What did you change 2 days ago, the issue most likely is there.

Have you followed instructions of doc about touchesBegan:

When creating your own subclasses, call super to forward any events that you do not handle yourself.

You should also add a print before line 65 to check function is called.

Code Block
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
print("Touches began")
guard let touch = touches.first else { return }
print("Touches go on")


Do you really want to
Code Block
self.addChild(redSprite!)

at each move ?

When creating your own subclasses, call super to forward any events that you do not handle yourself
I do

Do you really want to
> self.addChild(redSprite!)
at each move ?

This is just a quick reduction in my code to post. Hoping someone could spot something

I guess I'm just going to have to find the last version that worked, and slowly copy over all my mods.
I do not see any answer to my questions or suggestions.
  • Do you call super in each function ?

  • Did you try to add the print statements ?

  • Why do you create a new child each time you move ?


If you don't care about those suggestions, please tell, I'll abstain to provide more.

have a good day.

Do you call super in each function ?

I said I do, if you lookout the post.


Did you try to add the print statements ?

While the breakpoints showed me when I would or would not receive touch statements, I took your advice and added print statements (easier)

Why do you create a new child each time you move ?

I answered that as well..... the code you're looking at is a reduction of my actual code, so that I could post it. So there will be slight errors such as that. If I didn't reduce the code...I would have to post a bunch of separate files. So I reduced it into just GameScene so the error still existed

What I did just try, was to take my lastest code, the one not working, and I copied over a touchesBegan, with print statements, to redSprite.swift. Lo and behold, it is the one receiving the touches signals.

I've checked a million times...only GameScene has:

Code Block
SKPhysicsContactDelegate
physicsWorld.contactDelegate = self


I am making sure I call super.init on the redSprite class
and it is being added as a child to GameScene.



What I broke, I have no clue
OK, I did not see "I do" was the answer, looked at it as part of the comment. So, you added a super.touchesBegan in touchesBegan.

Then do you get the prints in touchesBegan ?

You say:

and I copied over a touchesBegan, with print statements, to redSprite.swift.

You did not post this code, exact ?

Sorry, I'm lost because you seem to refer to code you don't show, with versions that have changed… So extremely hard to focus on searching the cause of the problem.

I read again your initial post:

However...if I set GameScene's isUserInteractionEnabled to true, then I do get the touches and the debugger breaks.

So, why don't you set userInteraction to true ?!? It is needed if you want to receive the events.
I must be missing something in your post.

Then do you get the prints in touchesBegan ?

Nope, although if put the touchesBegan in the class I am touching, it gets triggered.
Even though only GameScene has SKPhysicsContactDelegate and physicsWorld.contactDelegate = self

So, why don't you set userInteraction to true ?!? 

I do, what I meant by reduced code was so it could be posted, so I was due to delete something I shouldn't have. But just in the post.

OK, I did not see "I do" was the answer, looked at it as part of the comment. So, you added a super.touchesBegan in touchesBegan.

My turn to say, I didn't catch that. I thought you meant super. in the functions in the class. So now I know to put them in the touches events. Didn't fix it, but thank you for letting me know it needs to be done.

Sorry if my reply seemed snippy. It's just that I got my app to a certain point, while learning Swift, and everything worked. So I stopped at a certain point and decided to clean up the code, add comments, make it clean, etc. etc. And I'm so ****** at myself for not testing after each cleanup that I did, and my backups of the clean code doesn't contain a working version.

So I have no other option but to start the cleanup again. I'm going to start all over, and make sure test every 5 minutes.....argh


So I have no other option but to start the cleanup again. I'm going to start all over, and make sure test every 5 minutes.....argh 

Yes, debugging may be a painful process…

In such a case, what I do is write some code, test it works, close the project, save a copy of the complete project, with a clear indication of the date, as "MyProject - save 2020.11.11 8am", then reopen the working project and continue.
As soon as project crashes, I compare with the last working code. And find out the difference that may cause the crash.

I also insert a lot of prints, with function name, at several places in critical code, to see exactly what happens:
Code Block
print(#function, "Here the value of this is ", thisValue)

Note: You could try to use git to do this, but that may be overkill.
Ok, have it down to three files, and am not receiving touchesBegan for the red ball. I'd rather it be something stupid I did and be scolded for that

GVC
Code Block
import SpriteKit
import SwiftUI
class GameViewController: UIViewController {
public let myView : SKView = {
let myView = SKView()
myView.translatesAutoresizingMaskIntoConstraints = false
return myView
}()
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if myGlobalVars.safeSceneRect == .zero {
myGlobalVars.sceneRect = view.frame
myGlobalVars.sceneRect = myView.frame
}
if #available(iOS 11.0, *) {
myGlobalVars.topSafeArea = view.safeAreaInsets.top
myGlobalVars.bottomSafeArea = view.safeAreaInsets.bottom
} else {
myGlobalVars.topSafeArea = topLayoutGuide.length
myGlobalVars.bottomSafeArea = bottomLayoutGuide.length
}
if myGlobalVars.gameScene == nil {
let scene = GameScene(size: myView.frame.size )
scene.anchorPoint = CGPoint(x: 0.0, y: 0.0)
scene.backgroundColor = .clear
scene.scaleMode = .aspectFit
myGlobalVars.gameScene = scene
myView.presentScene(scene)
}
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(myView)
myView.isHidden = false
myView.ignoresSiblingOrder = true
myView.showsFPS = true
myView.showsNodeCount = true
myView.showsPhysics = true
}
override var shouldAutorotate: Bool {
return false
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .portraitUpsideDown
} else {
return .all
}
}
override var prefersStatusBarHidden: Bool {
return false
}
}

GC
Code Block
import SpriteKit
import SwiftUI
struct GlobalVars{
var backGround : SKSpriteNode?
var sceneRect : CGRect
var safeSceneRect : CGRect
var gameScene : GameScene?
var topSafeArea : CGFloat
var bottomSafeArea : CGFloat
}
struct ScreenDimensions{
var widthPix : CGFloat
var heightPix : CGFloat
var widthPts : CGFloat
var heightPts : CGFloat
}
var myGlobalVars = GlobalVars(backGround: nil, sceneRect: .zero, safeSceneRect: .zero, gameScene: nil, topSafeArea: 0.0, bottomSafeArea: 0.0)
class GameScene: SKScene, SKPhysicsContactDelegate{
override func sceneDidLoad(){
super.sceneDidLoad()
print ("GC sceneDidLoad")
}
override func didMove(to view: SKView){
super.didMove(to: view)
physicsWorld.contactDelegate = self
self.isHidden = true
myGlobalVars.backGround = SKSpriteNode(imageNamed: "background")
myGlobalVars.backGround!.zPosition = -1
myGlobalVars.safeSceneRect = view.frame
myGlobalVars.gameScene = self
self.isHidden = false
self.isUserInteractionEnabled = false
self.addChild(myGlobalVars.backGround!)
let myAll = MyGem()
self.addChild(myAll)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?){
super.touchesBegan(touches, with: event)
#if DEBUG
print ("touches began")
#endif
}
}

Gem
Code Block
import Foundation
import SpriteKit
class MyGem : SKSpriteNode{
init(){
let skTexture = SKTexture(imageNamed: "gem_red")
super.init(texture: skTexture, color: .clear, size: CGSize(width: 67, height: 67))
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
self.size = CGSize(width: 67, height: 67)
self.zPosition = 2
self.position = CGPoint(x: 0, y: 0 )
self.position = CGPoint(x: 67/2 + 50,
y: 67/2 + 50)
self.size = CGSize(width: 67, height: 67)
self.isHidden = false;
self.isUserInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}


I stil don't understand why you create objects in didMove and not in didLoad (important including for setting delegate):

Code Block
override func sceneDidLoad(){
super.sceneDidLoad()
print ("GC sceneDidLoad")
physicsWorld.contactDelegate = self
self.isHidden = true
myGlobalVars.backGround = SKSpriteNode(imageNamed: "background")
myGlobalVars.backGround!.zPosition = -1
myGlobalVars.safeSceneRect = view.frame
myGlobalVars.gameScene = self
self.isHidden = false
self.isUserInteractionEnabled = false
self.addChild(myGlobalVars.backGround!)
let myAll = MyGem()
self.addChild(myAll)
}
override func didMove(to view: SKView){
super.didMove(to: view)
}


@SergioDCQ
In your didMove(to) of GameScene:
Code Block
self.isUserInteractionEnabled = false


Why false? It needs to be true when you want to detect touches.
&OOPer
That's exactly what I asked 1 day ago


I read again your initial post:
However...if I set GameScene's isUserInteractionEnabled to true, then I do get the touches and the debugger breaks.
So, why don't you set userInteraction to true ?!? It is needed if you want to receive the events.
I must be missing something in your post.






I stil don't understand why you create objects in didMove and not in didLoad (important including for setting delegate):

sceneDidLoad comes before didMove, and some of the variables from GVC are not yet filled

In your didMove(to) of GameScene:

Code Block
self.isUserInteractionEnabled = false

Why false? It needs to be true when you want to detect touches.
I've gone back on that one many times. All it does is make sure I get touches for the screen itself, not its children
Also for the sample code I uploaded today, I somehow left out the code below (in GVC) and call it right after I create myView

Code Block
private func addConstraints(){
var constraints = [NSLayoutConstraint]()
//add
constraints.append(myView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor))
constraints.append(myView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor))
constraints.append(myView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor))
constraints.append(myView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor))
//activate
NSLayoutConstraint.activate(constraints)
}

I have somehow lost the ability to receive touch notifications
 
 
Q