Skip to content

Instantly share code, notes, and snippets.

@stakancheck
Last active June 30, 2023 13:42
Show Gist options
  • Save stakancheck/85013d753dc198db1724140155bac889 to your computer and use it in GitHub Desktop.
Save stakancheck/85013d753dc198db1724140155bac889 to your computer and use it in GitHub Desktop.
Binding for moko filed from any place
// ADD THIS CODE
struct Binder {
private static func binding<T, R>(
mutable: CMutableStateFlow<T>,
viewModel: ViewModel,
equals: @escaping (T?, T?) -> Bool,
getMapper: @escaping (T) -> R,
setMapper: @escaping (R) -> T
) -> Binding<R> {
let stateFlow: CMutableStateFlow<T> = mutable
var lastValue: T? = stateFlow.value
var disposable: DisposableHandle? = nil
disposable = stateFlow.subscribe(onCollect: { [weak viewModel] value in
if !equals(lastValue, value) {
lastValue = value
viewModel?.objectWillChange.send()
disposable?.dispose()
}
})
return Binding(
get: { getMapper(stateFlow.value!) },
set: { stateFlow.value = setMapper($0) }
)
}
private static func state<T, R>(
cstate: CStateFlow<T>,
viewModel: ViewModel,
equals: @escaping (T?, T?) -> Bool,
getMapper: @escaping (T?) -> R?
) -> State<R?> {
let stateFlow: CStateFlow<T> = cstate
var lastValue: T? = stateFlow.value
var disposable: DisposableHandle? = nil
disposable = stateFlow.subscribe(onCollect: { [weak viewModel] value in
if !equals(lastValue, value) {
lastValue = value
viewModel?.objectWillChange.send()
disposable?.dispose()
}
})
return State(wrappedValue: getMapper(cstate.value))
}
private static func bindingTextField(
_ field: Fields_flowFormField<NSString, StringDesc>,
viewModel: ViewModel,
onChange: @escaping (_: String) -> Void
) -> Binding<String> {
return binding(
mutable: field.data,
viewModel: viewModel,
equals: { $0 == $1 },
getMapper: { $0 as String },
setMapper: {
onChange($0)
return $0 as NSString
}
)
}
private static func stateErrorField(
_ field: Fields_flowFormField<NSString, StringDesc>,
viewModel: ViewModel
) -> State<String?> {
return state(
cstate: field.error,
viewModel: viewModel,
equals: { $0 === $1 },
getMapper: { $0?.localized() })
}
static func bindingField(
_ field: Fields_flowFormField<NSString, StringDesc>,
viewModel: ViewModel,
onChange: @escaping (_: String) -> Void = {_ in}
) -> MokoField {
return MokoField(
value: bindingTextField(field, viewModel: viewModel, onChange: onChange),
error: stateErrorField(field, viewModel: viewModel)
)
}
}
struct MokoField {
let value: Binding<String>
let error: State<String?>
}
// USAGE
let authViewModel: ViewModel = ...
let codeField: Fields_flowFormField<NSString, StringDesc> = ...
let code = Binder.bindingField(codeField, viewModel: authViewModel)
// SIMPLE TEXT FIELD
VStack(spacing: 8) {
TextField(SharedRes().getLabelVerificationCode().localized(), text: code.value)
.keyboardType(.decimalPad)
.textContentType(.oneTimeCode)
.submitLabel(.done)
.multilineTextAlignment(.center)
.font(.title.weight(.bold))
.onSubmit {
authViewModel.onLoginButtonClicked()
}
if let error code.error.wrappedValue {
Text(error).font(.caption).foregroundColor(.red).bold()
}
}
@kramlex
Copy link

kramlex commented Jun 13, 2023

you can also use this approach, where you need to trigger the redrawing of a specific view, and not all where the ViewModel is declared as a StateObject
icerockdev/moko-mvvm#237

@stakancheck
Copy link
Author

@kramlex this really makes sense, thank you)) In this case, I will be able not to pass the viewmodel to Binding.bind...

@stakancheck
Copy link
Author

#Update
add error state

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment