user25917
user25917

Reputation: 967

Set segment equal width for SwiftUI Picker with SegmentedPickerStyle

Using the SegmentedPickerStyle style Picker could make the control looks like UISegmentedControl. But I wonder how to adjust the segment width in the picker. For examle, the picker in the image has a different width for text.

Is there a way to make the segments the same width in the SwiftUI?

    Picker(selection: $store.utility.saliencyType, label: EmptyView()) {
        ForEach(Store.Utility.SaliencyType.allCases, id: \.self) { saliencyType in
            Text(saliencyType.text)
                .tag(saliencyType)
        }
    }.pickerStyle(SegmentedPickerStyle())

enter image description here

Upvotes: 1

Views: 1358

Answers (3)

tiritea
tiritea

Reputation: 1279

...For examle, the picker in the image has a different width for text.

In case you arrive here seeking for iOS SwiftUI SegmentedPickerStyle solution... I've found the iOS SwiftUI .pickerStyle(SegmentedPickerStyle()) will conform to global UISegmentedControl.appearance() settings, so I've used the following to successfully apportion the width of each segment:

UISegmentedControl.appearance().apportionsSegmentWidthsByContent = true

enter image description here

This is particularly useful if, for example, you want to support Dynamic Type fonts in your app, which can otherwise cause segments with longer names to blow out and get truncated. [aside: I also use this trick to change the SwiftUI segmented picker's font size! see https://stackoverflow.com/a/71834578/3936065]

Upvotes: 3

Klajd Deda
Klajd Deda

Reputation: 375

Ah the reach down to AppKit method.

Very clever indeed. However this is not working for me, Monteray 12.3

Went to debug further using Xcode's Visual Debugger and I can see the NSPickerConfigurator class in the view hierarchy but no NSSegmetedControl.

It appears as if apple is clearing up NSViews from the hierarchy.

Time to think pure swiftui.

Upvotes: 0

Asperi
Asperi

Reputation: 257493

This is default macOS NSSegmetedControl behavirour

@property NSSegmentDistribution segmentDistribution API_AVAILABLE(macos(10.13)); 
// Defaults to NSSegmentDistributionFill on 10.13, older systems will continue to behave similarly to NSSegmentDistributionFit

Update: here is workaround, based on finding NSSegmentedControl in run-time view hierarchy.

demo

Disclaimer: Actually it is safe, ie. no crash in run-time, but can stop working in future returning to default behaviour.

So, the idea is to inject NSView via representable into view hierarchy above (!!) Picker, as

Picker(selection: $store.utility.saliencyType, label: EmptyView()) {
        ForEach(Store.Utility.SaliencyType.allCases, id: \.self) { saliencyType in
            Text(saliencyType.text)
                .tag(saliencyType)
        }
    }
    .overlay(NSPickerConfigurator {                // << here !!
        $0.segmentDistribution = .fillEqually // change style !!
    })
    .pickerStyle(SegmentedPickerStyle())

and configurator itself

struct NSPickerConfigurator: NSViewRepresentable {
    var configure: (NSSegmentedControl) -> Void

    func makeNSView(context: Context) -> NSView {
        let view = NSView()
        DispatchQueue.main.async {
            if let holder = view.superview?.superview {
                let subviews = holder.subviews
                if let nsSegmented = subviews.first?.subviews.first as? NSSegmentedControl {
                    self.configure(nsSegmented)
                }
            }
        }
        return view
    }

    func updateNSView(_ nsView: NSView, context: Context) {
    }
}

Upvotes: 1

Related Questions