Reputation: 5997
I'm using view models for my SwiftUI app and would like to have the focus state also in the view model as the form is quite complex.
This implementation using @FocusState in the view is working as expected, but not want I want:
import Combine
import SwiftUI
struct ContentView: View {
@ObservedObject private var viewModel = ViewModel()
@FocusState private var hasFocus: Bool
var body: some View {
Form {
TextField("Text", text: $viewModel.textField)
.focused($hasFocus)
Button("Set Focus") {
hasFocus = true
}
}
}
}
class ViewModel: ObservableObject {
@Published var textField: String = ""
}
How can I put the @FocusState into the view model?
Upvotes: 15
Views: 7315
Reputation: 63667
Create a @Published var hasFocus: Bool
in the ViewModel and sync it:
Form
...
.onAppear { self.hasFocus = viewModel.hasFocus}
.onChange(of: hasFocus) { viewModel.hasFocus = $0 }
.onChange(of: viewModel.hasFocus) { hasFocus = $0 }
Upvotes: 8
Reputation: 79
I faced the same problem and ended up writing an extension that can be reused to sync both values. This way the focus can also be set from the view model side if needed.
class ViewModel: ObservableObject {
@Published var hasFocus: Bool = false
}
struct ContentView: View {
@ObservedObject private var viewModel = ViewModel()
@FocusState private var hasFocus: Bool
var body: some View {
Form {
TextField("Text", text: $viewModel.textField)
.focused($hasFocus)
}
.sync($viewModel.hasFocus, with: _hasFocus)
}
}
extension View {
func sync<T: Equatable>(_ binding: Binding<T>, with focusState: FocusState<T>) -> some View {
self
.onChange(of: binding.wrappedValue) {
focusState.wrappedValue = $0
}
.onChange(of: focusState.wrappedValue) {
binding.wrappedValue = $0
}
}
}
Upvotes: 7
Reputation: 257711
Assuming you have in ViewModel
as well
class ViewModel: ObservableObject {
@Published var hasFocus: Bool = false
...
}
you can use it like
struct ContentView: View {
@ObservedObject private var viewModel = ViewModel()
@FocusState private var hasFocus: Bool
var body: some View {
Form {
TextField("Text", text: $viewModel.textField)
.focused($hasFocus)
}
.onChange(of: hasFocus) {
viewModel.hasFocus = $0 // << write !!
}
.onAppear {
self.hasFocus = viewModel.hasFocus // << read !!
}
}
}
as well as the same from Button
if any needed.
Upvotes: 12