Adding Controller Support to your iOS App

Sam DuBois
Jun 15 · 5 min read

From PS4 and Xbox One Controller Support to subscription services like Apple Arcade, there is no better time to learn how to add support for controllers in your game or application. Doing so not only enhances the experiences of your platform, but it also helps push the entire iOS gaming industry forward. While this task is quite easy, unfortunately controller support is shrouded by lack of documentation and tutorials. This article is should hopefully make it a little easier to wrap your head around Apple’s documentation and get controllers working in your iOS, iPadOS, macOS, or tvOS application!

Setup

To start, create a new Xcode Project, select the game option and name it whatever you would like:


First, we want to import the GameController Library to get frameworks we need for controller support. Under import GameplayKit, add the following line:

import GameController

Next, in the GameScene.Swift file, add the following code to the didMove() function:

ObserveForGameControllers()

We have not defined this function yet so this will throw an error. No worries, as we complete the rest of the code this error will take care of itself. Additionally, feel free to get rid of any of the code generated by Apple in the GameScene.Swift file like so:

Detecting Controller Input

We now want to create the ObserveForGameControllers() function in the same file. Paste this code into your project underneath the didMove() function:

// Function to run intially to lookout for any MFI or Remote Controllers in the areafunc 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)}

This code will allow us to detect when a controller has connected to our device via bluetooth and is ready to be used by our app. Pretty cool right?

Next, we want to to establish what will happen once the controller connects to our application and once it disconnects as well. To do this you will notice in the Notification Triggers we define a connectControllers() function and a disconnectControllers() function to handle these cases. Underneath the observerForGameControllers() paste this code:

// This Function is called when a controller is connected to the Apple TV@objc func connectControllers() {//Unpause the Game if it is currently pausedself.isPaused = false//Used to register the Nimbus Controllers to a specific Player Numbervar indexNumber = 0// Run through each controller currently connected to the systemfor controller in GCController.controllers() {//Check to see whether it is an extended Game Controller (Such as a Nimbus)if controller.extendedGamepad != nil {controller.playerIndex = GCControllerPlayerIndex.init(rawValue: indexNumber)!indexNumber += 1setupControllerControls(controller: controller)}}}

In this function, we first want to unpause the game if the controller connects. Next, we check and see how many ‘extended Gamepads’ (controllers) that we have connected to the app. Using a super rad indexing property that each controller has, we can set an index to each controller and establish players in our app. We then also call a separate setupControllerControls() function in this for loop to detect input changes from each controller individually!

Finally, paste this code underneath that function to account for when a controller disconnects from the game. For right now, this just pauses the scene, but feel free to add to this as you see fit.

// Function called when a controller is disconnected from the Apple TV@objc func disconnectControllers() {// Pause the Game if a controller is disconnected ~ This is mandated by Appleself.isPaused = true}

Establishing Controls

Most important is the code that follows, under disconnectControllers() add the following method:

func setupControllerControls(controller: GCController) {//Function that check the controller when anything is moved or pressed on itcontroller.extendedGamepad?.valueChangedHandler = {(gamepad: GCExtendedGamepad, element: GCControllerElement) in// Add movement in here for sprites of the controllersself.controllerInputDetected(gamepad: gamepad, element: element, index: controller.playerIndex.rawValue)}}

This function will allow us to handle different controller input and perform actions in our software from changes made by our hardware. This is really really cool! Using a valueChangeHandler() we are able to call a function for a specific controller every time a change in that controller is detected!

Mapping Controls

Finally, we are able to map the buttons of our controllers to actual functions and code in our app. Create a new swift file in your project called Controls.Swift. In here, import all the same frameworks we were using from before and extend the GameScene class:

In here, add this function into the extension:

func controllerInputDetected(gamepad: GCExtendedGamepad, element: GCControllerElement, index: Int) {if (gamepad.leftThumbstick == element){if (gamepad.leftThumbstick.xAxis.value != 0){print("Controller: \(index), LeftThumbstickXAxis: \(gamepad.leftThumbstick.xAxis)")}else if (gamepad.leftThumbstick.xAxis.value == 0){// YOU CAN PUT CODE HERE TO STOP YOUR PLAYER FROM MOVING}}// Right Thumbstickif (gamepad.rightThumbstick == element){if (gamepad.rightThumbstick.xAxis.value != 0){print("Controller: \(index), rightThumbstickXAxis: \(gamepad.rightThumbstick.xAxis)")}}// D-Padelse if (gamepad.dpad == element){if (gamepad.dpad.xAxis.value != 0){print("Controller: \(index), D-PadXAxis: \(gamepad.rightThumbstick.xAxis)")}else if (gamepad.dpad.xAxis.value == 0){// YOU CAN PUT CODE HERE TO STOP YOUR PLAYER FROM MOVING}}// A-Buttonelse if (gamepad.buttonA == element){if (gamepad.buttonA.value != 0){print("Controller: \(index), A-Button Pressed!")}}// B-Buttonelse if (gamepad.buttonB == element){if (gamepad.buttonB.value != 0){print("Controller: \(index), B-Button Pressed!")}}else if (gamepad.buttonY == element){if (gamepad.buttonY.value != 0){print("Controller: \(index), Y-Button Pressed!")}}else if (gamepad.buttonX == element){if (gamepad.buttonX.value != 0){print("Controller: \(index), X-Button Pressed!")}}}

This code will allow you to map specific controls to each of the buttons on the MFi, PS4, and Xbox One Controllers! For more specific layout guides on controllers from Apple, see this link here: Old Apple Documentation. Just beware when testing that simulators do have trouble connecting with the controllers. If it is not working in the simulator try it with an actual device!

Summary

Thats all you need to get controller support started in your application. If you have any questions feel free to reach out to me and I hope this got rid of some headaches you may have been experiencing.