swiftxcodeswiftuididset

SwiftUI: didSet doesn't update view


In the following, reproduced example, I have the following code which makes up a single screen. There's a lot of code, but it's simple!

The intended function: The screen is filled with a plain color. Below the color, there is a picker (DisclosureGroup in this example, but I don't think it matters which type of picker is used.), and this picker lets the user switch between mode1 and mode2. The screen starts off being filled black, and once the user picks a mode, the black view should change to red using didSet on the mode variable.

The issue is mainly with the didSet of the mode variable. I know the mode is being updated, because the label (commented "shows current mode") is changing. However, the color of the view itself isn't updating, as defined in the didSet

I checked here, and I have an @State, but the issue persists.

I get no errors, and am running XCode 13.2

Code:

struct ContentView: View {
    @State var color: Color = .black               //COLOR BEING SHOWN

    @State var mode: SHModes = .mode1 {            //CURRENT MODE
        didSet {
            color = .red
        }
    }
    
    public enum SHModes {                         //MODE OPTIONS
        case mode1, mode2
    }
    
    var body: some View {
        VStack {
            color                         
            ModePicker(mode: $mode)
        }
    }
    
    
    struct ModePicker: View {
        
        @Binding var mode: SHModes               //BINDING TO CONTENTVIEW MODE

        @State var clicked = false            //the rest is irrelevant code , just changes the binding variable above
        
        var body: some View {
            DisclosureGroup(isExpanded: $clicked, content: {
                VStack {
                    if mode != .mode1 {
                        Text("Mode 1")
                            .onTapGesture {
                                mode = .mode1
                                clicked.toggle()
                            }
                    }
                    if mode != .mode2 {
                        Text("Mode 2")
                            .onTapGesture {
                                mode = .mode2
                                clicked.toggle()
                            }
                    }
                }
            }, label: {
                Text(mode == .mode1 ? "mode1" : "mode2")    //label for current mode, proves `mode` is being changed
            })
        }
    }
}

I suspect it has to do with the way I'm binding the mode value, but why isn't the color on the view updating when didSet is called?


Solution

  • Use instead .onChange(of:..., like

    var body: some View {
        VStack {
            color                         
            ModePicker(mode: $mode)
              .onChange(of: mode) { _ in // or with arg if needed
                 color = .red     // << here 
              }
        }
    }