Reading time: 1 min

This recipe shows how to display multiple wheel pickers side-by-side in SwiftUI in a single component.

The end result looks like this:

preview

This solution works for SwiftUI 1+ (iOS 13+, macOS 10.15+).

Here's the full recipe:

  1. Use a GeometryReader to set the width of child Pickers so that they are evenly distributed into columns.
  2. Put a clipped modifier to each picker to modifier to hide any content that extends beyond the layout bounds of the shape.
  3. On iOS 15+, use a UIPicker extension that modifier intrinsic content size. Without this, content clipping won't work.

Check out the code for more details:

extension UIPickerView {
  open override var intrinsicContentSize: CGSize {
    CGSize(width: UIView.noIntrinsicMetric , height: 150)
  }
}

struct MultiWheelPicker<Data>: View where Data : Hashable {
  let labels: [String]
  let data: [[Data]]
  @Binding var selection: [Data]

  var body: some View {
    GeometryReader { geometry in
      HStack {
        ForEach(0..<data.count) { column in
          picker(label: labels[column],
                 columnData: data[column],
                 column: column,
                 geometry: geometry)
        }
      }
    }
  }

  @ViewBuilder private func picker(
    label: String,
    columnData: [Data],
    column: Int,
    geometry: GeometryProxy
  ) -> some View {
    Picker(label, selection: $selection[column]) {
      ForEach(0..<columnData.count) { row in
        Text(String(describing: columnData[row]))
          .tag(columnData[row])
      }
    }
    .pickerStyle(.wheel)
    .frame(width: geometry.size.width / CGFloat(self.data.count),
           height: geometry.size.height)
    .clipped()
    Spacer()
  }
}

And here's how to put it into action:

struct MultiWheelPickerTest: View {
  @State var selection = ["1", "1", "1991"]

  var body: some View {
    VStack(alignment: .center) {
      Text(verbatim: "Selection: \(selection)")
      MultiWheelPicker(labels: ["Day", "Month", "Year"],
                       data:  [
                        Array(1...31).map { "\($0)" },
                        Array(1...12).map { "\($0)" },
                        Array(1991...2023).map { "\($0)" }
                       ],
                       selection: $selection)
      .frame(width: 300, height: 300)
    }
  }
}

Next Post Previous Post