icerockdev / moko-mvvm

Model-View-ViewModel architecture components for mobile (android & ios) Kotlin Multiplatform development

Home Page:https://moko.icerock.dev/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Add additional features for creating Binding from the SwiftUI side

kramlex opened this issue · comments

add the ability to state the current value for a specific view, and not on the view in which the StateObject/ObservedObject for the ViewModel was created

here is an example:

ViewModel+Binding.swift

class SubscriptionBag: ObservableObject { }

extension ObservableObject where Self: ViewModel {
    func binding<T, R>(
        _ flowKey: KeyPath<Self, CMutableStateFlow<T>>,
        equals: @escaping (T?, T?) -> Bool,
        getMapper: @escaping (T) -> R,
        setMapper: @escaping (R) -> T,
        to bag: SubscriptionBag
    ) -> Binding<R> {
        let stateFlow: CMutableStateFlow<T> = self[keyPath: flowKey]
        var lastValue: T? = stateFlow.value
        
        var disposable: DisposableHandle? = nil
        
        disposable = stateFlow.subscribe(onCollect: { value in
            if !equals(lastValue, value) {
                lastValue = value
                bag.objectWillChange.send()
                disposable?.dispose()
            }
        })
        
        return Binding(
            get: {
                getMapper(stateFlow.value!)
            },
            set: {
                stateFlow.value = setMapper($0)
            }
        )
    }
}

SomeScreen.swift

struct SomeScreen: View {
    @StateObject private var viewModel: SomeViewModel
    
    init() {
        _viewModel = StateObject(wrappedValue: ...)
    }
    
    var body: some View {
            SomeScreenContent()
                .environmentObject(viewModel)
    }
}

SomeScreenContent.swift

struct SomeScreenContent: View {
    @EnvironmentObject var viewModel: SomeViewModel
    
    @StateObject private var subscriptionBag = SubscriptionBag()
    
    var body: some View {
         TextField(placeholderText, text: viewModel.binding(\.text, to: subscriptionBag))
    }
}