Как сделать так, чтобы многоразовое представление принимало общие типы

Я создал многоразовый элемент управления, который будет использоваться в проекте, над которым я работаю. Это простоUITextField который показывает UIPickerView как его inputView.

class InputPickerView: UIView {
    @IBOutlet private var view: UIView!
    @IBOutlet weak private var titleLabel: UILabel!
    @IBOutlet weak private var textField: UITextField!

    private(set) var pickerView = UIPickerView()

    var options: [String] = []

    var option: String {
        get {
            return textField.text ?? ""
        }
        set {
            textField.text = newValue
        }
    }

    var title: String = "" {
        didSet {
            titleLabel.text = title
        }
    }


    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }

    private func commonInit() {
        Bundle.main.loadNibNamed("InputPickerView", owner: self, options: nil)
        addSubview(view)
        view.frame = bounds
        view.autoresizingMask = [.flexibleHeight, .flexibleWidth]

        pickerView.dataSource = self
        pickerView.delegate = self
        textField.inputView = pickerView
    }
}

extension InputPickerView: UITextFieldDelegate {
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        return false
    }
}

extension InputPickerView: UIPickerViewDataSource {
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }

    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return options.count
    }

    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        return options[row]
    }
}

extension InputPickerView: UIPickerViewDelegate {
    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        textField.text = options[row]
    }
}

В настоящее время он принимает только массив строк и возвращает строку. Я пытаюсь сделать его еще более пригодным для повторного использования, заставив его принимать / возвращать любые типы, такие как структуры и перечисления, с помощью дженериков. Я надеялся, что структуры / перечисления будут соответствоватьCustomStringConvertible и используйте значение свойства description в качестве отображаемого значения для параметров представления средства выбора.

Но мне сложно понять, как это сделать. Все статьи, вопросы и руководства, с которыми я сталкивался, содержат протоколы. Так что я немного запутался.

Как я могу сделать options а также option переменные принимают / возвращают любой тип с дженериками?

Под этим я подразумеваю, скажем, я создаю объект с именем State.

struct State {
    let id: Int
    let title: String
}

extension State: CustomStringConvertible {
    var description: String {
        return title
    }
}

Вместо того, чтобы передавать в представление строки, я пытаюсь заставить его принимать экземпляры State объекты в options свойство и используйте представление descriptionзначение как отображаемое значение. И когда пользователь выбирает один, он возвращает выбранныйState объект через option свойство.

Демонстрационный проект

1 ответ

Решение

Сначала вам нужен протокол, который извлекает строку из ваших соответствующих типов для отображения в средстве выбора:

protocol Presentable {
    var title: String { get }
}

Сделайте свой State структура соответствует Presentable:

struct State: Presentable {
    let id: Int
    let title: String
}

Добавьте некоторые общие ограничения к вашему InputPickerView и всякий раз, когда вам нужен текст из вашей модели, просто ссылайтесь на titleсвойство. Обратите внимание: если вы используете дженерики, вы больше не можете создавать расширения для своихUIPickerViewDataSource а также UIPickerViewDelegate методы.

class InputPickerView<OptionType: Presentable>: UIView, UIPickerViewDataSource, UIPickerViewDelegate {

    private var titleLabel: UILabel!
    private var textField: UITextField!

    var options: [OptionType] = []

    var selectedOption: OptionType?

    var title: String = "" {
        didSet {
            titleLabel.text = title
        }
    }

    // ... Other stuff you need to add ...

    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }

    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return options.count
    }

    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        return options[row].title
    }

    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        textField.text = options[row].title
        selectedOption = options[row]
    }
}

Вы создаете свой InputPickerView как это:

let pickerView = InputPickerView<State>()
pickerView.options = [
    State(id: 1, title: "First"),
    State(id: 2, title: "Second"),
    State(id: 3, title: "Third"),
]
Другие вопросы по тегам