Я хотел бы знать, правильно ли хранить ссылку на модель, которую представляет в ней UITableViewCell.
Причина, по которой я спрашиваю, связана с необходимостью знать модель в случае нажатия кнопки внутри нее.
Есть ли лучший (также известный как желательный) способ сделать это?
Пример:
class Person {
var name: String
var lastName: String
var age: Int
}
protocol PersonCellDelegate: NSObjectProtocol {
// should the second parameter be the model that the cell represents?
func songCell(_ cell: PersonCell, didClickAtEditButtonOfPerson person: Person)
}
class PersonCell: UITableViewCell {
@IBOutlet private weak var nameLabel: UILabel!
@IBOutlet private weak var lastNameLabel: UILabel!
@IBOutlet private weak var ageLabel: UILabel!
@IBOutlet private weak var editButton: UIButton!
// does the cell need to store its reference?
var person: Person! {
didSet {
nameLabel.text = person.name
// ...
}
}
weak var delegate: PersonCellDelegate?
// ...
}





В строгом MVC представление не должно обращаться к модели напрямую.
Когда пользователь нажимает кнопку в ячейке, вызывается метод делегата. Пусть делегат (обычно это контроллер представления) обрабатывает событие щелчка (например, модель изменения).
После обновления модели контроллер при необходимости обновит вид.
Я использую его в некоторых случаях, особенно как вы здесь, в пользовательской ячейке. Я не полагаюсь на UItableViewCell для хранения данных для меня, которые у меня есть в модели, поскольку ячейку можно использовать повторно, когда она находится за пределами экрана.
Ячейка табличного представления является представлением. Чем меньше он знает о логике приложения, тем лучше.
Вы можете получить сущность, используемую с помощью метода indexPath(for:):
protocol MyTableViewCellDelegate: AnyObject {
func myTableViewCellDidSomething(_ cell: MyTableViewCell)
}
class MyTableViewCell: UITableViewCell {
weak var delegate: MyTableViewCellDelegate?
}
class ViewController: UITableViewController, MyTableViewCellDelegate {
var personList: [Person] = []
func myTableViewCellDidSomething(_ cell: MyTableViewCell) {
guard let indexPath = tableView.indexPath(for: cell) else { return }
let person = personList[indexPath.row]
// ...
}
}
Итак, нет единственно правильного пути, так что я могу просто сказать вам, что я бы сделал.
Если бы я не перемещал ячейки, я бы сохранил свойство модели. В классе ячеек не следует задавать свойства розеток, так как ячейки можно использовать повторно. Вы просто сообщаете контроллеру, что источник данных изменен, и вы должны перезагрузить строки/определенные строки.
var person: Person! // instead, tell controller, that person has been changed
Затем я бы оставил шаблон делегата и использовал бы переменные закрытия. Это делает код более быстрым (в будущем вы можете искать RxSwift).
class PersonCell: UITableViewCell {
var personChanged: (Person) -> Void = { _ in }
var person: Person!
func foo() {
// change person's properties
personChanged(person)
}
func setCell() {
nameLabel.text = person.name
}
}
Затем установите все такие вещи, как метка text в методе cellForRowAtUITableViewDelegate. Также не забудьте установить закрытие ячейки и объявить, что должно произойти после смены человека
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = // ...
cell.person = people[indexPath.row]
cell.setCell()
cell.personChanged = { [weak self] person in
guard let self = self else { return }
self.people[indexPath.row] = person
self.tableView.reloadRows(at: [indexPath], with: .automatic)
}
return cell
}
Это зависит:
Если вы можете переехать ячейки в представлении таблицы (вручную или нажатием кнопки) с помощью insertRows и deleteRows, то это почти единственный способ (наряду с протоколом/делегатом) иметь возможность эффективно получить индексный путь ячейки без перезагружая весь табличный вид.
В представлении прямой таблицы, в котором ячейки не перемещаются, не передавайте модель в ячейку. Вы можете использовать закрытие обратного вызова, которое фиксирует путь индекса и даже элемент модели.
Ты спрашиваешь:
does the cell need to store its reference?
Нет. На самом деле, это блокирует вас в семантике ссылок, и вы можете рассмотреть семантику значений для объекта Person. Я также думаю, что это запутывает модель собственности. Кому сейчас принадлежит этот Person объект?
И даже если вы привержены эталонной семантике и хотите использовать этот паттерн для обнаружения Person изменений, помните, что ваш didSet паттерн — это только половина решения. Тип Person является изменяемым, и вы обнаруживаете, когда объект заменяется новым объектом Person, но не когда изменяются отдельные свойства Person. Если вы собираетесь пойти по этому didSet пути с изменяемыми ссылочными типами, вы также можете добавить KVO для соответствующих свойств.
Этот шаблон влечет за собой довольно тесную связь объектов представления и объектов модели. Как предлагали другие, вы можете рассмотреть другие шаблоны для решения этой проблемы и/или снижения нагрузки на контроллер представления.
Если вы ищете автоматическое обновление ячейки при мутации объекта Person (и, возможно, наоборот), вы можете рассмотреть шаблоны привязки, такие как предлагаемые библиотеками, такими как RxSwift, Связь и т. д.
Я бы также отослал вас к презентации Дэйва Делонга Лучший MVC, которая проведет вас через соображения, если вы не хотите отказываться от MVC, но выяснить, как с ним работать, или Шаблоны архитектуры iOS от Medium, которая представляет собой введение в другие варианты.
В основном это правильно, но происходит сбой, если ячейки перемещаются в табличном представлении, потому что захваченный путь индекса в закрытии не изменится.