3

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!)
}
}
4
  • It seems like the second half of your question is answering the first half -- are you having a problem implementing the idea from the Apple developer page?
    – jnpdx
    Commented Mar 8, 2021 at 4:53
  • Shared code isn't enough to reproduce issue. Commented Mar 8, 2021 at 5:56
  • Correct, I don't know what goes in the [playState] spot .onChange(of: playState) { [playState] newState in. When I enter modelData.figureArray[figureIndex].specifics.specificsFirebase.new_haveCount I get an error "Expected 'weak', 'unowned', or no specifier in capture list"
    – Jacksonsox
    Commented Mar 8, 2021 at 23:24
  • If I do the following newVal in print("\(modelData.figureArray[figureIndex].specifics.specificsFirebase.loose_wantCount) to \(newVal)") I get 3 to 3 or whatever the new value is, printed twice.
    – Jacksonsox
    Commented Mar 8, 2021 at 23:29

2 Answers 2

2

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...

0

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.

Not the answer you're looking for? Browse other questions tagged or ask your own question.