I'm trying to achieve something like iOS Timer App time picker control:
I've investigated the DatePicker, but it doesnt have seconds view.
I'm trying to achieve something like iOS Timer App time picker control:
I've investigated the DatePicker, but it doesnt have seconds view.
After some time investigating, it turns out that the only solution would be to write it from scratch. I'm posting it here for anyone that has same problem:
import SwiftUI
struct MultiComponentPicker<Tag: Hashable>: View {
let columns: [Column]
var selections: [Binding<Tag>]
init?(columns: [Column], selections: [Binding<Tag>]) {
guard !columns.isEmpty && columns.count == selections.count else {
return nil
}
self.columns = columns
self.selections = selections
}
var body: some View {
GeometryReader { geometry in
HStack(spacing: 0) {
ForEach(0 ..< columns.count) { index in
let column = columns[index]
ZStack(alignment: Alignment.init(horizontal: .customCenter, vertical: .center)) {
if (!column.label.isEmpty && !column.options.isEmpty) {
HStack {
Text(verbatim: column.options.last!.text)
.foregroundColor(.clear)
.alignmentGuide(.customCenter) { $0[HorizontalAlignment.center] }
Text(column.label)
}
}
Picker(column.label, selection: selections[index]) {
ForEach(column.options, id: \.tag) { option in
Text(verbatim: option.text).tag(option.tag)
}
}
.pickerStyle(WheelPickerStyle())
.frame(width: geometry.size.width / CGFloat(columns.count), height: geometry.size.height)
.clipped()
}
}
}
}
}
}
extension MultiComponentPicker {
struct Column {
struct Option {
var text: String
var tag: Tag
}
var label: String
var options: [Option]
}
}
private extension HorizontalAlignment {
enum CustomCenter: AlignmentID {
static func defaultValue(in context: ViewDimensions) -> CGFloat { context[HorizontalAlignment.center] }
}
static let customCenter = Self(CustomCenter.self)
}
// MARK: - Demo preview
struct MultiComponentPicker_Previews: PreviewProvider {
static var columns = [
MultiComponentPicker.Column(label: "h", options: Array(0...23).map { MultiComponentPicker.Column.Option(text: "\($0)", tag: $0) }),
MultiComponentPicker.Column(label: "min", options: Array(0...59).map { MultiComponentPicker.Column.Option(text: "\($0)", tag: $0) }),
MultiComponentPicker.Column(label: "sec", options: Array(0...59).map { MultiComponentPicker.Column.Option(text: "\($0)", tag: $0) }),
]
static var selection1: Int = 23
static var selection2: Int = 59
static var selection3: Int = 59
static var previews: some View {
MultiComponentPicker(columns: columns, selections: [.constant(selection1), .constant(selection2), .constant(selection3)]).frame(height: 300).previewLayout(.sizeThatFits)
}
}
And here is how it looks like:
I've made it pretty generic so you could use it for multi component pickers of any kind not just time ones.
Credits for foundation of the solution goes to Matteo Pacini.