0

I have a picker that will change an attribute of an object that is bound to the view. When the picker is changed I want to update the bound object so the change is apparent in the previous view. The picker updates the label on each change but the .onChanged modifier does't fire after the first change. If changed 2 or more times the .onChanged body fires.

let categories: [String] = ["None", "Produce", "Dairy/Eggs", "Meat", "Breads", "Canned Goods", "Baking", "Frozen", "Bulk", "Snack Foods", "Spices/Seasonings", "Pasta/Rice", "Drinks", "Liquor", "Condiments"]

    //Name of recipe received from previous view
    @Binding var ingredient: Ingredient
   
    
    //ingredient variable that can be updated
    @State var category: String = ""

    var body: some View {
        VStack(alignment: .leading) {
                Text(ingredient.name)
                    .font(.title)
                    .padding(.leading, 5)
                Menu {
                    Picker("picker", selection: $category) {
                        ForEach(categories, id: \.self) {
                            Text($0)
                        }
                    }
                    .onChange(of: category, perform: { newValue in
                        print("PICKER CHANGED")
                        ingredient.category = self.category

                    })
                    .labelsHidden()
                    .pickerStyle(InlinePickerStyle())
                    
                } label: {
                    Text(category)
                        .foregroundColor(.black)
                        .padding(5)
                        .labelsHidden()
                        .clipped()
                        .mask(RoundedRectangle(cornerRadius: 20, style: .continuous))
                }
         }
    .onAppear(perform: {self.category = ingredient.category})
}

Ingredient class:

class Ingredient: Identifiable, Hashable{
    static func == (lhs: Ingredient, rhs: Ingredient) -> Bool {
        if (lhs.id == rhs.id) {return true}
        else {return false}
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
    
    public var id = UUID()
    public var name: String = ""
    public var inStock: Bool = false
    public var category: String = ""
    public var keepInStock: Bool = false
}
7
  • 1
    Just use $ingredient.category as the Picker selection Commented Apr 2, 2022 at 16:26
  • @loremipsum That works but the label is not updating. Also, suppose I want to store the change in a DB, is there any alternative to .onChanged, I was thinking .onDisappear. Commented Apr 2, 2022 at 20:17
  • 1
    Just get. rid of the @State var category it isn't needed. By connecting it all through $ingredient.category when you persist to a database it will all get updated. It is best to have a Save Button. onAppear, onChange, and onDismiss seems to be unreliable. You can try but I would not count on them Commented Apr 2, 2022 at 20:23
  • I made those changes and it works well. But the label does not update. I'm using a hacky label thing on top of the picker so that may be why there is an issue. It doesn't update when the bound ingredient object is changed. Commented Apr 3, 2022 at 18:26
  • 1
    Because ingredient is a class change it to a struct OR make it an ObservableObject wrap the variables in @Published and change @Binding var ingredient: Ingredient to @ObservedObject var ingredient: Ingredient Commented Apr 3, 2022 at 19:40

2 Answers 2

1

This issue is because SwiftUI does not know about the changes.

Ingredient is a class change it to a struct

OR

make it an ObservableObject wrap the variables in @Published and change

@Binding var ingredient: Ingredient

to

@ObservedObject var ingredient: Ingredient

class is reference type and struct is value type.

@State, @Binding, @Published see changes for value type.

@ObservedObject, @StateObject, and @EnvironmentObject are for reference. Accompanied by ObservableObject and @Published

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

Comments

0

Have you tried setting a tag modifier on the Text property inside the ForEach for the picker?

Picker("picker", selection: $category) {
    ForEach(categories, id: \.self) {
        Text($0).tag($0)
    }
}

1 Comment

Tried this but it still results in the same behavior. I am getting the right value but it is delayed by one interaction. .onChange doesn't recognize the first changed make with the picker, but the second and all subsequent changes are recognized. Its as if after detecting the first change the program breaks and then finishes firing after the next change is made.

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.