Многокомпонентный инструмент выбора (UIPickerView) в SwiftUI - PullRequest
2 голосов
/ 12 июня 2019

Я пытаюсь добавить трехкомпонентный инструмент выбора (UIPickerView) в приложение SwiftUI (в традиционном приложении UIKit источник данных возвращает 3 из метода numberOfComponents), но не могу найтипример этого где угодно.

Я пытался добавить HStack из трех однокомпонентных сборщиков, но перспектива отличается от того, что было бы, если бы все они были частью одного сборщика.

Ответы [ 2 ]

4 голосов
/ 12 июня 2019

Лучшее решение, которое я нашел - пока что - это перенести исходный UIPickerView в SwiftUI через UIViewRepresentable и координатор.

Портирование компонентов UIKit в SwiftUI обсуждается в этом удивительном видео WWDC 2019:

Интеграция SwiftUI


Вот результат (см. Демонстрационный код внизу):

enter image description here


Осуществление

Есть две привязки, data и selection, которые передаются из суперпредставления.

data - это массив массивов 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))")
            }
        }
    }

}
0 голосов
/ 12 июля 2019

Это не так элегантно, но не требует портирования на 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

...