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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Practical State Machines with GameplayKit
25 mins
Using Other Abilities of GKStateMachine
Remember the different transitions between states of the state machine?
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:
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. :]