0

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.

2
  • 1
    Please edit to focus on one thing you want to customise, especially not "etc". Commented May 22 at 10:48
  • OK, edited to be focused on just a single issue. Commented May 22 at 12:25

1 Answer 1

2

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

  • change the style of the wrapped picker to .inline
  • apply .labelsHidden(), to prevent the picker label from being shown
  • use your own ButtonStyle to style the menu button.

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.

  • The simplest way to prevent different labels from causing the button width to change is to set a fixed minimum width on the label part.
  • Alternatively, the minimum width can be determined dynamically by showing all the labels in a ZStack. This can then be hidden and used as the footprint for the actual visible label, which is shown as an overlay.

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

Sign up to request clarification or add additional context in comments.

1 Comment

Good idea ... and works!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.