How to Build A Shaking Game for iPhone

For years, I’ve wanted to make an app that focuses entirely on shakes. I just couldn’t think of any reasonable idea that warranted that sort of user interaction. That is until now! Today, we’re going to build a rhythmic shaking game. Essentially, we’ll turn your iPhone into a Shake Weight! The longer you can shake your iPhone, the more points you get. Create a new Single View Swift project in Xcode, and we’ll get started!

Layout the Storyboard

Drag a label into the middle of the screen, and change the text to 0. This will be your score label. Set the font to System 100.0. Center the text alignment. Change the Autoshrink to Minimum Font Scale with a scale of 0.5. Let’s add some constraints to go with it. Center the label horizontally within the safe area. Then, center it vertically in the safe area as well. That takes care of positioning. Now make its width equal to 85% of its superview, and set its height equal to 30% of its superview. That takes care of the score label.

Slide another label right above the score label. This will display helpful messages for the player. Let’s write Shake to Start for the initial text. Change the font to System 30.0. Center the alignment, and use the same Autoshrink settings as you did for the score label. As for constraints, center the label horizontally in the safe area. Set its vertical positioning relative to score label, then adjust that spacing to a Standard unit. Set the label’s width equal to 85% of the superview, and constrain the height to 10% of the superview.

Make sure to let Xcode adjust your frames to match your constraints after you’ve set everything. The final storyboard should look like this.

Shake Game Storyboard

Does your storyboard look similar? Great! Go ahead and hop into the Assistant editor, and link your labels into your ViewController.swift to create two outlets. Call them scoreLabel and messageLabel. Our work in the storyboard is done!

Coding the Shakes

At the top of ViewController.swift, we need to add the following variables.

var gameActive = false
var score = 0
var timeOfLastShake = 0
var timer = Timer()

We’ll use the gameActive boolean to keep track of whether the player is in the middle of a game or not. This is necessary to keep track of whether a shake should start a new game or contribute to the user’s score in an existing game.

The score variable keeps track of how many seconds the player has been actively shaking his device. We’ll give the player a grace period of a couple seconds between shakes before we end the game. Therefore, we need a timestamp of the most recent shake stored in timeOfLastShake. Finally, our timer is used to schedule the score incrementation every second and to check that the player is still actively shaking.

Now we need some functions to register shakes. Add these to the bottom of ViewController.swift.

override var canBecomeFirstResponder: Bool {
    get {
        return true
    }
}

override func motionEnded(_ motion: UIEventSubtype, with event: UIEvent?) {
    if motion == .motionShake {
        print("Shake!")
        if !gameActive {
            gameActive = true
            scoreLabel.text = String(score)
            messageLabel.text = "Good Luck!"
            
            startTimer()
        } else {
            timeOfLastShake = score
        }
    }
}

We override canBecomeFirstResponder to return true, so that we can process gestures. That allows us to use the motionEnded function. We only check for shakes. If a game is not currently active, we start it, reset the score label, wish the player good luck, and start our timer. If the game is currently active, we update the time of our last shake to equal the score. The score is simply a timer, so it serves as an effective timestamp for our purposes.

Writing the Timer Code

We can finish this app with some code for our timer. In our last bit of code, we called startTimer. Let’s define it at the bottom of ViewController.swift.

func startTimer(){
    timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.updateScore), userInfo: nil, repeats: true)
}

Note that our time interval is one second, and our selector is updateScore. Due to the way that selectors work, we need to expose our updateScore function to Objective-C in order for it to process. That just requires adding @objc in front of the function, like this.

@objc func updateScore(){
    score += 1
    scoreLabel.text = String(score)
    
    let timeSinceLastShake = score - timeOfLastShake
    
    switch timeSinceLastShake {
    case 1:
        messageLabel.text = "Great Job!"
    case 2:
        messageLabel.text = "Hurry Up!"
    case 3:
        messageLabel.text = "That's Too Slow!"
    default:
        gameActive = false
        score = 0
        timeOfLastShake = 0
        messageLabel.text = "Game Over! Shake to Play Again."
        timer.invalidate()
        timer = Timer()
    }
}

Take a look at the contents of the function. We increment the score by one, then update the score label to reflect the change.

Next, we check how long it has been since the last shake. We’ll adjust our message to the player based on this spread. It’s impossible for the difference to be zero, since we literally just incremented the score. Therefore, the first case to address in our switch statement is 1 second. I think that warrants a “Great Job!”  If the player has fallen behind an additional second, we’ll tell him to “Hurry Up!” If our spread is 3 seconds, we’ll issue a warning: “That’s Too Slow!” Finally, our default case will execute if the player fails to deliver a shake after the warning. We’ll set the game to inactive, reset our variables, display the game over message, and unschedule our timer.

Once gameActive is set to false, the next shake will restart the game. This will reactivate our timer, and take us back through the same game logic.

Final Thoughts

There are two things you’ll want to keep in mind. First, you have to shake rhythmically with a distinct break in between your shakes. Otherwise, your device won’t detect the end of your first shake and the start of your next one.

Second—and this is more important—you’re no longer restricted to the mundane “nothing much” when your friends ask you, “What’s shaking?” Now, you have the option to proudly state, “My phone!” Your friends will gawk at the impressive score on your screen. Perhaps the counter is at 85,000 or so. Maybe more. Recall that we set the score label to autoshrink if necessary, so you’re going to want to score high enough to test that. Good luck!