SwiftUI中的多组件选择器(UIPickerView)

时间:2019-06-12 17:28:38

标签: swift uipickerview picker swiftui

我正在尝试向SwiftUI应用添加三成分选取器(UIPickerView)(在传统的UIKit应用中,数据源将从3方法返回numberOfComponents),但是我在任何地方都找不到这样的例子。

我曾尝试添加一个由三个单一组件Picker组成的HStack,但是如果它们全部都属于一个Picker,则其透视图将大相径庭。

5 个答案:

答案 0 :(得分:4)

到目前为止,我发现最好的解决方案是通过UIPickerView和协调器将原始UIViewRepresentable移植到SwiftUI中。

在这个惊人的WWDC 2019视频中讨论了将UIKit组件移植到SwiftUI:

Integrating SwiftUI


这是结果(请参见底部的演示代码):

enter image description here


实施

有两个绑定dataselection从超级视图中向下传递。

数据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())
    }
}