Search code examples
swiftuionchangepicker

SwiftUI Picker OnChange not being called when new option is being selected


SeasonData is an array of objects of type Season

userData holds 2 [INT] lastUsedSeason -> which reference the last 3array positions of SeasonData that were accessed [3,1,0]

arrayPosition holds [0,1,2].

These are loaded from a file.

The display and selection of this picker works correctly. However I cannot figure out how to get the onChange to fire when changing a selection.

    struct UserData: Codable {
      var lastUsedSeason: [Int]
      var arrayPosition: [Int]
    
     mutating func addLastUsedSeason(index: Int) {
        if (lastUsedSeason.count > 3){
          lastUsedSeason.insert(index, at: 0)
          lastUsedSeason.remove(at: 2)
        }
      }
    }

    @main
    struct SandStats_New_App: App {
      @StateObject private var modelData = ModelData()
      
      @State private var showSeason = false
      
      
        var body: some Scene {
            WindowGroup {
              ContentView(seasonSelected: $showSeason)
                .environmentObject(modelData)
                .frame(minWidth: 700, minHeight: 300, alignment: .topLeading)
                .background(Color.red)
            }
            .commands {
              SandStatCommands(addSeasonButton: $showSeason, modelData: modelData)
            }
        }
    }

struct SandStatCommands: Commands {
  var modelData : ModelData
  @Binding var addSeasonButton: Bool
  @State var seasonSelection: Int = 0

  var body: some Commands {
    
      CommandMenu("Season"){
        
        Button("Add Season"){
          addSeasonButton = true
        }
        Picker(selection: $seasonSelection, label: Text("Selected Season")){
          ForEach(modelData.userData.arrayPosition, id:\.self) { index in
            Text(modelData.seasons[modelData.userData.getLastUsedSeason(index1: index)].seasonName()).tag(index)
          }
        }.onChange(of: seasonSelection, perform:  { value in
          print(value)
        })
        
        Button("save Index"){
          saveData(for: modelData.userData, to: "userData")
        }
      }
      
    }
}

Solution

  • For seasonSelection to work in a Picker, add a .tag() to Text(...) and make sure it is of the same type as the seasonSelection, for example (assuming index is of type Int):

    NOTE: this works for macCatalyst, for macOS only, see EDIT.

    Picker(selection: $seasonSelection, label: Text("Selected Season")){
      ForEach(userData.userData.lastUsedSeason, id:\.self) { index in
        Text(seasonData.seasons[index].seasonName()).tag(index)  // <-- here
      }
    }
     .onChange(of: seasonSelection, perform:  { _ in
      seasonData.seasons[0].testPrint()
     })
    

    EDIT: sample code that shows how to use tag with seasonSelection in .onReceive() on macOS only, and .onChange() for macCatalyst.

    import SwiftUI
    import Combine
    
    @main
    struct TestMacApp: App {
        var body: some Scene {
            WindowGroup {
                Text("demo").frame(width: 480.0, height: 320.0)
            }
            .commands {
                SandStatCommands()
            }
        }
    }
    
    struct SandStatCommands: Commands {
        @State var seasons = ["Spring","summer","autumn","winter"]  // array of values
        @State var seasonSelection: Int = 0  // <-- here initial selection
        
        var body: some Commands {
            CommandMenu("Season"){
                Button("Add Season"){
                    seasons.append(String(UUID().uuidString.prefix(6))) // for demo, add a random name
                }
                Picker(selection: $seasonSelection, label: Text("Selected Season")){
                    ForEach(0..<seasons.count, id:\.self) { index in
                        Text(seasons[index]).tag(index)  // <-- here tag
                    }
                }
               // .onChange(of: seasonSelection) { value in
               //     print("---> seasonSelection value: \(value)")
               //  }
              .onReceive(Just(seasonSelection)) { value in
                 print("---> seasonSelection value: \(value)")
              }
            }
        }
    }