我正在尝试向SwiftUI应用添加三成分选取器(UIPickerView)(在传统的UIKit应用中,数据源将从3
方法返回numberOfComponents
),但是我在任何地方都找不到这样的例子。
我曾尝试添加一个由三个单一组件Picker组成的HStack,但是如果它们全部都属于一个Picker,则其透视图将大相径庭。
答案 0 :(得分:4)
到目前为止,我发现最好的解决方案是通过UIPickerView
和协调器将原始UIViewRepresentable
移植到SwiftUI中。
在这个惊人的WWDC 2019视频中讨论了将UIKit组件移植到SwiftUI:
这是结果(请参见底部的演示代码):
实施:
有两个绑定data
和selection
从超级视图中向下传递。
数据是Data
的数组,例如
[
Array(1...100),
Array(1...100),
Array(1...100)
]
每个数组代表一个选择器组件。
数组中的每个值代表选择器组件中的一行。
选择是Data
的数组,例如[10, 20, 30]
它代表选择器的选择(遍及所有组件)。
如果其中一个绑定发生更改,则会触发所有组件的重绘。
此外,恢复了选择。
struct CustomPicker<Data>: UIViewRepresentable where Data: Equatable {
@Binding var data: [[Data]]
@Binding var selection: [Data]
class Coordinator: NSObject, UIPickerViewDelegate, UIPickerViewDataSource {
@Binding var data: [[Data]]
@Binding var selection: [Data]
init(data: Binding<[[Data]]>, selection: Binding<[Data]>) {
$data = data
$selection = selection
}
func pickerView(_ pickerView: UIPickerView,
numberOfRowsInComponent component: Int) -> Int {
data[component].count
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
data.count
}
func pickerView(_ pickerView: UIPickerView,
widthForComponent component: Int) -> CGFloat {
return (pickerView.superview?.bounds.width ?? 0) * 0.33
}
func pickerView(_ pickerView: UIPickerView,
rowHeightForComponent component: Int) -> CGFloat {
return 30
}
func pickerView(_ pickerView: UIPickerView,
viewForRow row: Int,
forComponent component: Int,
reusing view: UIView?) -> UIView {
guard let reusableView = view as? UILabel else {
let label = UILabel(frame: .zero)
label.backgroundColor = UIColor.red.withAlphaComponent(0.15)
label.text = "\(data[component][row])"
return label
}
reusableView.text = "\(data[component][row])"
return reusableView
}
func pickerView(_ pickerView: UIPickerView,
didSelectRow row: Int,
inComponent component: Int) {
let value = data[component][row]
selection[component] = value
}
}
func makeCoordinator() -> CustomPicker.Coordinator {
return Coordinator(data: $data,
selection: $selection)
}
func makeUIView(context: UIViewRepresentableContext<CustomPicker>) -> UIPickerView {
let picker = UIPickerView()
picker.delegate = context.coordinator
return picker
}
func updateUIView(_ uiView: UIPickerView,
context: UIViewRepresentableContext<CustomPicker>) {
uiView.reloadAllComponents()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.selection.enumerated().forEach { tuple in
let (offset, value) = tuple
let row = self.data[offset].firstIndex { $0 == value } ?? 0
uiView.selectRow(row, inComponent: offset, animated: false)
}
}
}
}
演示:
struct ContentView: View {
@State var data: [[Int]] = [
Array(0...10),
Array(20...40),
Array(100...200)
]
@State var selection: [Int] = [0, 20, 100]
var body: some View {
NavigationView {
VStack {
CustomPicker(data: self.$data,
selection: self.$selection)
Text("Selection: \(String(describing: selection))")
}
}
}
}
答案 1 :(得分:1)
即使使用.clipped()
,底层选择器也不会缩小,而倾向于与其他选择器重叠。我设法裁剪甚至底层选择器视图的唯一方法是将.mask(Rectangle())
添加到父容器。不要问为什么,我不知道。
一个带有2个选择器(小时和分钟)的工作示例:
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())
答案 2 :(得分:0)
这不是那么优雅,但不涉及任何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)
}
}
像这样使用几何形状并固定大小,这表明两个拾取器整齐地占据了屏幕宽度的一半。现在,您只需要处理从两个不同状态变量中选择的内容,而不是一个,但是我更喜欢这种方式,因为它将所有内容保持在快速的UI中
答案 3 :(得分:0)
以下是使用UIKit选择器对上述解决方案的改编:
Sub Test()
Dim K As Long
Dim LR As Long
LR = Cells(Rows.Count, 1).End(xlUp).Row
For K = 2 To LR
Cells(K, 2).Value = Right(Cells(K, 1).Value, Len(Cells(K, 1)) - InStr(1, Cells(K, 1).Value, "_"))
Next K
End Sub
答案 4 :(得分:0)
我非常喜欢woko的回答,但最终结果在视觉上有些不足。元素感觉有点间隔,所以我将geometry.size.width乘数从2更改为5,并在拾取器的两侧添加了间隔。 (我还包括了woko答案中缺少的hoursIndex和mintuesIndex变量。)
以下是在iPhone 14 Pro Max模拟器上使用Xcode 12在iOS 14上进行的测试。
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())
}
}