我试图将一个三组件的Picker(UIPickerView)添加到SwiftUI应用程序中(在传统的UIKit应用程序中,数据源将从numberOfComponents方法返回3),但我在任何地方都找不到这样的例子。我尝试过添加一个包含三个单组件Picker的HStack,但是如果它们都是单个Picker的一部分,那么这个视角就不一样了。
numberOfComponents
3
ltqd579y1#
在纯SwiftUI中更新了答案-在本例中,数据类型为String。在Xcode 11.1上测试-可能无法在以前的版本上工作。
SwiftUI
String
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) } } }
结果:
piwo6bdm2#
下面是使用UIKit选择器对上述解决方案的改编:
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) } context.coordinator.parent = self // fix } 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() } }
rta7y2nd3#
最简单的方法是使用UIDatePicker创建一个 Package 的UI视图,并将datePickerMode设置为.countDownTimer。将下面的代码粘贴到名为“TimeDurationPicker”的新SwiftUI视图文件中。选择器使用DatePicker中的countDownDuration的值更新duration。您可以在画布上预览选取器。
UIDatePicker
datePickerMode
.countDownTimer
DatePicker
countDownDuration
duration
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)) } }
thigvfpy4#
即使使用.clipped(),底层拾取器也不会收缩,并且倾向于与其他拾取器重叠。我成功剪辑底层选择器视图的唯一方法是将.mask(Rectangle())添加到父容器中。别问为什么,我不知道。具有2个拾取器的工作示例(小时和分钟):
.clipped()
.mask(Rectangle())
GeometryReader { geometry in HStack(spacing: 0) { Picker("", selection: self.$hoursIndex) { ForEach(0..<13) { Text(String($0)).tag($0) } } .labelsHidden() .fixedSize(horizontal: true, vertical: true) .frame(width: geometry.size.width / 2, height: 160) .clipped() Picker("", selection: self.$minutesIndex) { ForEach(0..<12) { Text(String($0*5)).tag($0*5) } } .labelsHidden() .fixedSize(horizontal: true, vertical: true) .frame(width: geometry.size.width / 2, height: 160) .clipped() } } .frame(height: 160) .mask(Rectangle())
2exbekwf5#
就像这个论坛中的其他几个开发者一样,我发现纯SwiftUI解决方案在iOS 15或更高版本中并不好用。顺便说一下,带有UIRepresentableView的UIPickerView解决方案也不太好用。高度与需要用户输入的其他视图重叠。在苹果开发者论坛上,TommyL提出了一个非常优雅和简单的解决方案。基本上,您必须使用以下代码扩展UIPickerView:
extension UIPickerView { open override var intrinsicContentSize: CGSize { return CGSize(width: UIView.noIntrinsicMetric, height: 60) } }
您应该填写高度值,以防止Picker在垂直方向上延伸太多。这将限制拾取器的视图和触摸区域。它适用于Xcode 13和14以及iOS 15及以上版本。
4dbbbstv6#
我很喜欢woko的回答,但最终的结果在视觉上还有点不尽如人意。元素感觉有点间隔开了,所以我把几何形状。大小。宽度乘数从2改为5,并在拾取器的两侧添加了间隔物。(我还包括woko的答案中缺少的hoursIndex和mintuesIndex变量。以下是在iOS 14上使用Xcode 12在iPhone 12 Pro Max模拟器上进行的测试。
struct TimerView: View { @State private var hours = Calendar.current.component(.hour, from: Date()) @State private var minutes = Calendar.current.component(.minute, from: Date()) var body: some View { TimeEditPicker(selectedHour: $hours, selectedMinute: $minutes) } } struct TimeEditPicker: View { @Binding var selectedHour: Int @Binding var selectedMinute: Int var body: some View { GeometryReader { geometry in HStack(spacing: 0) { Spacer() Picker("", selection: self.$selectedHour) { ForEach(0..<24) { Text(String($0)).tag($0) } } .labelsHidden() .fixedSize(horizontal: true, vertical: true) .frame(width: geometry.size.width / 5, height: 160) .clipped() Picker("", selection: self.$selectedMinute) { ForEach(0..<60) { Text(String($0)).tag($0) } } .labelsHidden() .fixedSize(horizontal: true, vertical: true) .frame(width: geometry.size.width / 5, height: 160) .clipped() Spacer() } } .frame(height: 160) .mask(Rectangle()) } }
hfwmuf9z7#
在iOS15中,此解决方案:https://stackoverflow.com/a/56568715/12847995很好,但它需要在“.clipped()”修饰符之前使用修饰符“.compositingGroup()”。
5f0d552i8#
这不是很优雅,但它不涉及任何UIKit的东西移植。我知道你在回答中提到了透视法,但也许这里的几何学可以解决这个问题
GeometryReader { geometry in HStack { Picker(selection: self.$selection, label: Text("")) { ForEach(0 ..< self.data1.count) { Text(self.data1[$0]) .color(Color.white) .tag($0) } } .pickerStyle(.wheel) .fixedSize(horizontal: true, vertical: true) .frame(width: geometry.size.width / 2, height: geometry.size.height, alignment: .center) Picker(selection: self.$selection2, label: Text("")) { ForEach(0 ..< self.data2.count) { Text(self.data2[$0]) .color(Color.white) .tag($0) } } .pickerStyle(.wheel) .fixedSize(horizontal: true, vertical: true) .frame(width: geometry.size.width / 2, height: geometry.size.height, alignment: .center) } }
使用几何形状并固定大小,如下图所示,两个拾取器整齐地占据了屏幕的一半宽度。现在你只需要从两个不同的状态变量中处理选择,而不是一个,但我更喜欢这种方式,因为它可以将所有内容都保存在Swift UI中
8条答案
按热度按时间ltqd579y1#
在纯
SwiftUI
中更新了答案-在本例中,数据类型为String
。在Xcode 11.1上测试-可能无法在以前的版本上工作。
Demo:
结果:
piwo6bdm2#
下面是使用UIKit选择器对上述解决方案的改编:
rta7y2nd3#
最简单的方法是使用
UIDatePicker
创建一个 Package 的UI视图,并将datePickerMode
设置为.countDownTimer
。将下面的代码粘贴到名为“TimeDurationPicker”的新SwiftUI视图文件中。选择器使用
DatePicker
中的countDownDuration
的值更新duration
。您可以在画布上预览选取器。
thigvfpy4#
即使使用
.clipped()
,底层拾取器也不会收缩,并且倾向于与其他拾取器重叠。我成功剪辑底层选择器视图的唯一方法是将.mask(Rectangle())
添加到父容器中。别问为什么,我不知道。具有2个拾取器的工作示例(小时和分钟):
2exbekwf5#
就像这个论坛中的其他几个开发者一样,我发现纯SwiftUI解决方案在iOS 15或更高版本中并不好用。顺便说一下,带有UIRepresentableView的UIPickerView解决方案也不太好用。高度与需要用户输入的其他视图重叠。在苹果开发者论坛上,TommyL提出了一个非常优雅和简单的解决方案。基本上,您必须使用以下代码扩展UIPickerView:
您应该填写高度值,以防止Picker在垂直方向上延伸太多。这将限制拾取器的视图和触摸区域。它适用于Xcode 13和14以及iOS 15及以上版本。
4dbbbstv6#
我很喜欢woko的回答,但最终的结果在视觉上还有点不尽如人意。元素感觉有点间隔开了,所以我把几何形状。大小。宽度乘数从2改为5,并在拾取器的两侧添加了间隔物。(我还包括woko的答案中缺少的hoursIndex和mintuesIndex变量。
以下是在iOS 14上使用Xcode 12在iPhone 12 Pro Max模拟器上进行的测试。
hfwmuf9z7#
在iOS15中,此解决方案:https://stackoverflow.com/a/56568715/12847995很好,但它需要在“.clipped()”修饰符之前使用修饰符“.compositingGroup()”。
5f0d552i8#
这不是很优雅,但它不涉及任何UIKit的东西移植。我知道你在回答中提到了透视法,但也许这里的几何学可以解决这个问题
使用几何形状并固定大小,如下图所示,两个拾取器整齐地占据了屏幕的一半宽度。现在你只需要从两个不同的状态变量中处理选择,而不是一个,但我更喜欢这种方式,因为它可以将所有内容都保存在Swift UI中