Элементы пользовательского интерфейса в протоколе?

#swift #protocols

#swift #протоколы

Вопрос:

Мое приложение имеет около 6 представлений на основе построителя интерфейса, которые содержат одни и те же элементы пользовательского интерфейса и инициируются одной и той же ViewModel, но все представления представляют собой разные классы (некоторые — UICollectionViewCell, некоторые — UITableViewCell, а некоторые из них являются частью представления в UIViewController), они выглядят совершенно по-разномус разными ограничениями и в целом другим дизайном.

Я обнаружил, что существует много дублирования кода для привязки элементов пользовательского интерфейса к ViewModel в каждом представлении, и я попытался найти способ очистить код.

Поскольку приложение ориентировано на протокол, я придумал следующее решение:

 protocol Common {
    var someTitleLabel: UILabel! { get set }
    var someImageView: UIImageView! { get set }
    func configure(viewModel: ViewModel?)
}

extension Common {
    func configure(viewModel: ViewModel?) {
        guard let viewModel = viewModel else { return }
        someTitleLabel?.text = viewModel.title
        someImageView?.image = viewModel.image
}

  

и реализация каждого представления:

 class someClass: UICollectionViewCell, Common {

    @IBOutlet weak var someTitleLabel: UILabel!
    @IBOutlet weak var someImageView: UIImageView!

    override func awakeFromNib() {
        super.awakeFromNib()
    }
  
 cell.configure(viewModel: viewModel)
  

Хорошо то, что это работает.

Плохо то, что я никогда не видел такой реализации ни в одном приложении, над которым я работал.

Итак, основные вопросы — это хорошая практика для такой проблемы? Должен ли я отказаться от структуры, ориентированной на протокол, для этой ситуации и использовать ООП? Какова была бы наилучшая практика создания «общих» представлений конструктора интерфейса, настроенных одной и той же ViewModel?

Ответ №1:

Вы можете использовать силу наследования для достижения того же результата.

 YourBaseCell: UICollectionViewCell {

        @IBOutlet weak var someTitleLabel: UILabel!
        @IBOutlet weak var someImageView: UIImageView!

        func configure(viewModel: ViewModel?) {
            guard let viewModel = viewModel else { return }
            someTitleLabel?.text = viewModel.title
            someImageView?.image = viewModel.image
        }
}
  

А затем подключитесь к выходам к базовому классу для каждого файла xib, который у вас есть.

Комментарии:

1. Спасибо, но это очевидный ответ, как уже упоминалось, приложение структурировано, ориентировано на протокол, и поэтому я стараюсь избегать структуры ООП. Я ищу гораздо более глубокий ответ или обсуждение моделирования пользовательского интерфейса с помощью протоколов, и если это даже хорошая практика.

2. На мой взгляд, действительно здорово иметь протоколы в вашем приложении. Однако это не обязательно означает, что вам следует избегать ООП в вашей архитектуре.

3. Я думал, что написал это в вопросе, и теперь буду редактировать. представления не обязательно являются CollectionViewCell, это может быть также tableViewCell или даже ViewController.

4. Да, это хорошая информация. Теперь это невозможно реализовать с наследованием.

5. Проблема с этим подходом заключается в том, что метод configure не реализован в ваших конкретных классах

Ответ №2:

Лучше предоставить вашему конкретному классу контроль над методом configure

 protocol Common {
    var someTitleLabel: UILabel! { get set }
    var someImageView: UIImageView! { get set }
    func configure(viewModel: ViewModel)

}

extension Common {
    func configureCommonProperties(viewModel: ViewModel) {
        guard let viewModel = viewModel else { return }
        someTitleLabel?.text = viewModel.title
        someImageView?.image = viewModel.image
    }
}

class someClass: UICollectionViewCell, Common {

    @IBOutlet weak var someTitleLabel: UILabel!
    @IBOutlet weak var someImageView: UIImageView!

    func configure(viewModel: ViewModel) {
        configureCommonProperties(viewModel: viewModel)
    }
    
}
  

Комментарии:

1. Пожалуйста, не отвечайте дважды. Если есть два возможных решения, обсудите их в одном ответе. Если вам не нравится ваш первый ответ (возможно, OP изменил вопрос), отредактируйте первый ответ.

2. @matt, если решения достаточно разные, их можно публиковать отдельно — meta.stackexchange.com/a/25210/277866

3. Да, это два разных подхода. Поэтому я решил ответить отдельно

4. @Cristik Это одно мнение, и я с ним не согласен. Проблема в том, что это выглядит как попытка набрать двойной набор голосов.

Ответ №3:

YourBaseCell:

 UICollectionViewCell {

    @IBOutlet weak var someTitleLabel: UILabel!
    @IBOutlet weak var someImageView: UIImageView!

    func configure(viewModel: ViewModel?) {
        guard let viewModel = viewModel else { return }
        someTitleLabel?.text = viewModel.title
        someImageView?.image = viewModel.image
    }
}
  

Вместо наследования от Cell вы можете наследовать от UIView и использовать этот новый класс в качестве пользовательского представления при создании пользовательского интерфейса cell таким образом, нет проблем с таблицей и ячейками коллекции, и это решает «дублирование кода для привязки элементов пользовательского интерфейса к ViewModel»

вы можете сделать что-то подобное:

 CellContenierView: UIView {

    @IBOutlet weak var someTitleLabel: UILabel!
    @IBOutlet weak var someImageView: UIImageView!

    func configure(viewModel: ViewModel?) {
        guard let viewModel = viewModel else { return }
        someTitleLabel?.text = viewModel.title
        someImageView?.image = viewModel.image
    }
}