iosswiftswiftuipicker

Is it possible to change the color of label inside Picker?


I have an app and I want to implement an option to change the app's accent color but only with a certain selection of colors and I want them inside of a picker with a label with a circle of each color option's color

I've tried using a picker inside of a menu (because I want the label to not have the image) and applying .foregroundStyle, .tint and .background with no result and I'm starting to think it is not possible or not easy to do so. Here is my current code. Thanks.

Menu {
  Picker("", selection: vm.$selectedColor) {
    ForEach(colorOptions.allCases) { option in
      Label(option.rawValue.capitalized, systemImage: "circle.fill")
        .foregroundColor(option.color)
        .tag(option)
    }
  }
}  label: {
    Text("Select color")
}

ColorOptions:

enum colorOptions: String, CaseIterable, Identifiable {
  
  var id: String { self.rawValue }
  
  case indigo
  case red
  case green
  case blue
  case yellow
  case orange
  case purple
  
  var color: Color {
    switch self {
    case .indigo: return .indigo
    case .red: return .red
    case .green: return .green
    case .blue: return .blue
    case .yellow: return .yellow
    case .orange: return .orange
    case .purple: return .purple
    }
  }
}

Note that these are not the colors i want to use those were for testing the picker


Solution

  • I am revisiting my previous answer with this new answer, since I figured out that there is a way to change the color of the icon inside a Picker.

    I stumbled upon this when I was looking at the documentation for .foregroundStyle when using primary and secondary levels:

    .foregroundStyle(.blue, .orange)
    

    Using this format on the icon inside the picker will allow it to be colored. This is because when using this initializer, it will use the .palette rendering mode for symbol images.

    This means that we can also specify the .palette rendering mode and use the regular .foregroundStyle modifier to achieve the same effect (UPDATE: Turns out .tint is also needed for iOS 18.4):

    Picker("Select color", selection: $selectedColor) {
        ForEach(PickerColor.allCases) { option in
            Label {
                Text(option.rawValue.capitalized)
            } icon: {
                Image(systemName: "circle.fill")
                    .symbolRenderingMode(.palette) // <- specify .palette rendering mode
                    .foregroundStyle(option.color) // <- use .foregroundStyle as usual
                                    .tint(option.color) // required for iOS 18.4
            }
            .tag(option)
        }
    }
    

    To have the picker's label also be colored using the selected color, use the .tint modifier on the picker:

    .tint(selectedColor.color)
    

    Here's the complete code to try:

    import SwiftUI
    
    enum PickerColor: String, CaseIterable, Identifiable {
        
        var id: String { self.rawValue }
        
        case indigo
        case red
        case green
        case blue
        case yellow
        case orange
        case purple
        
        var color: Color {
            switch self {
                case .indigo: return .indigo
                case .red: return .red
                case .green: return .green
                case .blue: return .blue
                case .yellow: return .yellow
                case .orange: return .orange
                case .purple: return .purple
            }
        }
    }
    
    struct PickerIconColor: View {
        
        //State values
        @State private var showMenu = false
        @State private var selectedColor: PickerColor = .indigo
        
        //Body
        var body: some View {
            
                Picker("Select color", selection: $selectedColor) {
                    ForEach(PickerColor.allCases) { option in
                        Label {
                            Text(option.rawValue.capitalized)
                        } icon: {
                            Image(systemName: "circle.fill")
                                .symbolRenderingMode(.palette)
    .foregroundStyle(option.color)
    .tint(option.color)
                        }
                            .tag(option)
                    }
                }
                .tint(selectedColor.color)
        }
    }
    
    
    #Preview("PickerMenuColorAlt") {
        PickerIconColor()
    }
    

    enter image description here

    BONUS: For more control over the selected option styling, wrap the picker in a Menu with a custom label:

    Menu {
        Picker("Select color", selection: $selectedColor) {
            ForEach(PickerColor.allCases) { option in
                Label {
                    Text(option.rawValue.capitalized)
                } icon: {
                    Image(systemName: "circle.fill")
                        .symbolRenderingMode(.palette)
                        .foregroundStyle(option.color)
                                            .tint(option.color)
                }
                .tag(option)
            }
        }
    } label : {
        HStack {
            Label {
                Text(selectedColor.rawValue.capitalized)
            } icon: {
                Image(systemName: "circle.fill")
                    .foregroundStyle(selectedColor.color)
                    .imageScale(.small)
            }
            
            //Chevron icon to make it look like the picker
            Image(systemName: "chevron.up.chevron.down")
                .imageScale(.small)
        }
    }
    .tint(selectedColor.color)
    

    enter image description here

    For even more control, use an HStack instead of a Label to set spacing between the icons and text.

    UPDATE iOS 18.4:

    For some strange reason, after updating to iOS 18.4, all icons in all menus turned the same color blue on device, while still showing multi colored in XCode Previews. After some poking, I figured out they now require both a .foregroundStyle and a .tint (set to the same color) to restore the individual color of each icon. I don't know if this is intentional or a bug.

    I updated the code above to reflect this.

    Looks like the change could be related to this from the iOS 18.4 release notes: