5

I have a 3-part picker, and I'm trying to make the values of one Picker to be based on the value of another. Specifically adding/removing the s on the end of "Days","Weeks",etc. I have read a similar post (here) on this type of situation, but the proposed Apple solution for IOS 14+ deployments is not working. Given that the other question focuses primarily on pre-14 solutions, I thought starting a new question would be more helpful.

Can anyone shed any light on why the .onChange is never getting called? I set a breakpoint there, and it is never called when the middle wheels value change between 1 and any other value as it should.

The unconventional init is just so I could encapsulate this code removed from a larger project. Also, I have the .id for the 3rd picker commented out in the code below, but can un-comment if the only problem remaining is for the 3rd picker to update on the change.

enter image description here

import SwiftUI

enum EveryType:String, Codable, CaseIterable, Identifiable {

    case every="Every"
    case onceIn="Once in"

    var id: EveryType {self}
    var description:String {
        get {
            return self.rawValue
        }
    }
}

enum EveryInterval:String, Codable, CaseIterable, Identifiable {
    case days = "Day"
    case weeks = "Week"
    case months = "Month"
    case years = "Year"

    var id: EveryInterval {self}
    var description:String {
        get {
            return self.rawValue
        }
    }
}

struct EventItem {
    var everyType:EveryType = .onceIn
    var everyInterval:EveryInterval = .days
    var everyNumber:Int = Int.random(in:1...3)
}

struct ContentView: View {

    init(eventItem:Binding<EventItem> = .constant(EventItem())) {
        _eventItem = eventItem
    }

    @Binding var eventItem:EventItem 
    @State var intervalId:UUID = UUID()

    var body: some View {
        GeometryReader { geometry in
            HStack {
                Picker("", selection: self.$eventItem.everyType) {
                    ForEach(EveryType.allCases)
                    { type in Text(type.description)
                    }
                }
                .pickerStyle(WheelPickerStyle())
                .frame(width: geometry.size.width * 0.3, height:100)
                .compositingGroup()
                .padding(0)
                .clipped()

                Picker("", selection: self.$eventItem.everyNumber
                ) {
                    ForEach(1..<180, id: \.self) { number in
                        Text(String(number)).tag(number)
                    }
                }
                //The purpase of the == 1 below is to only fire if the
                // everyNumber values changes between being a 1 and
                // any other value.
                .onChange(of: self.eventItem.everyNumber == 1) { _ in
                    intervalId = UUID() //Why won't this ever happen?
                }
                .pickerStyle(WheelPickerStyle())
                .frame(width: geometry.size.width * 0.25, height:100)
                .compositingGroup()
                .padding(0)
                .clipped()

                Picker("", selection: self.$eventItem.everyInterval) {
                    ForEach(EveryInterval.allCases) { interval in
                            Text("\(interval.description)\(self.eventItem.everyNumber == 1 ? "" : "s")")
                    }
                }
                .pickerStyle(WheelPickerStyle())
                .frame(width: geometry.size.width * 0.4, height:100)
                .compositingGroup()
                .clipped()
                //.id(self.intervalId)
            }
        }
        .frame(height:100)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView(eventItem: .constant(EventItem()))
    }
}
cdeerinck
  • 688
  • 6
  • 17

3 Answers3

2

For Picker, its item data type must conform Identifiable and we must pass a property of item into "tag" modifier as "id" to let Picker trigger selection and return that property in Binding variable with selection.

For example :

Picker(selection: $selected, label: Text("")){
            
            ForEach(data){item in //data's item type must conform Identifiable
                
                HStack{
                    
                    //item view
                    
                
                }
                .tag(item.property)
                
            }
            
        }
        
        .onChange(of: selected, perform: { value in
            
            //handle value of selected here (selected = item.property when user change selection)
            
        })

//omg! I spent whole one day to find out this

1

Try the following

.onChange(of: self.eventItem.everyNumber) { newValue in
    if newValue  == 1 {
       intervalId = UUID()
    }
}

but it might also depend on how do you use this view, because with .constant binding nothing will change ever.

Asperi
  • 228,894
  • 20
  • 464
  • 690
  • My problem is that I was trying to debug it in preview. As you commented, the preview is generated with a .constant() around the provided eventItem, and so onChange was not firing. – cdeerinck Nov 10 '20 at 17:25
1

The answer by Thang Dang, above, turned out to be very helpful to me. I did not know how to conform my tag to Identifiable, but changed my tags from tag(1) to a string, as in the SwiftUI code below. The tag with a mere number in it caused nothing to happen when the Picker was set to Icosahedron (my breakpoint on setShape was never triggered), but the other three caused the correct shape to be passed in to setShape.

// set the current Shape

func setShape(value: String) { print(value) }

@State var shapeSelected = "Cube"

    VStack {
         Picker(selection: $shapeSelected, label: Text("$\(shapeSelected)")) {
             Text("Cube").tag("Cube")
             Text("Simplex").tag("Simplex")
             Text("Pentagon (3D)").tag("Pentagon")
             Text("Icosahedron").tag(1)
         }.onChange(of: shapeSelected, perform: { tag in
             setShape(value: "\(tag)")
            })
      }
George D Girton
  • 763
  • 10
  • 15