skip to Main Content

Below I have the code for my picker:

struct pickerSwitch: View {
  @ObservedObject var appState: AppState
  @State var selection: String = "Red"
  var colors = ["Red", "Blue"]

  init(appState: AppState) {
        print("Init ran again")
        self.appState = appState
        if appState.showBlueControl {
            UISegmentedControl.appearance().setTitleTextAttributes([.font : UIFont.preferredFont(forTextStyle: .headline)], for: .normal)
            UISegmentedControl.appearance().backgroundColor = .blue
        } else {
            UISegmentedControl.appearance().setTitleTextAttributes([.font : UIFont.preferredFont(forTextStyle: .headline)], for: .normal)
            UISegmentedControl.appearance().backgroundColor = .red
        }
    }

  var body: some View {
        Picker(selection: $selection, label: Text("")) {
            ForEach(colors, id: .self) {
                Text($0)
            }
        }.pickerStyle(SegmentedPickerStyle())
  }
}

Elsewhere in my code, I have a button that changes the value of ‘showBlueControl’ for the specific instance of ‘AppState.’ In my Xcode logs, I see lots of the ‘Init ran again’ log so I thought the segmented control should be changing but for some reason the change only takes places when I close the view entirely and reopen it. How can I dynamically change the SegmentedControl when there is a SwiftUI state change (w/o closing/reopening the view)?

2

Answers


  1. You just need an @State to keep track of the background color. Then you set the .background() on the picker. To change your state variable, simply use .onChange.

    struct PickerSwitch: View {
        @State var selection: String = "Red"
        var colors = ["Red", "Blue"]
        @State var backgroundColor = Color.red // This tracks your background color
        
        var body: some View {
            Picker(selection: $selection, label: Text("")) {
                ForEach(colors, id: .self) {
                    Text($0)
                }
            }
    // .onChange reacts to the change of selection
            .onChange(of: selection, perform: { value in 
                if value == "Red" {
                    backgroundColor = .red
                } else {
                    backgroundColor = .blue
                }
            })
            .pickerStyle(SegmentedPickerStyle())
            .background(backgroundColor) // Set your background color here
        }
    }
    
    Login or Signup to reply.
  2. You could use SwiftUI-Introspect. I prefer this rather than changing the .id(_:) of the view, because:

    When you change the ID of a view, you are causing it to be invalidated and then therefore recreated. It can also lead to some strange bugs such as the animation stopping weirdly, and inability to change tab mid-way through to interrupt the animation. However, with Introspect, the view doesn’t get reinitialized. The view body just gets updated when appState changes.

    Also, with Introspect the styling only affects just this one Picker – not every-single-one throughout the whole app.

    I also simplified how you deal with keeping track of the colors. Note that we bind to the selected color with $appState.switchColor.

    Code:

    struct ContentView: View {
        @StateObject private var appState = AppState()
    
        var body: some View {
            PickerSwitch(appState: appState)
        }
    }
    
    enum SwitchColor: String, CaseIterable, Identifiable {
        case red = "Red"
        case blue = "Blue"
    
        var id: String { rawValue }
    
        var color: UIColor {
            switch self {
            case .red: return .red
            case .blue: return .blue
            }
        }
    }
    
    class AppState: ObservableObject {
        @Published var switchColor: SwitchColor = .red
    }
    
    struct PickerSwitch: View {
        @ObservedObject var appState: AppState
    
        var body: some View {
            Picker("Select color", selection: $appState.switchColor) {
                ForEach(SwitchColor.allCases) { color in
                    Text(color.rawValue).tag(color)
                }
            }
            .pickerStyle(.segmented)
            .introspectSegmentedControl { segmentedControl in
                let attributes = [NSAttributedString.Key.font : UIFont.preferredFont(forTextStyle: .headline)]
                segmentedControl.setTitleTextAttributes(attributes, for: .normal)
                segmentedControl.backgroundColor = appState.switchColor.color
            }
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search