This article is the 15th day article of Cluster Advent Calendar 2020. Yesterday was noir_neo's "Face Tracking with ARKit to move avatars right and left". If you want to display your avatar with Face Tracking, it will be very helpful ...!
Hello, this is the cluster, Inc. Hashimoto. I wrote Unity C # until the first half of this year, but recently I've been writing Swift exclusively. In cluster mobile app, I recently Native UI, but when I was developing it, I found it useful Swift enum. I will write about the state management of View.
The native part of the cluster's mobile app uses the MVVM architecture (https://ja.wikipedia.org/wiki/Model_View_ViewModel) and uses RxSwift for data binding. So, the ViewModel has the state of View (ViewController in Swift).
Initially, I wrote a ViewModel like this and bound it with View. (* Private definitions are omitted because it will be redundant)
ViewModel.swift
enum HogeViewStatus {
case processing
case empty
case idle
}
final class HogeViewModel {
let updateViewStatus: Observable<HogeViewStatus>
var hoges: [Hoge] {
return hogesRelay.value
}
init(hogeRepository: HogeRepository) {
self.hogeRepository = hogeRepository
updateViewStatus = hogeViewStatusRelay.asObservable()
refreshRelay
.flatMapLatest { _ in hogeRepository.get() }
.subscribe(onNext: { [weak self] hoges in
self?.hogesRelay.accept(hoges)
let status: HogeViewStatus = hoges.isEmpty
? .empty
: .idle
self?.hogeViewStatusRelay.accept(status)
})
.disposed(by: disposeBag)
}
func refresh() {
refreshRelay.accept(())
}
}
What I was worried about in this code was that it was not associated with the data displayed in the list when it was in the idle
state of HogeViewStatus
(the state in which the list should be displayed). For example, while modifying the code, when HogeViewStatus
is .empty
, you can write code that hoges
is not isEmpty
, so depending on the screen specifications, it may be unexpected in View. It is expected that will be displayed.
Swift has a mechanism called Associated Values that solves the above problems. (This is very convenient !!)
It's a rough explanation, but Associated Values is that you can assign a free type to each enum case. This time, [Hoge]
can be added to idle
.
enum HogeViewStatus {
case processing
case empty
case idle([Hoge])
}
Let's rewrite the ViewModel earlier using this.
ViewModel.swift
enum HogeViewStatus {
case processing
case empty
case idle([Hoge])
}
final class HogeViewModel {
let updateViewStatus: Observable<HogeViewStatus>
//It will be added to HogeViewStatus so it will not be necessary
// var hoges: [Hoge] {
// return hogesRelay.value
// }
init(hogeRepository: HogeRepository) {
self.hogeRepository = hogeRepository
updateViewStatus = hogeViewStatusRelay.asObservable()
refreshRelay
.flatMapLatest { _ in hogeRepository.get() }
.subscribe(onNext: { [weak self] hoges in
//hoges changed to be given to idle
// self?.hogesRelay.accept(hoges)
let status: HogeViewStatus = hoges.isEmpty
? .empty
: .idle(hoges) //assocate hoges
self?.hogeViewStatusRelay.accept(status)
})
.disposed(by: disposeBag)
}
func refresh() {
refreshRelay.accept(())
}
}
Now that we don't have to accept them separately, the actual state and array are no longer different.
However, this alone is difficult to handle on the View side, so I will write an extension that extracts [Hoge]
from HogeViewStatus
.
HogeViewModel.swift
extension HogeViewModel {
var hoges: [Hoge] {
// (How to write this pattern match is also convenient, isn't it?)
if case .idle(let hoges) = hogeViewStatusRelay.value {
return hoges
}
return []
}
}
Now you can safely handle states and arrays on the View side. Also, it seems that you do not have to write complicated code even if the state of View increases or you want to display data other than [Hoge]
. The usage of Swift enum introduced this time is just an example, but I hope you can understand the convenience!
Tomorrow is YOSHIOKA_Ko57's "Change the UI judgment area for each platform in Unity". I'm looking forward to it ...!
Recommended Posts