swiftmacosswiftuisymbolspicker

Increase size of Picker / drop down when displaying symbols with large font


I want to scale size of the Picker selection drop down consistently with the font size of the elements. I'm using code below to display a picker / drop down with list of symbols:

import SwiftUI

struct ContentView: View {
    @State private var pickerSelection: String = "sun.min"
    let symbols = ["sun.max.fill", "sun.min.fill", "sun.min", "figure.walk"]
    var body: some View {
        VStack {
            Picker("Icon label", selection: $pickerSelection) {
                ForEach(symbols, id: \.self) { symbolString in
                    Text(Image(systemName: symbolString))
                }
            }
            .scaledToFit()
        }
        .padding()
    }
}

Preview

sample list

Problem

I would like to increase size of the labels and make the picker labels, bigger, let's say I true to use .font(.title) to achieve that:

struct ContentView: View {
    @State private var pickerSelection: String = "sun.min"
    let symbols = ["sun.max.fill", "sun.min.fill", "sun.min", "figure.walk"]
    var body: some View {
        VStack {
            Picker("Icon label", selection: $pickerSelection) {
                ForEach(symbols, id: \.self) { symbolString in
                    Text(Image(systemName: symbolString))
                        .font(.title)
                }
            }
            .scaledToFit()
        }
        .padding()
    }
}

This looks hideous: broken ui with label

How can I properly control the size of text/icon label within the Picker/drop-down element and expand it consistently with the size of the menu items?

I'm open to alternative UI elements that would enable me to achieve the same outcome in a presentable manner - drop down list with symbols, where I can control size of the whole element and scale accordingly with the size of the symbol.


Solution

  • You could try applying a .scaleEffect to the Picker.

    VStack {
        HStack {
            Text("Icon label")
            Picker("", selection: $pickerSelection) {
                ForEach(symbols, id: \.self) { symbolString in
                    Label(symbolString, systemImage: symbolString)
                        .labelStyle(.iconOnly)
                }
            }
            .accessibilityLabel("Icon label")
            .fixedSize()
            .scaleEffect(1.5)
            .padding(.trailing, 14)
            .padding(.vertical, 5)
        }
    }
    .padding()
    

    Screenshot


    EDIT After a bit more experimentation, I found that you can get closer to a fully scaled result by using a Menu instead of a Picker:

    I also tried the technique of showing the checkmark against the selected item by switching label style, as shown in part 2 of the answer to Section header inside a Menu for a Picker not visible (it was my answer). However, in this context, the checkmark is shown on the left, so this was causing the options to go out of alignment.

    struct MyButtonStyle: ButtonStyle {
        func makeBody(configuration: Configuration) -> some View {
            HStack(spacing: 20) {
                configuration.label
                    .scaleEffect(1.5)
                    .padding(.leading, 10)
                Image(systemName: "chevron.up.chevron.down")
                    .resizable()
                    .scaledToFit()
                    .fontWeight(.bold)
                    .padding(3)
                    .frame(width: 20, height: 20)
                    .foregroundStyle(.white)
                    .background(
                        RoundedRectangle(cornerRadius: 4)
                            .fill(.blue)
                    )
            }
            .padding(4)
            .background(
                RoundedRectangle(cornerRadius: 6)
                    .fill(Color(NSColor.controlColor))
                    .shadow(radius: 2)
            )
        }
    }
    
    var body: some View {
        HStack {
            Text("Icon label")
            Menu {
                ForEach(symbols, id: \.self) { symbolString in
                    Button {
                        pickerSelection = symbolString
                    } label: {
                        Group {
                            if pickerSelection == symbolString {
                                Text(Image(systemName: symbolString)) +
                                Text("    ") +
                                Text(Image(systemName: "checkmark"))
                            } else {
                                Text(Image(systemName: symbolString))
                            }
                        }
                        .font(.title)
                    }
                }
            } label: {
                Image(systemName: pickerSelection)
            }
            .accessibilityLabel("Icon label")
            .buttonStyle(MyButtonStyle())
            .fixedSize()
        }
        .padding(.bottom, 150)
    }
    

    Screenshot