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
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)
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()
}
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)
For even more control, use an HStack
instead of a Label
to set spacing between the icons and text.
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:
Fixed: A color set by the
tint(_:)
modifier does not override the tint color of buttons in that view’s confirmation dialogs and alerts. (138774306)