-
Hi, I've been thinking a lot lately about how to use pure MVVM on SwiftUI. I sympathized with the point-free episode about what problems �mvvm had and why they made TCA like this, but I think TCA is too strong dependency for my company. There is two thing I care about.
Can anyone recommand how to design pure MVVM like TCA? here is pure MVVM example code I'm currently using import Foundation
final class ParentViewModel: ObservableObject {
enum Destination {
case child1(ChildViewModel1)
}
// MARK: Output
@Published private(set) var isFetching: Bool = false
@Published private(set) var someChildViewLists: [ChildViewModel2] = []
@Published private(set) var destination: Destination? = nil
init(
isFetching: Bool,
someChildViewLists: [ChildViewModel2],
destination: Destination?
) {
self.isFetching = isFetching
self.someChildViewLists = someChildViewLists
}
// MARK: Input
func onFirstAppear() {
isFetching = true
defer { isFetching = false }
// do some network call
let networkModels = [SomeChildModel(), SomeChildModel(), SomeChildModel()]
/// `NOTE1`
/// I don't like this pattern
/// because I have to loop ForEach ViewModel in View
/// so ViewModel should be Identifiable
someChildViewLists = networkModels.map { networkModel in
return ChildViewModel2(
with: networkModel,
someDelegate2: { string in
// do something
}
)
}
// child viewmodel to show on screen when network call is done
let childViewModel1: ChildViewModel1 = .init(
someDelegate: { [weak self] childModel in
self?.doSomeLogic(with: childModel)
}
/// and some dependency injection
)
destination = .child1(childViewModel1)
}
// MARK: Private functions
private func doSomeLogic(with childModel: SomeChildModel) {
// some business logic that can be tested
}
}
struct SomeChildModel { }
final class ChildViewModel1: ObservableObject {
private let someDelegate: (SomeChildModel) -> Void
/// `NOTE2`
/// Is this the best way to communicate?
/// It's so annoying when there's a lot of closures
init(
someDelegate: @escaping @Sendable (SomeChildModel) -> Void
) {
self.someDelegate = someDelegate
}
/// `NOTE3`
/// and I think this way is ambiguous to define input
func someButtonTapped(model: SomeChildModel) {
someDelegate(model)
}
}
final class ChildViewModel2: ObservableObject {
private let model: SomeChildModel
private let someDelegate2: (String) -> Void
init(
with model: SomeChildModel,
someDelegate2: @escaping @Sendable (String) -> Void
) {
self.model = model
self.someDelegate2 = someDelegate2
}
} |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment
-
We discussed this kind of thing in our "Modern SwiftUI" series, and had an episode where we talked about child-to-parent communication. Also, the app we build in that series is open source, so you can see the final product there. The short of it is that delegate closures are probably the best way to facilitate the communication, but there are tricks you can employ to make it a little nicer than what you have above. There are more details in the episodes and the open source project. |
Beta Was this translation helpful? Give feedback.
We discussed this kind of thing in our "Modern SwiftUI" series, and had an episode where we talked about child-to-parent communication. Also, the app we build in that series is open source, so you can see the final product there.
The short of it is that delegate closures are probably the best way to facilitate the communication, but there are tricks you can employ to make it a little nicer than what you have above. There are more details in the episodes and the open source project.