I'm curious, does anyone know how to use
FocusState
from within
Observable
types?
From what I can find, @FocusState
can only be applied to properties on views. But I would like to push all state and business logic from my view to a dedicated Observable
type, including validation and focus setting.
I have written some code to show what I'm talking about. This is the version where state is stored in view-local
State
properties:
struct StateVersion: View {
@State private var name = ""
@State private var answer = ""
@State private var showAlert = false
@State private var alertMessage = ""
@FocusState var focus: Field?
enum Field: Hashable, Codable {
case name, answer
}
func validateForm() -> Bool {
if name == "" {
focus = .name
alertMessage = "Please enter your name."
showAlert = true
return false
} else if answer == "" {
focus = .answer
alertMessage = "Please give your answer."
showAlert = true
return false
} else if answer != "Rush" {
focus = .answer
alertMessage = "Incorrect! Try again!\n(Hint: It's not Metallica)."
showAlert = true
return false
} else {
return true
}
}
var body: some View {
Form {
TextField("Name", text: $name)
.focused($focus, equals: .name)
Section("Best Metal Band In the World?") {
TextField("Band Name", text: $answer)
.focused($focus, equals: .answer)
}
Button("OK") {
if !validateForm() {
return
} else {
alertMessage = "Rock on, \(name)!\nYou are correct!"
showAlert = true
}
}
.alert(alertMessage, isPresented: $showAlert) {
Button("OK") {}
}
}
}
}
And here is the version where I've pushed everything but FocusState
into an Observable
type:
@Observable class FormState {
var name = ""
var answer = ""
var showAlert = false
var alertMessage = ""
}
struct ObservableVersion: View {
@Environment(FormState.self) var state
@FocusState var focus: Field?
enum Field: Hashable, Codable {
case name, answer
}
func validateForm() -> Bool {
if state.name == "" {
focus = .name
state.alertMessage = "Please enter your name."
state.showAlert = true
return false
} else if state.answer == "" {
focus = .answer
state.alertMessage = "Please give your answer."
state.showAlert = true
return false
} else if state.answer != "Rush" {
focus = .answer
state.alertMessage = "Incorrect! Try again!\n(Hint: It's not Metallica)."
state.showAlert = true
return false
} else {
return true
}
}
var body: some View {
Form {
@Bindable var state = state
TextField("Name", text: $state.name)
.focused($focus, equals: .name)
Section("Best Metal Band In the World?") {
TextField("Band Name", text: $state.answer)
.focused($focus, equals: .answer)
}
Button("OK") {
if !validateForm() {
return
} else {
state.alertMessage = "Rock on, \(state.name)!\nYou are correct!"
state.showAlert = true
}
}
.alert(state.alertMessage, isPresented: $state.showAlert) {
Button("OK") {}
}
}
}
}
#Preview {
ObservableVersion()
.environment(FormState())
}
I would like to move validateForm
from ObservableVersion
to FormState
, as might seem sensible. I haven't been able to think of anything (eg using
FocusState.Binding
to overcome this.
Does anyone have any ideas? Ideally, I'm looking for a version where FormState
is set up as an environmentObject, as I have done in the example, but other solutions are also good!
I'm curious, does anyone know how to use
FocusState
from within
Observable
types?
From what I can find, @FocusState
can only be applied to properties on views. But I would like to push all state and business logic from my view to a dedicated Observable
type, including validation and focus setting.
I have written some code to show what I'm talking about. This is the version where state is stored in view-local
State
properties:
struct StateVersion: View {
@State private var name = ""
@State private var answer = ""
@State private var showAlert = false
@State private var alertMessage = ""
@FocusState var focus: Field?
enum Field: Hashable, Codable {
case name, answer
}
func validateForm() -> Bool {
if name == "" {
focus = .name
alertMessage = "Please enter your name."
showAlert = true
return false
} else if answer == "" {
focus = .answer
alertMessage = "Please give your answer."
showAlert = true
return false
} else if answer != "Rush" {
focus = .answer
alertMessage = "Incorrect! Try again!\n(Hint: It's not Metallica)."
showAlert = true
return false
} else {
return true
}
}
var body: some View {
Form {
TextField("Name", text: $name)
.focused($focus, equals: .name)
Section("Best Metal Band In the World?") {
TextField("Band Name", text: $answer)
.focused($focus, equals: .answer)
}
Button("OK") {
if !validateForm() {
return
} else {
alertMessage = "Rock on, \(name)!\nYou are correct!"
showAlert = true
}
}
.alert(alertMessage, isPresented: $showAlert) {
Button("OK") {}
}
}
}
}
And here is the version where I've pushed everything but FocusState
into an Observable
type:
@Observable class FormState {
var name = ""
var answer = ""
var showAlert = false
var alertMessage = ""
}
struct ObservableVersion: View {
@Environment(FormState.self) var state
@FocusState var focus: Field?
enum Field: Hashable, Codable {
case name, answer
}
func validateForm() -> Bool {
if state.name == "" {
focus = .name
state.alertMessage = "Please enter your name."
state.showAlert = true
return false
} else if state.answer == "" {
focus = .answer
state.alertMessage = "Please give your answer."
state.showAlert = true
return false
} else if state.answer != "Rush" {
focus = .answer
state.alertMessage = "Incorrect! Try again!\n(Hint: It's not Metallica)."
state.showAlert = true
return false
} else {
return true
}
}
var body: some View {
Form {
@Bindable var state = state
TextField("Name", text: $state.name)
.focused($focus, equals: .name)
Section("Best Metal Band In the World?") {
TextField("Band Name", text: $state.answer)
.focused($focus, equals: .answer)
}
Button("OK") {
if !validateForm() {
return
} else {
state.alertMessage = "Rock on, \(state.name)!\nYou are correct!"
state.showAlert = true
}
}
.alert(state.alertMessage, isPresented: $state.showAlert) {
Button("OK") {}
}
}
}
}
#Preview {
ObservableVersion()
.environment(FormState())
}
I would like to move validateForm
from ObservableVersion
to FormState
, as might seem sensible. I haven't been able to think of anything (eg using
FocusState.Binding
to overcome this.
Does anyone have any ideas? Ideally, I'm looking for a version where FormState
is set up as an environmentObject, as I have done in the example, but other solutions are also good!
- What would be the goal or benefits of what you're trying to achieve? – Andrei G. Commented Feb 16 at 4:07
- It will allow me to reach into the Form state from somewhere other than the current view (specifically a child view). My actual situation is complicated, but let's say there's a parent form with a child "Wizard" form (using Windows parlance