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

64 Views Asked by At

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.

1

There are 1 best solutions below

4
Benzy Neez On BEST ANSWER

You could try applying a .scaleEffect to the Picker.

  • By default, the Picker fills the full width of the screen. To prevent this, apply .fixedSize() or set a maxWidth.

  • The scale effect applies to the picker label and the menu icon too. However, it doesn't seem to apply to the items seen in the expanded menu (unfortunately).

  • To keep the picker label at the regular size, supply an empty string to Picker.init and use a leading Text element instead. You might also like to add an .accessibilityLabel to the Picker, as substitute for the missing label.

  • For the picker items, I found it worked best to use a Label with label style .iconOnly, instead of wrapping the images as Text items.

  • Use padding to compensate for the scaling having an impact on layout, if necessary.

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:

  • The menu is formed as a collection of Button items.
  • The label for each Button is formed as an image wrapped as Text, as used in your original example. This gives the larger appearance for the expanded menu that was missing from the previous solution above.
  • A checkmark can be integrated into the label to show the currently selected item.
  • The menu button itself is styled using a custom ButtonStyle. This gives you a lot of control over its appearance.

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