#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
}
}