I'm trying to add a three-component Picker (UIPickerView) to a SwiftUI app (in a traditional UIKit app, the data source would return 3
from the numberOfComponents
method), but I can't find an example of this anywhere.
I've tried adding an HStack of three single-component Pickers, but the perspective is off from what it would be if they were all part of a single Picker.
Step 1: Our SwiftUI multi-component Picker basically consists of several individual Picker views arranged horizontally. Therefore, we start by creating an ordinary Picker view for our first component. To do this, we declare an appropriate State property and initialize an array that contains the set for the Picker view.
Figure 1. SwiftUI’s Picker is the standard way of working with options. It allows developers to have the capability of UIKit’s UIPickerView and UISegmentedControl without the working knowledge of how those view works. This article is part of my SwiftUI Tutorial series. In this tutorial, we’re going to tackle different ways of working with Picker.
Another way of storing options in Picker is by customizing the data types using enum. Although its complicated to understand for novice coders, it’s the best practice for sophisticated programs. Note: Swift is a type-safe programming language. This means that it encourage clarity when declaring types of values.
The options are: . inline, . menu, . segmented, and . wheel. By default, it is set to . automatic where SwifUI’s Picker automatically adapts based on the environment of Views. In our above example, it used .menu style to present the choices.
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()
}
}
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:
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))
}
}
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