We published an native iOS game application to the App store. This app provides a multi-payer game to play between your friends and neighbors. Since this game includes great random factors and customization, each time a player will be provided with variable joys. This app has a lot of UI factors and data models with a reactive relationship. Thus, we adopted RxSwift and MVVM design patterns to completely separate views, models, and logic.
We published our app to the App Store!π App Store Links
There are enormous unique and awesome games in every country and culture. "King's game" which is called "ηζ§γ²γΌγ " in Japan is also one of them. This party game is a sort of combination of, "Simon Says" and "truth or dare", but might be more flexible and thralling. We thought it would be great to introduce this game into a mobile application so that all over the people in world can know, play and share its wonderful experience!
The king's game simply consists of two factors, command and obey. Participants decide on one "King" and others(citizens). Every citizen is provided a secret index that only him/herself knows. Then, the King makes a command involving indexes, such as "No. 2 have to make No.3 laugh! π€£". Finally, each person reveals their index (number), and those who are targeted have to follow the command!
Tp apply this, we created a game app, which we replace "King" with "Queen" instead. This is because to make this game unique, and there is already a game called "King's cup" in western culture which might cause misunderstanding.
In the app, we randomly select the queen and others(citizens), the same as King's game. What is more, by using the advantage of the application platform, we can also prepare template collections of commands. When a queen chooses the command, targets are randomly selected.
Besides, we can let users edit these commands whatever they want, like a "To-do list" app feature. And of course, those commands are saved permanently in the app so that even after closing the app, users can keep on using their customized commands when restart!
Lastly, we can introduce a lot of fun animation because it's an app and this makes users much more fun!
In this work, what I contributed is as follows.
Once you launch the app, users can select whether to start a new game or modifying game settings.
The basic flow of the game is as follows.
In the game settings, what we can control is shown below.
We used the GitHub project (Kanban feature) for scheduling and followed the agile style. This makes the project complete efficiently with even remote work.
We created a whole UI mockup both considering light and dark mode with Figma. In addition to creating a style guide, We created UI components that are well suitable for implementing with MVVM design pattern.
We also prepared and introduce both light and dark modes to suffice users' demands these days. These light and dark modes correspond to the iOS system's appearance. Thus, as soon as a user switches its app appearance, the app will be reflected without restarting.
This feature was implemented by using traitCollection
and userInterfaceStyle
. We implemented a static custom color wrapped by struct
.
/// Custom color set used for this project.
/// - Compatible with dark mode.
struct CustomColor {
/// Used for main text color.
/// - Light mode -> similar to black color
/// - Dark mode -> similar to white color
static var text: UIColor {
return UIColor { (traitCollection: UITraitCollection) -> UIColor in
return traitCollection.userInterfaceStyle == .light ?
UIColor(hex: "#251F1F")! : UIColor(hex: "#E1DFDF")!
}
}
For harmony and consistency of UI, this app has a lot of custom animation and UI functions, such as transition, search bar, and loading screen.
Although the existing UISearchController
embedded in Navigation Controller is useful and well-prepared, it is hard to customize in your way. So this time, we decided not to use any UISearchController
but only our custom UISearchBar
.
To create a custom UISearchBar
, some of the basic features have to be implemented by yourself. For example,
It was a bit painstaking to do define, but thankfully, we were able to create our own flexible customized search bar!
extension CommonCommandViewController: UISearchBarDelegate {
// Whenever Text is changed
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
viewModel.searchText = searchText
viewModel.readItems()
// If text is empty -> This is executed when "x" button is click. We want to stop focus.
if searchText.isEmpty {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
searchBar.resignFirstResponder()
}
}
}
The default transition animation is nice but limited. To make the transition more game-like, we created a natural pop-up menu. We used UIViewControllerAnimatedTransitioning
and UIViewControllerTransitioningDelegate
for customizing modal animation.
// The delegatee who will do the transition. We will send this to the delegator.
class PopUpTransitioningDelegatee: NSObject, UIViewControllerTransitioningDelegate { }
extension PopUpTransitioningDelegatee {
// Tells delegate What kind if animation transitioning do you want to use when presenting ?
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
PopUpTransitioning.shared.presenting = true
return PopUpTransitioning.shared
}
// Tells delegate What kind of animation transitioning do you want to use when dismissing?
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
PopUpTransitioning.shared.presenting = false
return PopUpTransitioning.shared
}
}
This app includes many reactive scenes. For instance, when the number of players reaches the maximum, it will auto-disable the plus button. For another example, after a countdown, the app will show a different view on the screen.
Therefore, we adopted a view model (MVVM) pattern and reactive framework which is RxSwift.
We added a feature that a user can choose how many players join the game. Following the original King's game spec, we wanted to set the maximum and the minimum number of players.
So we needed features like as follows,
This was the exact opportunity to adopt RXswift. We made each button, label, and variable for #player reactive as reactive relation.
// View model
let numOfPlayers: BehaviorRelay = BehaviorRelay(value: 5)
let vm = ViewModel()
// when button tapped -> increment #player
plusButton.rx.tap
.subscribe { [weak self] _ in
self?.vm.numOfPlayers.accept((self?.vm.numOfPlayers.value)! + 1)
}
.disposed(by: vm.disposeBag)
// when #player changes -> bind to UI (label)
vm.numOfPlayers
.map{String($0)}
.bind(to: playerCountLabel.rx.text)
.disposed(by: vm.disposeBag)
// when #player changes and reach to limit, -> bind to UI (disable the button)
vm.numOfPlayers
.map {$0 < 9}
.bind(to: plusButton.rx.isEnabled)
.disposed(by: vm.disposeBag)
For another example, we created a countdown screen, in which each second the label changes, and after reaching 0 seconds, the whole view is replaced.
For this feature, we use onNext
and onComplete
.
Each second, we send onNext
and bind to UILabel. After finishing the countdown, we emit onComplete
, which triggers a next view replacing.
// View model
var timer = Timer()
var countdownTime = Int(Settings.shared.queenWaitingSeconds) // Singleton
// Subject
let rxCountdownTime = PublishSubject<Int?>()
func countdown() {
self.timer = Timer.scheduledTimer(
withTimeInterval: 1,
repeats: true,
block: { [weak self] timer in
self?.countdownTime -= 1
// Each second, send time
self?.rxCountdownTime.onNext(self?.countdownTime)
if self?.countdownTime == -1 {
// When it's 0, send finish.
self?.rxCountdownTime.onCompleted()
}
})
}
// view controller
self.viewModel.countdown()
self.viewModel.rxCountdownTime
.subscribe(onNext: { [weak self] time in
guard let time = time else { return }
DispatchQueue.main.async {
self?.countdownStackView.countdownLabel.text = String(time)
}
},
onCompleted: {
self.replaceView() // after count down, replace view
})
.disposed(by: disposeBag)
This game contains initial template command so that users only have to select them. Of course, a user can add new commands or edit them later. This is a sort of Todo list feature. We utilized it by using a UICollectionView
with difffable datasource, which elegantly displays the differences of items. In addition, Realm was used to keep items as persistence data.
Furthermore, we added a searching(filtering) feature, which leads the user to comfortably find their item.
This is an example code. The ViewModel reads stored items considering search text. The viewController subscribes snapshot and it reflects the UI.
// View model
/// Read items from realm and update snapshot (and update UI)
func readItems() {
let items: [Command]
if searchText == "" {
items = Array(
realm.objects(Command.self)
.sorted(byKeyPath: "detail", ascending: true)
)
} else {
items = Array(
realm.objects(Command.self)
.filter("detail CONTAINS[c] %@", searchText)
.sorted(byKeyPath: "detail", ascending: true)
)
}
updateSnapshot(newItems: items)
}
// view controller
viewModel.snapshotSubject
.subscribe (onNext: { [unowned self] snapshot in
self.dataSource.apply(snapshot, animatingDifferences: true)
})
.disposed(by: viewModel.disposeBag)
What we're planning to do next is as follows.