macosswiftuipicker

MacOS, SwiftUI - styling Picker button (menu type)


Is it possible to style a Picker of .pickerStyle(.menu) ? I want to change color of up-down arrows on the picker button and background color behind them.


Solution

  • To change the styling of the picker button you can wrap the picker with a Menu. Then:

    The label of the menu button can show the current selection. However, if the choices have different widths then the button width will keep changing.

    Here is an example to show it all working. The styled button tries to emulate the appearance of the native picker, but with a different icon:

    struct ContentView: View {
        let choices = ["Zero", "One", "Two", "Three", "Four", "Five"]
        @State private var selection = "Zero"
    
        var body: some View {
            VStack(alignment: .trailing) {
                Picker("Native picker", selection: $selection) {
                    ForEach(choices, id: \.self) { choice in
                        Text(choice).tag(choice)
                    }
                }
                .pickerStyle(.menu)
                .fixedSize()
    
                LabeledContent("Styled picker") {
                    Menu(selection) {
                        Picker("", selection: $selection) {
                            ForEach(choices, id: \.self) { choice in
                                Text(choice).tag(choice)
                            }
                        }
                        .pickerStyle(.inline)
                        .labelsHidden()
                    }
                    .buttonStyle(MenuButton(choices: choices))
                }
                .fixedSize()
            }
        }
    }
    
    struct MenuButton: ButtonStyle {
        let choices: [String]
    
        func makeBody(configuration: Configuration) -> some View {
            HStack {
                footprint
                    .overlay(alignment: .leading) {
                        configuration.label
                    }
                    .padding(.leading, 6)
                Image(systemName: "atom")
                    .imageScale(.small)
                    .padding(1)
                    .foregroundStyle(.white)
                    .background(.purple, in: .rect(cornerRadius: 4))
            }
            .padding(2)
            .background {
                RoundedRectangle(cornerRadius: 5)
                    .fill(.background)
                    .shadow(radius: 1, y: 0.5)
            }
        }
    
        private var footprint: some View {
            ZStack {
                ForEach(choices, id: \.self) { choice in
                    Text(choice)
                }
            }
            .hidden()
        }
    }
    

    Animation