Pass an @State variable to a Class so it can make changes to it

Viewed 125

The code below is what I'm trying to attempt. I want to pass the @State myName to the class, and allow changes to it when buttons are pressed. (There a whole bunch more, this is the only part I'm stuck on). When I run this, there are no errors, but it doesn't do anything. If I change it to self.helper.fieldBeingEdited = self.$myName, it gives an error. The class Helper has more code in it and it appears it has to be a class, not a struct. Is there an easy way to do this?

I should also mention that in the end, multiple textFields will use the same Helper, each sending their @State var to the Helper to be changed by the changeField().

Updated question for clarification for what I'm trying to achieve:

I want the TextField to change myName and I want to pass myName to a class that will be able to modify it (by pressing a button). But I also want to be able to pass otherName to the same helper class.

class Helper: ObservableObject {

    @Published var fieldBeingEdited: String = ""

    func changeField() {
        fieldBeingEdited = "Something else"
    }
}


struct ContentView: View {
    @State var myName = "John"
    @State var otherName = "Paul"
    var helper = Helper()
    var body: some View {
        VStack {
            TextField("Your Name", text: $myName)
                .onTapGesture {
                    self.helper.fieldBeingEdited = self.myName
            }
           TextField("Other Name", text: $otherName)
                .onTapGesture {
                    self.helper.fieldBeingEdited = self.otherName
            }
            Button(action: {
                self.helper.changeField()
            }) {
                Text("Change")
            }
        }
    }
}


2 Answers

I don't really understand what you are trying to do but SwiftUI structs should not init objects (unless property wrapped with @StateObject) and in this case you don't need an ObservableObject. When a struct is wrapped in @State it can be mutated and you should use a struct to group together all the state properties a view needs into a config as follows:

struct ContentViewConfig {
    var myName = "John"
    var otherName = "Paul"
    var fieldBeingEdited: String = ""

    mutating func changeField() {
        fieldBeingEdited = "Something else"
    }
}


struct ContentView: View {
    @State var config = ContentViewConfig()

    var body: some View {
        VStack {
            TextField("Your Name", text: $config.myName)
                .onTapGesture {
                    config.fieldBeingEdited = "Your Name"
            }
            TextField("Other Name", text: $config.otherName)
                .onTapGesture {
                    config.fieldBeingEdited = "Other Name"
            }
            Button(action: {
                config.changeField()
            }) {
                Text("Change")
            }
        }
    }
}

I think this will accomplish your (perhaps questionable) goals:

class Helper: ObservableObject {

    var fieldBeingEdited: Binding<String> = .constant("init")

    func changeField() {
        fieldBeingEdited.wrappedValue = "Something else"
    }
}


struct ContentView: View {
    @State var myName = "John"
    @State var otherName = "Paul"
    @StateObject var helper = Helper()

    var body: some View {
        VStack {
            TextField("Your Name", text: $myName)
                .onTapGesture {
                    self.helper.fieldBeingEdited = self.$myName
            }
           TextField("Other Name", text: $otherName)
                .onTapGesture {
                    self.helper.fieldBeingEdited = self.$otherName
            }
            Button(action: {
                self.helper.changeField()
            }) {
                Text("Change")
            }
        }
    }
}

Bear in mind that onTapGesture isn't the only way a text field could become active, so you might be better off using the TextField's editing callbacks to set the fieldBeingEdited.

Related