I’m currently building out a new MVVM app in SwiftUI and wanted to see whether there was a cleaner way of assigning a value from a CurrentValueSubject to my ViewModel, whilst still achieving animations?
I’m currently leaning toward solution 2 below as that keeps the animation code neatly within the View, but it’s quite annoying having to setup another State for every animated change. Hoping there’s another way!
Solution 1 - use sink and withAnimation together in the ViewModel:
ViewModel:
authenticationService.authenticationState
.receive(on: DispatchQueue.main)
.sink { [unowned self] state in
withAnimation(.easeInOut(duration: Constants.Animation.slideSpeed)) {
self.authenticationState = state
}
}
.store(in: &cancellables)
Solution 2 - Use assign in the ViewModel and onReceive (+ withAnimation) in the view
ViewModel:
authenticationService.authenticationState
.receive(on: DispatchQueue.main)
.assign(to: &$authenticationState)
View:
struct ContentView: View {
@StateObject private var viewModel = ViewModel()
@State private var authenticationState: AuthenticationState = .loggedOut
private var shiftAnimation: AnyTransition {
.asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .leading))
}
var body: some View {
ZStack {
switch authenticationState {
case .loggedOut:
LoginView()
.transition(shiftAnimation)
case .loggedIn:
AdminView()
.transition(shiftAnimation)
}
if viewModel.showProgressView {
ProgressView()
}
}
.onReceive(viewModel.$authenticationState) { state in
withAnimation(.easeInOut(duration: Constants.Animation.slideSpeed)) {
self.authenticationState = state
}
}
}
}