pickerswiftuihstack

SwiftUI - Placing two pickers side-by-side in HStack does not resize pickers


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?


Update

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.


Solution

  • The overlapping in the middle you can fix by adding a clipped() modifier. As for the width, I see them both exactly the same:

    enter image description here

    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)
                }
            }
        }
    }