Jacksonsox
Jacksonsox

Reputation: 1233

SwiftUI2, Picker .onChange firing twice need value before change and after

I'm having an issue similar to the following post .onReceive firing twice.

I have a picker that fires .onChange twice. I am using a model data Environment object for the picker.

Is there a way for me to get the before state such that I can compare if the new_haveCount value is truely changing? Or better yet, to prevent the double fire in the first place?

@EnvironmentObject var modelData: ModelData

specifics and specificsFirebase are both structures.

Picker code

            Picker(selection: $modelData.figureArray[figureIndex].specifics.specificsFirebase.new_haveCount,
                   label: Text("  \(modelData.figureArray[figureIndex].specifics.specificsFirebase.new_haveCount) ")) {
                ForEach(0 ..< 20) {
                    Text("\(kSpecificType_Labels[1]) \($0) \(kNewText.lowercased())")
                }
            }
            .onChange(of: modelData.figureArray[figureIndex].specifics.specificsFirebase.new_haveCount) { _ in
                saveSpecifics()
            }

From the apple dev page, .onChange seems to have a before and ofter property.

struct PlayerView : View {
var episode: Episode
@State private var playState: PlayState = .paused

var body: some View {
    VStack {
        Text(episode.title)
        Text(episode.showTitle)
        PlayButton(playState: $playState)
    }
    .onChange(of: playState) { [playState] newState in
        model.playStateDidChange(from: playState, to: newState)
    }
}
}

Full View if it helps

import SwiftUI
import Firebase

struct SpecificsEntryView: View {
@EnvironmentObject var modelData: ModelData

let figure: Figure

var figureIndex: Int {
    modelData.figureArray.firstIndex(where: { $0.id == figure.id })!
}

var body: some View {
    HStack(spacing: 4) {
        // new labels
        VStack(alignment: .leading, spacing: 4) {
            ForEach(kSpecificType_Labels, id: \.self) { label in
                Text(label)
                    .frame(maxHeight: .infinity)
                    .padding(.bottom, 2)
                Divider()
            }
        }
        
        // new values
        VStack(alignment: .center, spacing: 4) {
            Text(kNewText)
                .frame(maxHeight: .infinity)
                .padding(.bottom, 2)
            Divider()
            Picker(selection: $modelData.figureArray[figureIndex].specifics.specificsFirebase.new_haveCount,
                   label: Text("  \(modelData.figureArray[figureIndex].specifics.specificsFirebase.new_haveCount) ")) {
                ForEach(0 ..< 20) {
                    Text("\(kSpecificType_Labels[1]) \($0) \(kNewText.lowercased())")
                }
            }
            .onChange(of: modelData.figureArray[figureIndex].specifics.specificsFirebase.new_haveCount) { _ in
                saveSpecifics()
            }
            .frame(maxHeight: .infinity)
            .padding(.bottom, 2)
            .pickerStyle(MenuPickerStyle())
            Divider()
            Picker(selection: $modelData.figureArray[figureIndex].specifics.specificsFirebase.new_wantCount,
                   label: Text("  \(modelData.figureArray[figureIndex].specifics.specificsFirebase.new_wantCount)  ")) {
                ForEach(0 ..< 20) {
                    Text("\(kSpecificType_Labels[2]) \($0) \(kNewText.lowercased())")
                }
            }
            .onChange(of: modelData.figureArray[figureIndex].specifics.specificsFirebase.new_wantCount) { _ in
                saveSpecifics()
            }
            .frame(maxHeight: .infinity)
            .padding(.bottom, 2)
            .pickerStyle(MenuPickerStyle())
            Divider()
            Picker(selection: $modelData.figureArray[figureIndex].specifics.specificsFirebase.new_sellCount,
                   label: Text("  \(modelData.figureArray[figureIndex].specifics.specificsFirebase.new_sellCount)  ")) {
                ForEach(0 ..< 20) {
                    Text("\(kSpecificType_Labels[3]) \($0) \(kNewText.lowercased())")
                }
            }
            .onChange(of: modelData.figureArray[figureIndex].specifics.specificsFirebase.new_sellCount) { _ in
                saveSpecifics()
            }
            .frame(maxHeight: .infinity)
            .padding(.bottom, 2)
            .pickerStyle(MenuPickerStyle())
            Divider()
            Picker(selection: $modelData.figureArray[figureIndex].specifics.specificsFirebase.new_orderCount,
                   label: Text("  \(modelData.figureArray[figureIndex].specifics.specificsFirebase.new_orderCount)  ")) {
                ForEach(0 ..< 20) {
                    Text("\(kSpecificType_Labels[4]) \($0) \(kNewText.lowercased())")
                }
            }
            .onChange(of: modelData.figureArray[figureIndex].specifics.specificsFirebase.new_orderCount) { _ in
                saveSpecifics()
            }
            .frame(maxHeight: .infinity)
            .padding(.bottom, 2)
            .pickerStyle(MenuPickerStyle())
            Divider()
        } // end new vstack

        Divider() // vertical

        // loose values
        VStack(alignment: .center, spacing: 4) {
            Text(kLooseText)
                .frame(maxHeight: .infinity)
                .padding(.bottom, 2)
            Divider()
            Picker(selection: $modelData.figureArray[figureIndex].specifics.specificsFirebase.loose_haveCount,
                   label: Text("  \(modelData.figureArray[figureIndex].specifics.specificsFirebase.loose_haveCount)  ")) {
                ForEach(0 ..< 20) {
                    Text("\(kSpecificType_Labels[1]) \($0) \(kNewText.lowercased())")
                }
            }
            .onChange(of: modelData.figureArray[figureIndex].specifics.specificsFirebase.loose_haveCount) { _ in
                saveSpecifics()
            }
            .frame(maxHeight: .infinity)
            .padding(.bottom, 2)
            .pickerStyle(MenuPickerStyle())
            Divider()
            Picker(selection: $modelData.figureArray[figureIndex].specifics.specificsFirebase.loose_wantCount,
                   label: Text("  \(modelData.figureArray[figureIndex].specifics.specificsFirebase.loose_wantCount)  ")) {
                ForEach(0 ..< 20) {
                    Text("\(kSpecificType_Labels[2]) \($0) \(kNewText.lowercased())")
                }
            }
            .onChange(of: modelData.figureArray[figureIndex].specifics.specificsFirebase.loose_wantCount) { newVal in
                print("\(modelData.figureArray[figureIndex].specifics.specificsFirebase.loose_wantCount) to \(newVal)")
                saveSpecifics()
            }
            .frame(maxHeight: .infinity)
            .padding(.bottom, 2)
            .pickerStyle(MenuPickerStyle())
            Divider()
            Picker(selection: $modelData.figureArray[figureIndex].specifics.specificsFirebase.loose_sellCount,
                   label: Text("  \(modelData.figureArray[figureIndex].specifics.specificsFirebase.loose_sellCount)  ")) {
                ForEach(0 ..< 20) {
                    Text("\(kSpecificType_Labels[3]) \($0) \(kNewText.lowercased())")
                }
            }
            .onChange(of: modelData.figureArray[figureIndex].specifics.specificsFirebase.loose_sellCount) { _ in
                saveSpecifics()
            }
            .frame(maxHeight: .infinity)
            .padding(.bottom, 2)
            .pickerStyle(MenuPickerStyle())
            Divider()
            TextField("Order from", text: $modelData.figureArray[figureIndex].specifics.specificsFirebase.new_orderText,
                      onCommit: {
                        saveSpecifics()
                      })
                .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
                .padding(.bottom, 2)
                .background(Color(.systemGray5))
                .cornerRadius(4)
            Divider()
        } // end loose vstack
    } // end all hstack specifics
    .fixedSize(horizontal: false, vertical: true)
    .font(/*@START_MENU_TOKEN@*/.title/*@END_MENU_TOKEN@*/)
} // end body

// save specifics on update
func saveSpecifics() {

    // Inject Firebase authentication
    let userID = Auth.auth().currentUser?.uid
    modelData.figureArray[figureIndex].specifics.specificsFirebase.saveSpecifics(userID: userID!)
}
}

Upvotes: 3

Views: 1379

Answers (2)

jcaruso
jcaruso

Reputation: 1020

I was having a similar issue, but I was getting 4 duplicate .onChange handler calls on a DatePicker control.

I ended up changing my code to use the Binding extension from here and the duplicate calls went away https://www.hackingwithswift.com/quick-start/swiftui/how-to-run-some-code-when-state-changes-using-onchange

I'd still like to know what was causing the duplicate calls if someone has any insight.

Upvotes: 0

JWBR
JWBR

Reputation: 41

I ran into the same capture list syntax issue and fixed it by using an explicit alias for the captured value from the EnvironmentObject variable (Xcode gave a hint). Like so:

.onChange(model.someVariable) {[oldValue = model.someVariable] newValue in { ... }

But my code still causes .onChange to fire twice...

Upvotes: 2

Related Questions