Using Swift, how do I *continuously* test for a GamePad being on/connected?

Using Swift, how do I continuously test for a GamePad being on/connected?

When you build a Browser-based Canvas + Javascript Game, you do the above via:

function listenForGamepadConnected() {

    window.addEventListener("gamepadconnected", (event) => {
       
       // this is the BIGEE that is always testing

    });

}   // listenForGamepadConnected

And it definitely works!

I cannot find its equivalent in the Xcode/Swift world?

I do see the following that’s built into a boilerplate Game that you initially create using Xcode - but it seems that it just looks once, versus continuously:

    func ObserveForGameControllers() {
                
        NotificationCenter.default.addObserver(
                    self,
                    selector: #selector(connectControllers),
                    name: NSNotification.Name.GCControllerDidConnect,
                    object: nil)
       
        NotificationCenter.default.addObserver(
                    self,
                    selector: #selector(disconnectControllers),
                    name: NSNotification.Name.GCControllerDidDisconnect,
                    object: nil)
       
    }   // ObserveForGameControllers
    
       
    @objc func connectControllers() {
       
        // Unpause the Game if it is currently paused
        self.isPaused = false
       
        // Used to register the Nimbus Controllers to a specific Player Number
        var indexNumber = 0

        // Run through each controller currently connected to the system
        for controller in GCController.controllers() {
           
            // Check to see whether it is an extended Game Controller (Such as a Nimbus)
            if controller.extendedGamepad != nil {
                print("CONNECTED - Extended Gamepad #\(indexNumber)")
                
                controller.playerIndex = GCControllerPlayerIndex.init(rawValue: indexNumber)!
                
                indexNumber += 1
                setupControllerControls(controller: controller)
            }
            else {
                print("CONNECTED - but, NOT an Extended Gamepad #\(indexNumber)")
            }
           
        }
       
    }   // connectControllers

    
    @objc func disconnectControllers() {
       
        print("DIS-CONNECTED")

        // Pause the Game if a controller is disconnected ~ This is mandated by Apple
        self.isPaused = true
       
    }   // disconnectControllers

I try to call it in a Timer loop, but still does not work:

    @objc func testForGamepadIsConnected() {

        ObserveForGameControllers

        var gamepadOn = !self.isPaused   // does not work
       
    }   // testForGamepadIsConnected


    func startTestForGamepad() {

        guard (gamepadTimer != nil) else {
            gamepadTimer = Timer.scheduledTimer(
                                    timeInterval: 1.0,
                                    target: self,
                                    selector:#selector(testForGamepadIsConnected),
                                    userInfo: nil,
                                    repeats: true)
            return
        }

    }   // startTestForGamepad


    func stopTestForGamepad() {

        guard (gamepadTimer == nil) else {
            gamepadTimer!.invalidate()
            // .invalidate() removes Timer() from gamepadTimer, so reinitialize it.
            gamepadTimer = Timer()
            return
        }

    }   // stopTestForGamepad

Scoured the Google world, but I’ve come up empty.

Would appreciate some genius out there providing what I’m missing.

Thanks loads.

Answered by JohnLove in 749744022

SOLVED --

1st and foremost, I want to sing the praises of Keith at Apple DTS without whom I could never have chased down the cause of my problem. His assistance on this one issue totally pays for my $99.

A significant part of the solution rests with the just released update of Xcode to 14.3 and tvOS to 16.4

Now the expected CONNECTED and DIS-CONNECTED Console messages appear as anticipated.

I've got some more work to do, but at least Apple's

NotificationCenter.default.addObserver(..)

works as Apple advertises.

WHOOPIE!!

Thank you Justin for not throwing a brick at me.

Hi JohnLove

Listening for the NSNotification.Name.GCControllerDidConnect notification is equivalent to listening for gamepadconnected in JavaScript. The NSNotification.Name.GCControllerDidConnect notification is posted whenever a compatible game controller connects to the device. We also recommend, as shown in the template project, to check GCController.controllers() on app start to discover controllers that connected to the device before the user launched your app. You can also poll GCController.controllers() on a timer to discover new controllers, but this is less efficient than listening for the NSNotification.Name.GCControllerDidConnect notification.

If you are not seeing the NSNotification.Name.GCControllerDidConnect notification posted and GCController.controllers() is returning an empty array, we need to know what make and model game controller you are testing with. The GameController framework supports many popular controllers (including Xbox and PlayStation controllers), but not all controllers are supported at this time.

I apologize for being so tardy in getting back to you. I assure you it's not because I don't consider this a serious problem ... because I definitely do.

  1. Nimbus++ Extended Game Controller

  2. My call to ObserveForGameControllers() is within my override func didMove(to view: SKView).

  3. I definitely see within my code that the "CONNECT" message shows within the Xcode Console =

"CONNECTED - Extended Gamepad #0"

However, what is disturbing is that IF I deliberately turn OFF my Nimbus+ before I even Run my Code the 1st time, the "CONNECT" message still shows. I clearly do not understand the latter. It's OFF and it still registers as present (index = 0).

  1. Next, I see within my Code no "DIS-CONNECTED" message if I press the "Home" Button on my Nimbus+ to turn off the Nimbus+ during the playing of the Game. Again, it's OFF and it still registers as present.
    @objc func disconnectControllers() {      

        print("DIS-CONNECTED")

        // Pause the Game if a controller is disconnected ~ This is mandated by Apple
        self.isPaused = true        

    }   // disconnectControllers

I believe the root of my problem centers on the fact that I see no "DIS-CONNECTED" message.

Because if I did, I could simply state:

gamepadOn = !self.isPaused

and I wouldn't need to bug you.

John Love

One more tidbit ...

My Game runs when the Nimbus+ is on and nothing moves when the Nimbus+ is off ... so that's not the problem.

I want to programmatically find out if the Nimbus+ is on or off for the sole purpose of setting the fill color of a "Status Light" SKSpriteNode I have in my SKView. And I have a func updateStatus() that does that color change for me depending on the value of my gamepadOn var.

If it weren't for that, the Nimbus+ Buttons being inert (after I press its Home Button) would be all the "clue" the user would need.

Hi John Love

Is this the controller that you are testing with? There are multiple Nimbus models. Does yours have Home, Menu, and Options buttons, or just a Menu button? https://steelseries.com/gaming-controllers/nimbus-plus

In general, the NSNotification.Name.GCControllerDidDisconnect notification should be posted when the host (Mac or iOS device) looses Bluetooth connectivity to the gamepad. If you are not seeing the NSNotification.Name.GCControllerDidDisconnect notification posted, check your Apple device's Bluetooth settings and confirm whether the gamepad is still listed as connected.

If your gamepad is turned off but you are seeing the NSNotification.Name.GCControllerDidConnect notification posted, check your Apple device's Bluetooth settings and confirm whether it reports the gamepad is connected.

Hi John Love

You mentioned that you are testing on the simulator. Do you see the same behavior on a real device? The simulator does not currently track controller connects and disconnects from the host Mac.

Justin

Justin, I totally understand what you are saying about the Simulator because it has a whole slew of problems associated with Sound errors and others as well.

But ... when my Nimbus+ is connected, the Nimbus' Buttons and Joysticks work as I have coded them ... and when it is not connected, nothing works as you would expect. So, so far everything is good.

Nimbus+ on, Buttons and Joysticks work ... Nimbus off, they do not work. Simple and very good!

Two challenges:

(1) if the Nimbus+ is off when I initially Run my App, Apple's code still registers the Nimbus+ as connected. In short, the @objc func disconnectControllers() doesn't get called.

(2) if the Nimbus+ is on and the Game is running as it should, and then I press the Home Button of the Nimbus+ to turn it off, once again @objc func disconnectControllers() is not called.

A significant update =

Initially I had all the Game Controller code in my GameScene = SKScene, with the call to ObserveForGameControllers within the SKScene's didMove and all I have reported happens.

Then, I spent a bunch of time moving all the Game Controller code to my GameViewController = UIViewController, with the call to ObserveForGameControllers now moved to its viewDidLoad().

Conceptually I believe the Game Controller code properly belongs as part of a ViewController and not a UIView because, again conceptually, the Game Controller controls all Scenes, not just one Scene.

Anyway, now the Game Controller code in GameViewController, including Apple's func setupControllerControls(...). The latter calls Apple's func controllerInputDetected(...)which is also moved to my GameViewController. This last funccalls a whole slew of funds located in my GameScene... like pressing "Y" to move a Game Piece "up".

All the Buttons and Joysticks work great when the Nimbus+ is on and don't work when the Nimbus+ is off ... identical to when my Game Controller Code was part of my GameScene = SKScene.

But the problems of turning off the Nimbus+ before App is run and while the App is running remain .

Hi John Love

Let me be more clear, your (1) and (2) challenges are currently expected when testing in the Simulator. I encourage you to test on a real iOS device.

If you do not yet have a real iOS device to test with, consider building your app for Mac Catalyst. It should not be too difficult if you are primarily using SpriteKit and your app already supports the iPad screen size(s). The GameController framework is fully supported in Mac Catalyst (and native macOS) apps; you will see the correct game controller connect/disconnect notifications posted when you power on/off your Nimbus+ while your app is running as a Mac Catalyst app.

Justin

Justin, are you 100% certain that the Simulator is the certain culprit and the only source of my problems?

I say this because you are saying the Simulator is 100 % defective on this issue.

And it’s a significant issue because you & I know game development has a very large % of all development.

You are asking me to do a whole bunch of work and so I need to be 100% certain.

I am not inclined to do all this work on a maybe.

Okay, Justin,

  1. I turned on my Apple TV

  2. Within Xcode, I chose the destination = "TV Room" = my Apple TV

  3. Under Xcode's Window Menu, I selected "Devices and Simulators"

  4. Paired with "TV Room"

  5. Ran the App within Xcode

  6. My App showed on the Apple TV

  7. Same Problem(s)

(a) I guess this is definitely not your idea of a "Real Device"

OR

(b) I have a coding problem

Speaking of code, here is my updateGameStatus func

    func updateGameStatus() {        

        if itsScene.isPaused { 
            // Set within the connect/disconnect @objc handlers written by Apple
            // The reason for itsScene.isPaused syntax is because I moved all the 
            // NotificationCenter stuff from my GameScene (SKScene) class
            // to my GameViewController (UIViewController) class.
            itsStatusNode.fillColor = SKColor.black
            itsStatusNode.strokeColor = SKColor.black
        }
        else if ballIsMoving {   // set = true when I start/resume the Game or = false when I pause the Game
            itsStatusNode.fillColor = SKColor.white
            itsStatusNode.strokeColor = SKColor.white
        }
        else {
            itsStatusNode.fillColor = SKColor.black
            itsStatusNode.strokeColor = SKColor.black
        }

    }

This func demonstrates my problem because when the Game is running (ballIsMoving) and I press the "Home Button" of my Nimbus+ to turn it off, the light stays white.

In the meantime, I am trying to discover my coding problem. FWIW, I did attempt to insert a Timer that polls the above func, but no such luck = that would have been too easy.

John

Hi John

Without your code in front of me I can't speak with 100% certainty whether some observed behavior is due to an OS bug or a bug in your code. If you would like to work 1:1 with an Apple engineer, who can look at your full project (if you choose to share it), I encourage you to reach out to Developer Technical Support..

The simulator is useful tool for quick iterative development, but it does not match the way that App Store review or your customers will experience your app. Device features like accelerometer/gyro are not possible to accurately reflect in the simulator for example. The game controller experience in the simulator could be improved, and I encourage you to file enhancement requests for this or anything else you think could be improved in the simulator.

If you are not seeing the controller connect/disconnect notifications posted when running on the AppleTV device, that may be an OS bug. I would confirm by making a simple project that listens for the NSNotification.Name.GCControllerDidConnect and NSNotification.Name.GCControllerDidDisconnect notifications and prints a log message when the handler for each is called. Use this simple project to test whether the notifications are posted as your power on/off your Nimbus+.

Justin

Not small Project, but I already have print statement in the @objc disconnect code and the statement is never called EVER EVER

I would like to submit the whole project to the Developer shop + a link to this very long conversation.

Anyway I will use my first TSI red flag and do that after I print out my existing changes and stare at them until Friday.

And then let them work. That oughta get me some bang for my $99 bucks, at least $49.50 worth.

Let them work for awhile. Most of the App Review process is done by inanimate bits and bytes. So it’s time to get some breathing mammals into the fray, don’t you think.

When I first submitted my very 1st App and was initially rejected for something stupid. I yelled $@!? and someone here said you’re yelling at a damn computer.

So I calmed down and submitted an Appeal which overturned the Rejection.

and BAMB .. “Monster Paddle Pong” is up on Store in all its glory.

Got work to do folks

While my TSI is being worked …

I know how to solve the Gamepad Connectivity problem.

Within Apple Inc there are many, many programmers.

Premise:

  1. there’s one set of programmers that work on System stuff, eg, the Bluetooth Menu.

  2. there’s a 2nd set of programmers that work on Xcode stuff.

Set 1 did the Connectivity right. Stare at the Bluetooth Menu while you’re turning off & on your Game Controller. Said Menu shows within a split second this toggling. SPOT ON.

Instruct Set 2 to copy their code.

DONE!

👍👍👍

What a 100% waste of my time = sending that TSI to Apple.

They sent back “Not enough info”.

So, this entire Chat from the get-go isn’t enough?

Well maybe it wasn’t to them because of their incompetence.

My reply to this was even more blunt.

Forget TSIs, Justin.

I will just scour around until I find the solution.

Using Apple's very own ObserveForGameControllers() which I call in my GameViewController's viewDidLoad(), even when my Nimbus+ Game Controller is set to off when I initially Run my Project, Apple's Code consistently shows "CONNECTED"

This totally blows my mind.

Furthermore, Apple claims that their two NotificationCenter.default.addObserver() calls are continuously called. So, I should be able to toggle my Nimbus+ off and on while playing ... and Apple claims that these two addObserver() are cotinuously called. I deliberately have print statements in each @objc and while this toggling is going on, there should be a whole slew of print statements that toggle between "connected" and "dis-connected".

Never happens??

Stepping back from code for a moment ...

What do I want to do?

1) upon initial start up of app, I want to wait for a gamepadconnected Event.

2) Once I get this Event, I immediately change a SKSpriteNode's color and then start looking for:

a) button presses on the gamepad and

b) a gamepaddisconnected Event

3) So while I am executing code based on 2(a), I receive 2(b) .. I immediately change the above color to something different, followed by looking for another gamepadconnected Event.

Back-and-forth

Back-and-forth.

Using Swift, how do I *continuously* test for a GamePad being on/connected?
 
 
Q