Multi-Component Picker (UIPickerView) in SwiftUI
Updated answer in pure SwiftUI
- in this example the data is of type String
.
Tested on Xcode 11.1 - may not work on previous versions.
struct MultiPicker: View {
typealias Label = String
typealias Entry = String
let data: [ (Label, [Entry]) ]
@Binding var selection: [Entry]
var body: some View {
GeometryReader { geometry in
HStack {
ForEach(0..<self.data.count) { column in
Picker(self.data[column].0, selection: self.$selection[column]) {
ForEach(0..<self.data[column].1.count) { row in
Text(verbatim: self.data[column].1[row])
.tag(self.data[column].1[row])
}
}
.pickerStyle(WheelPickerStyle())
.frame(width: geometry.size.width / CGFloat(self.data.count), height: geometry.size.height)
.clipped()
}
}
}
}
}
Demo:
struct ContentView: View {
@State var data: [(String, [String])] = [
("One", Array(0...10).map { "\($0)" }),
("Two", Array(20...40).map { "\($0)" }),
("Three", Array(100...200).map { "\($0)" })
]
@State var selection: [String] = [0, 20, 100].map { "\($0)" }
var body: some View {
VStack(alignment: .center) {
Text(verbatim: "Selection: \(selection)")
MultiPicker(data: data, selection: $selection).frame(height: 300)
}
}
}
Result:
Here's an adaptation of the solutions above, using the UIKit picker:
import SwiftUI
struct PickerView: UIViewRepresentable {
var data: [[String]]
@Binding var selections: [Int]
//makeCoordinator()
func makeCoordinator() -> PickerView.Coordinator {
Coordinator(self)
}
//makeUIView(context:)
func makeUIView(context: UIViewRepresentableContext<PickerView>) -> UIPickerView {
let picker = UIPickerView(frame: .zero)
picker.dataSource = context.coordinator
picker.delegate = context.coordinator
return picker
}
//updateUIView(_:context:)
func updateUIView(_ view: UIPickerView, context: UIViewRepresentableContext<PickerView>) {
for i in 0...(self.selections.count - 1) {
view.selectRow(self.selections[i], inComponent: i, animated: false)
}
}
class Coordinator: NSObject, UIPickerViewDataSource, UIPickerViewDelegate {
var parent: PickerView
//init(_:)
init(_ pickerView: PickerView) {
self.parent = pickerView
}
//numberOfComponents(in:)
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return self.parent.data.count
}
//pickerView(_:numberOfRowsInComponent:)
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return self.parent.data[component].count
}
//pickerView(_:titleForRow:forComponent:)
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return self.parent.data[component][row]
}
//pickerView(_:didSelectRow:inComponent:)
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
self.parent.selections[component] = row
}
}
}
import SwiftUI
struct ContentView: View {
private let data: [[String]] = [
Array(0...10).map { "\($0)" },
Array(20...40).map { "\($0)" },
Array(100...200).map { "\($0)" }
]
@State private var selections: [Int] = [5, 10, 50]
var body: some View {
VStack {
PickerView(data: self.data, selections: self.$selections)
Text("\(self.data[0][self.selections[0]]) \(self.data[1][self.selections[1]]) \(self.data[2][self.selections[2]])")
} //VStack
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The easiest way zto do this is creating a wrapped UI View using a UIDatePicker
with datePickerMode
set to .countDownTimer
.
Paste the code below into a new SwiftUI view file called "TimeDurationPicker". The picker updates duration
with the value of countDownDuration
in DatePicker
.
You can preview the picker on the Canvas.
struct TimeDurationPicker: UIViewRepresentable {
typealias UIViewType = UIDatePicker
@Binding var duration: TimeInterval
func makeUIView(context: Context) -> UIDatePicker {
let timeDurationPicker = UIDatePicker()
timeDurationPicker.datePickerMode = .countDownTimer
timeDurationPicker.addTarget(context.coordinator, action: #selector(Coordinator.changed(_:)), for: .valueChanged)
return timeDurationPicker
}
func updateUIView(_ uiView: UIDatePicker, context: Context) {
uiView.countDownDuration = duration
}
func makeCoordinator() -> TimeDurationPicker.Coordinator {
Coordinator(duration: $duration)
}
class Coordinator: NSObject {
private var duration: Binding<TimeInterval>
init(duration: Binding<TimeInterval>) {
self.duration = duration
}
@objc func changed(_ sender: UIDatePicker) {
self.duration.wrappedValue = sender.countDownDuration
}
}
}
struct TimeDurationPicker_Previews: PreviewProvider {
static var previews: some View {
TimeDurationPicker(duration: .constant(60.0 * 30.0))
}
}