In SwiftUI, I have the following:
func segments() -> [String] {
var toReturn = Calendar.current.veryShortWeekdaySymbols
toReturn.append("T")
return toReturn
}
Picker("Day of week?", selection: $selectedDay) {
ForEach(segments(), id: \.self) {
Text($0)
.scaledToFit()
}
}
.pickerStyle(.segmented)
.fixedSize()
This shows as:
However, if I change the toReturn.append("T")
to be a longer string such as toReturn.append("Tasks")
, then the segments become as wide as the widest segment and not actually fit its content:
How to make SwiftUI Picker's segments be actually as wide as its content and not as wide as the widest segment?
SwiftUI doesn't support this at the moment, so you'd have to drop down to UIKit, and set UISegmentedControl.apportionsSegmentWidthsByContent
to true.
If you don't want any segmented picker to have equal widths, just use the UIAppearance
APIs,
// put this in onAppear wherever you need it
UISegmentedControl.appearance().apportionsSegmentWidthsByContent = true
Otherwise, you can write your own UIViewRepresentable
. Here is an example:
struct FitWidthSegmenetedPicker<Selection: Hashable>: UIViewRepresentable {
let options: [Selection]
@Binding var selection: Selection
let titleForOption: (Selection) -> String
init(
_ options: [Selection],
selection: Binding<Selection>,
titleForOption: @escaping (Selection) -> String = { String(describing: $0) }
) {
self.options = options
self._selection = selection
self.titleForOption = titleForOption
}
func makeUIView(context: Context) -> UISegmentedControl {
let segmentedControl = UISegmentedControl()
segmentedControl.apportionsSegmentWidthsByContent = true
segmentedControl.addTarget(context.coordinator, action: #selector(Coordinator.selectionChanged(_:)), for: .valueChanged)
return segmentedControl
}
func updateUIView(_ uiView: UISegmentedControl, context: Context) {
uiView.removeAllSegments()
for option in options.reversed() {
uiView.insertSegment(withTitle: titleForOption(option), at: 0, animated: false)
}
uiView.selectedSegmentIndex = options.firstIndex(of: selection) ?? 0
context.coordinator.onSelectionChanged = { index in
selection = options[index]
}
}
func makeCoordinator() -> Coordinator {
.init()
}
@MainActor
class Coordinator: NSObject {
var onSelectionChanged: ((Int) -> Void)?
@objc func selectionChanged(_ sender: UISegmentedControl) {
onSelectionChanged?(sender.selectedSegmentIndex)
}
}
}
Usage:
struct ContentView: View {
let options = ["x", "xx", "xxx", "xxxx", "xxxxxxxxxxxx"]
@State private var selection = "x"
var body: some View {
FitWidthSegmenetedPicker(options, selection: $selection)
.fixedSize()
Text(selection)
}
}