最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

swiftui - How to use FocusState with Observable? - Stack Overflow

programmeradmin2浏览0评论

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!

Share Improve this question edited Feb 16 at 18:31 Curious Je asked Feb 16 at 3:13 Curious JeCurious Je 1,3196 silver badges33 bronze badges 4
  • 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
发布评论

评论列表(0)

  1. 暂无评论