Building an independent watchOS 6 game using SwiftUI and GamePainKit

Alex Moiseenko
techpro.studio
Published in
5 min readNov 18, 2019

Technology stack decision

Well, I guess the first question for me could be something like: “Hey, dude, why do you use SwiftUI for a game?”. And this is a normal question because there are a lot of frameworks for games in general, included SceneKit and SpriteKit provided by Apple. I made a historical decision: the same game that I released for iOS in 2014 was entirely built using UIKit and CoreAnimation. Since I decided to write an independent watchOS game which meant using watchOS 6+, SwiftUI fitted pretty good for my solution.

Architecture

The next step was to decide what architecture to use in my app. Well, for UIKit based application I like to use patched MVVM named MVPCF. Shortly it is MVVM + Coordinator + Factory. Here is a link for the details:

But SwiftUI differs from UIKit and the same approach won’t work. I can use totally the same pattern if I have SwiftUI view as a replacement of UIView for ViewController. And from my point of view, it makes sense to use this architecture somewhere. But I wanted to use SwiftUI only, so I had two options. The first one was using Flux pattern which is trendy in the frontend world, especially in ReactJS. The second option was to use MVVM or similar approach. I didn’t want to store the whole state of an app in one place and wanted my objects to die, that’s why I decided to use MVVM. I played with architecture and tried to make it flexible and SOLID. However, there were few moments that blocked my path to clean architecture like in patched MVVM I was talking before. All the details of architecture I have chosen for the app are here:

SwiftUI navigation and GamePainKit

I would like to focus on some aspects of these two frameworks because everything else works fine. Combine could replace RxSwift and it works great.

So navigation. My game is pretty simple. It has 4 views: Main menu view, Game View, Game Finished View, and Leaderboard View. The pain begins when you need to use “replacement” in navigation. You can’t just call setViewControllers like in UIKit UINavigationController, because it uses declarative navigation links. And I think Flutter, in this case, wins SwiftUI because it has class Navigator with methods push/pop/replace. The second funny thing is that NavigationLink has two initializers with the binding state, so theoretically you can control when to show/hide the presented view. With hidden navigation bar it should imitate replacement. We can put NavigationLink with EmptyView label and binding, and launch from code changes in binding. This looks logical. Guys on StackOverflow recommended to use the same approach, but it didn’t work. When a label is EmptyView, navigation doesn’t work. I did not test it on iOS, however, watchOS gave me that results. So I decided to write my own NavigationView. As you see, if the application has not trivial navigation, it can cause an inconvenience sometimes.

So GamePainKit. Well, GameKit works well when your player is authenticated. GKLocalPlayer in iOS has authenticateHandler and it looks like this:

var authenticateHandler: ((UIViewController?, Error?) -> Void)? { get set }

It means that if your player is not authenticated, you should show provided UIViewController and everything will be ok. And it works fine on iOS. However, if you look at the same property in watchOS, it has a different signature and looks like this:

var authenticateHandler: ((Error?) -> Void)? { get set }

So you have only an error. It is logical because the watch has a small screen and it was a companion for iOS before watchOS 6 and it gets GKLocalPlayer state from iPhone. Thus, it means that you need to authenticate Game Center on iPhone and everything should work. And it works sometimes. It is difficult to predict when it will work and when it will not because there were a lot of cases when my GameCenter player on iPhone was authenticated but I caught errors in watchOS like “GKLocalPlayer not authenticated”. I tried to log out and log in again, and sometimes it helped to fix the problem but not always and it was weird. If someone knows how to make this case more stable, please share your solution in comments. My game has a small feature. It shows champion on the main menu view: nickname and top score. However, since most of the time my player is not authenticated for only God knows the reason, I need to hide my GameCenter features. I think Apple just doesn’t care about GameCenter and watchOS and here is one more proof. Have a look at the screenshot:

“PlayerID is deprecated on watchOS, please use teamPlayerID”, but “teamPlayerID is unavailable for watchOS”! It is funny and sad at the same time.

Well, after going through all those things I have successfully released my SwiftUI game. I couldn’t manage the Game Center working stable as it is Apple who is responsible for that case. But the game is in App Store and you’re welcome to have a look at it:

Conclusion

If you want to make independent apps for watchOS 6+, you can use SwiftUI and Combine. But keep in mind that navigation could cause an inconvenience. And if you’re going to make an independent game for watchOS and you rely on GameKit, keep in mind that it works awful.

--

--