2

I am having a strange issue with an @State var not updating an iOS SwiftUI view. I have an edit screen for themes for a small game with a NavigationView with a list of game themes. When in edit mode and I select one of these themes, I open up an editor view, passing the theme as a binding to the editor view struct. In my editor view I then have sections that allow the user to edit properties of the theme. I do not want to use bindings to the various theme properties in my edit fields because I do not want the changes to take effect immediately. Instead, I have created @State vars for each of these properties and then use bindings to these in the edit fields. That way, I give the user the option to either cancel without and changes taking effect, or select "Done" to assign the changes back to the theme via the binding. In order to initialise the @State vars I have an onAppear block that assign the @State vars values from the respective theme properties. The issue I am having is that when the onAppear block is executed and the vars are assigned, the relevant edit fields are not updating! Here is a cut-down version of my code:

struct EditorView: View {

    /// The current presentation mode of the view.
    @Environment(\.presentationMode) var presentationMode

    @Binding var theme: GameTheme

    @State private var name = ""
    ...
    
    var body: some View {
        NavigationView {
            Form {
                nameSection
                ...
            }
            .navigationTitle("Edit \(theme.name)")
            .toolbar {
                ToolbarItem(placement: .cancellationAction) {
                    Button("Cancel", action: cancel)
                }
                ToolbarItem(placement: .confirmationAction) {
                    Button("Done", action: saveTheme)
                        .disabled(!canSaveTheme)
                }
            }
            .onAppear {
                name = theme.name
                ...
            }
        }
        .frame(minWidth: Constants.minViewSize.width, minHeight: Constants.minViewSize.height)
    }
    
    var nameSection: some View {
        Section(header: Text("Name")) {
            TextField(LocalizedStringKey("Name"), text: $name)
        }
    }

    ...
}

So the view gets shown an on appearing, the @State var name does correctly get assigned the value from theme.name; however, this allocation does not cause an update of the view and the value of "name" is not entered into the TextField.

Interestingly, and I do not know if this is a good thing to do, if I wrap the contents of the onAppear block in a DispatchQueue.main.async, everything works fine!

i.e.

.onAppear {
    DispatchQueue.main.async {
        name = theme.name
        ...
    }
}

Does anyone have any idea as to how, within the onAppear, I can force a view refresh? Or, why the assignment to "name" does not force an update?

Thanks.

3
  • You should pass the binding variable into the init instead of onAppear. And then you can use the binding variable to set the @State variable. Commented Jul 14, 2021 at 7:16
  • Because you are changing a variable within a class GameTheme. The @State observes the object as a whole. Meaning that it will only change if it is a struct Or something like a String. If you want to observe the variables of a class you have to have it conform to ObservableObject and wrap the variables with @Published and then use @StateObject Or @ObservedObjec Commented Jul 14, 2021 at 10:02
  • I seem to have solved my problem with an init(). I created init(theme: Binding<GameTheme>) and then within the init assigned the theme via _theme = theme and then assigned the name via _name = State(initialValue: theme.name.wrappedValue). Commented Aug 5, 2021 at 6:44

2 Answers 2

0

This isn't the answer per se, but I went ahead and created a new iOS project with the following code (based on your post, but I cleaned it up a bit and came up with the missing GameTheme object myself).

It's more or less the same, and shows that your posted structure does re-render.

I'm wondering if there's more to the code we can't see in your post that could be causing this.

Are you possibly setting the name state variable anywhere else in a way that could be overriding the value on load?

import SwiftUI

@main
struct TestIOSApp: App {
    @State var gameTheme: GameTheme = GameTheme(name: "A game theme")
    var body: some Scene {
        WindowGroup {
            ContentView(theme: $gameTheme)
        }
    }
}

struct GameTheme {
    var name:String;
}

struct ContentView: View {
   
    @Binding var theme:GameTheme;
    /// The current presentation mode of the view.
    @Environment(\.presentationMode) var presentationMode
    @State private var name = "DEFAULT SHOULD NOT BE DISPLAYED"
    var body: some View {
        NavigationView {
            Form {
                nameSection
            }
            .navigationTitle("Edit \(theme.name)")
            .onAppear {
                name = theme.name
            }
        }
        .toolbar {
              ToolbarItem(placement: .cancellationAction) {
                  Button("Cancel", action: {})
              }
              ToolbarItem(placement: .confirmationAction) {
                  Button("Done", action: {})
              }
          }
        .frame(maxWidth:.infinity, maxHeight: .infinity)
    }
    
    var nameSection: some View {
        Section(header: Text("Name")) {
            TextField(LocalizedStringKey("Name"), text: $name)
        }
    }
}

enter image description here

Sign up to request clarification or add additional context in comments.

Comments

0

I seem to have solved my problem with an init(). I created init(theme: Binding<GameTheme>) and then within the init assigned the theme via _theme = theme and then assigned the name via _name = State(initialValue: theme.name.wrappedValue).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.