My goal is to have two pickers placed side-by-side horizontally with each picker taking up half the width of the screen. Imagine a UIPickerView
that fits the width of the screen and has two components of equal width - that's what I'm attempting to recreate in SwiftUI.
Since pickers in SwiftUI do not currently allow for multiple components, the obvious alternative to me was just to place two pickers inside an HStack
.
Here's some example code from a test project:
struct ContentView: View {
@State var selection1: Int = 0
@State var selection2: Int = 0
@State var integers: [Int] = [0, 1, 2, 3, 4, 5]
var body: some View {
HStack {
Picker(selection: self.$selection1, label: Text("Numbers")) {
ForEach(self.integers) { integer in
Text("\(integer)")
}
}
Picker(selection: self.$selection2, label: Text("Numbers")) {
ForEach(self.integers) { integer in
Text("\(integer)")
}
}
}
}
}
And here is the canvas:
SwiftUI - Pickers in HStack
The pickers do not resize to be half the width of the screen like I would expect. They retain their size and instead stretch the width of the content view, distorting the widths of other UI elements in the process (as I found out when I tried to do this in my other project).
I know that I can use UIViewRepresentable
to get the effect that I want, but SwiftUI would be much easier to use given the complexity of what I'm trying to use this for.
Is it a bug that placing two pickers inside an HStack
does not properly resize them, or do pickers in SwiftUI just have a fixed width that cannot be changed?
Using GeometryReader
, I've managed to get closer to resizing the pickers how I want, but not all the way.
Side note: you can also achieve this same imperfect result without using GeometryReader
by simply setting the frame on each picker to .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.
Here's the example code:
struct ContentView: View {
@State var selection1: Int = 0
@State var selection2: Int = 0
@State var integers: [Int] = [0, 1, 2, 3, 4, 5]
var body: some View {
GeometryReader { geometry in
HStack(spacing: 0) {
Picker(selection: self.$selection1, label: Text("Numbers")) {
ForEach(self.integers) { integer in
Text("\(integer)")
}
}
.frame(maxWidth: geometry.size.width / 2)
Picker(selection: self.$selection2, label: Text("Numbers")) {
ForEach(self.integers) { integer in
Text("\(integer)")
}
}
.frame(maxWidth: geometry.size.width / 2)
}
}
}
}
And here is the canvas:
Pickers in HStack with GeometryReader
The pickers are now closer to having the appearance that I want, but the sizing is still slightly off, and they're now overlapping each other in the middle.
The overlapping in the middle you can fix by adding a clipped() modifier. As for the width, I see them both exactly the same:
struct ContentView: View {
@State var selection1: Int = 0
@State var selection2: Int = 0
@State var integers: [Int] = [0, 1, 2, 3, 4, 5]
var body: some View {
GeometryReader { geometry in
HStack(spacing: 0) {
Picker(selection: self.$selection1, label: Text("Numbers")) {
ForEach(self.integers) { integer in
Text("\(integer)")
}
}
.frame(maxWidth: geometry.size.width / 2)
.clipped()
.border(Color.red)
Picker(selection: self.$selection2, label: Text("Numbers")) {
ForEach(self.integers) { integer in
Text("\(integer)")
}
}
.frame(maxWidth: geometry.size.width / 2)
.clipped()
.border(Color.blue)
}
}
}
}
As of iOS 15.5 (tested on simulator), Xcode 13.4 additionally to adding .clipped() you also need to add the following extension to prevent the touch area overlap issue mentioned in the comments from the other answers:
extension UIPickerView {
open override var intrinsicContentSize: CGSize {
return CGSize(width: UIView.noIntrinsicMetric , height: 150)
}
}
Just place it before the struct of the View where you're using the Picker.
Source: TommyL on the Apple forum: https://developer.apple.com/forums/thread/687986?answerId=706782022#706782022
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With