Search code examples
swiftuionchange

onChange not firing when attached to a Picker


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()))
    }
}

Solution

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