Learn Courses My Dashboard

onChange with Picker not working

I am writing a program which uses a picker to update the current view. However, I can not get the .onChange() event to occur. I wrote a small example to test it and it is still not working. Can anyone see what my issue is in the code?

Here is my non-event example:

import SwiftUI

struct PickerOnChangeTestView: View
{
    @State var selectedCourse: String = "Math"
    var courses: [String] = ["Math","Biology","Civics","History","Art","Philosophy","Sociology"]
    
    var body: some View {
        
        VStack {
            
            // This Text should update when selectedCourse changes
            Text( selectedCourse )
            
            HStack {
                
                Text("Course: ")
                
                Picker("Course", selection: $selectedCourse) {
                    ForEach(0..<courses.count) {
                        index in
                        
                        Text(courses[index])
                            .tag(index)
                    }
                    .onChange(of: selectedCourse,
                              perform: {
                        _ in
                        
                        print("On Change")
                    })
                    .pickerStyle(MenuPickerStyle())
                    
                }
                
            }
            .padding(.leading)
                .frame(width: 400, height:20)
        }
    }
}

struct PickerOnChangeTestView_Previews: PreviewProvider {
    static var previews: some View {
        PickerOnChangeTestView()
    }
}

Hi Dnbowen, I don’t really know what the issue is here, but have a quite similar code that works. Maybe you can figure it out by comparison. Hope it helps…

import SwiftUI

struct YoutubeTimePicker: View {
@Binding public var seconds: Int

@State private var hourSelection = 0
@State private var minuteSelection = 0
@State private var secondSelection = 0

private let frameHeight: CGFloat = 160

var body: some View {
  
     
       
        HStack(spacing: 0) {
   
          
            Picker(selection: self.$hourSelection, label: Text("")) {
                ForEach(0..<60, id: \.self) {
                    Text("\($0 ) h")
                }
            }
            .onChange(of: self.hourSelection) { newValue in
                seconds = totalInSeconds
            }
            .frame(width: 40, alignment: .leading)
            .clipped()
            
            Text(":")
                .padding(.horizontal, 10)
            
            Picker(selection: self.$minuteSelection, label: Text("")) {
                ForEach(0..<60, id: \.self) {
                    Text("\($0 ) min")
                }
            }
            .onChange(of: self.minuteSelection) { newValue in
                seconds = totalInSeconds
            }
            .frame(width: 40, alignment: .leading)
            .clipped()
            
            Text(":")
                .padding(.horizontal, 10)
            
            Picker(selection: self.self.$secondSelection, label: Text("")) {
                ForEach(0..<60, id: \.self) {
                    Text("\($0 ) sec")
                }
            }
            .onChange(of: self.secondSelection) { newValue in
                seconds = totalInSeconds
            }
            .frame(width: 40, alignment: .leading)
            .clipped()
            
         Spacer()
        }
        .accentColor(.blue)
        
        }

var totalInSeconds: Int {
    return  hourSelection * 3600 + minuteSelection * 60 + secondSelection
}

}

@dnbowen The problem is that your Picker uses a selection value (the selectedValue property) of type String but the items inside the ForEach you use to build the Picker values has an id of type Int, so the implicit tag type of each item is an Int. You are also assigning an explicit tag of Int to each item. The type of the tag (whether implicit or explicit) has to match the type of the selection value.

So you can either change selectedValue to an Int or you can do something like one of these options:

//either this:
ForEach(0..<courses.count) { index in
    Text(courses[index]).tag(courses[index])
}

//or this:
ForEach(courses, id: \.self) { course in
    Text(course)
}