Multi-Component Picker (UIPickerView) in SwiftUI

穿精又带淫゛_ 提交于 2019-12-05 17:20:50

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:

This isn't quite as elegant but it doesn't involve porting over any UIKit stuff. I know you mentioned perspective was off in your answer but perhaps the geometry here fixes that

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)

    }
}

Using the geometry and fixing the size like this shows the two pickers neatly taking up half the width of the screen in each half. Now you just need to handle selection from two different state variables instead of one but I prefer this way as it keeps everything in swift UI

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()
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!