Practical State Machines with GameplayKit

In this tutorial, you’ll convert an iOS app to use a state machine for navigation logic using GameplayKit’s GKStateMachine. By Keegan Rush.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 3 of 3 of this article. Click here to view the first page.

Using Other Abilities of GKStateMachine

Remember the different transitions between states of the state machine?

Kanji List State Transitions

Well, you can add these transitions to your GKState classes to prevent any invalid transitions from ever happening. Open AllState.swift and add the following method:

override func isValidNextState(_ stateClass: AnyClass) -> Bool {
  return false
}

isValidNextState(_:) lets you define which states that this GKState can reach. Because it returns false, the state machine won’t be able to transition from this state to any others. Build and run the app. Tapping on a kanji does nothing:

Unable to transition between states

Because it only makes sense for the AllState to move to the Detail Screen for a particular kanji, the only valid next state is the DetailState. Replace the contents of isValidNextState(_:) with this:

return stateClass == DetailState.self

Build and run the app, and you should be able to reach the Detail screen again. Next, add this to DetailState.swift:

override func isValidNextState(_ stateClass: AnyClass) -> Bool {
  return stateClass == AllState.self || stateClass == ListState.self
}

The DetailState can move to either of the remaining states, so you return true for either state.

Similar to the DetailState, add the following to ListState.swift:

override func isValidNextState(_ stateClass: AnyClass) -> Bool {
  return stateClass == DetailState.self || stateClass == AllState.self
}

Build and run the app. Everything should still be functional.

Now, there’s one final change to make for the AllState. Open ApplicationCoordinator.swift and take a look at receivedAllKanjiNotification().

When the All button in the navigation bar is tapped, it fires a notification and the ApplicationCoordinator pops to the root view controller. The coordinator shouldn’t have this sort of knowledge about the navigation hierarchy. All it should know is that the app is meant to enter the AllState. So, remove the contents of receivedAllKanjiNotification() and replace it with this:

stateMachine.enter(AllState.self)

Now, rather than directly popping to the root view controller, receivedAllKanjiNotification() will just transition to the AllState instead. Build and run the app. When you tap the All button, it pushes a new view controller onto the stack. Rather than pushing a new view controller, you still want it to pop to the root view controller. Open AllState.swift and replace the contents of didEnter(from:) with this:

if previousState == nil {
  allKanjiListCoordinator?.start()
} else {
  (stateMachine as? KanjiStateMachine)?.presenter
    .popToRootViewController(animated: true)
}

When you call GKStateMachine.enter(_:), the previous state gets passed into didEnter(from:) in for the current state. If this is the first state for the state machine, there is no previous state, so previousState will be nil. In this case, you can call start() on the allKanjiListCoordinator. But if there is a previous state, it means you should pop to the root view controller to get back to the All screen.

Build and run the app. When on the List screen or Detail screen, the All button should take you back to the All screen.

That’s it. You’ve refactored Kanji List to use a GKStateMachine to manage the navigation in the app. Great work!

Where to Go From Here?

If you want to download the finished project, use the Download Materials button at the top or bottom of this tutorial.

Kanji List is still a simple app, but in this tutorial, you learned how to use a state machine combined with the coordinator pattern to handle the navigation in a clear and defined way. With this approach, no class needs to know how the navigation is done or what view controllers are used — simply instruct the GKStateMachine to transition to the appropriate state.

I hope you enjoyed learning about a different pattern to manage navigation in this tutorial. Maybe you can find some other uses for a GKStateMachine in the apps you’re building? If you’re interested in learning about clean architectures for your iOS apps, take a look at the Advanced iOS App Architecture book for more.

Don’t forget to take a look at the Coordinator Tutorial for iOS for more info on the coordinator pattern. As always, Apple’s documentation on GKState and GKStateMachine is invaluable. You might also be interested in learning more about state machines in general.

If you have any questions or insights to share, I’d love to hear from you in the comments below. :]

Keegan Rush

Contributors

Keegan Rush

Author

Scott McAlister

Tech Editor

Tyler Bos

Editor

Aleksandra Kizevska

Illustrator

Kelvin Lau

Final Pass Editor

Richard Critz

Team Lead

Over 300 content creators. Join our team.