Я уникаю використання UITableViewController
, оскільки це покладає багато обов'язків на один об'єкт. Тому я відокремлюю UIViewController
підклас від джерела даних та делегую. Відповідальність контролера перегляду полягає у підготовці подання таблиці, створенні джерела даних із даними та з'єднанні цих речей. Зміна способу подання перегляду таблиці може бути здійснена без зміни контролера перегляду, і дійсно один і той же контролер подання може використовуватися для безлічі джерел даних, які всі відповідають цій схемі. Аналогічно, зміна робочого процесу в додатку означає зміни в контролері перегляду, не турбуючись про те, що відбувається з таблицею.
Я намагався розділити протоколи UITableViewDataSource
та UITableViewDelegate
протоколи на різні об'єкти, але це, як правило, виявляється помилковим розщепленням, оскільки майже кожен метод делегата потребує копання в джерело даних (наприклад, при виборі, делегат повинен знати, який об'єкт представлений вибраний рядок). Отже, я закінчую єдиним об'єктом, який є і джерелом даних, і делегатом. Цей об'єкт завжди пропонує метод, для -(id)tableView: (UITableView *)tableView representedObjectAtIndexPath: (NSIndexPath *)indexPath
якого і джерело даних, і делегуючі аспекти повинні знати, над чим вони працюють.
Це мій проблемний "рівень 0". Рівень 1 займається, якщо мені доведеться представляти предмети різного типу в одному поданні таблиці. Наприклад, уявіть, що вам довелося написати додаток "Контакти" - для одного контакту можуть бути рядки, що представляють номери телефонів, інші рядки, що представляють адреси, інші, що представляють адреси електронної пошти тощо. Я хочу уникати такого підходу:
- (UITableViewCell *)tableView: (UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath {
id object = [self tableView: tableView representedObjectAtIndexPath: indexPath];
if ([object isKindOfClass: [PhoneNumber class]]) {
//configure phone number cell
}
else if …
}
Поки що два представлені рішення. Один полягає в динамічній побудові селектора:
- (UITableViewCell *)tableView: (UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath {
id object = [self tableView: tableView representedObjectAtIndexPath: indexPath];
NSString *cellSelectorName = [NSString stringWithFormat: @"tableView:cellFor%@AtIndexPath:", [object class]];
SEL cellSelector = NSSelectorFromString(cellSelectorName);
return [self performSelector: cellSelector withObject: tableView withObject: object];
}
- (UITableViewCell *)tableView: (UITableView *)tableView cellForPhoneNumberAtIndexPath: (NSIndexPath *)indexPath {
// configure phone number cell
}
У такому підході вам не потрібно редагувати епічне if()
дерево для підтримки нового типу - просто додайте метод, який підтримує новий клас. Це чудовий підхід, якщо цей подання таблиці є єдиним, який повинен представляти ці об'єкти, або потрібно представити їх особливим чином. Якщо одні і ті ж об’єкти будуть представлені в різних таблицях з різними джерелами даних, цей підхід розпадається, оскільки методам створення комірок потрібен спільний доступ до джерел даних - ви можете визначити загальний надклас, який надає ці методи, або ви могли це зробити:
@interface PhoneNumber (TableViewRepresentation)
- (UITableViewCell *)tableView: (UITableView *)tableView representationAsCellForRowAtIndexPath: (NSIndexPath *)indexPath;
@end
@interface Address (TableViewRepresentation)
//more of the same…
@end
Потім у вашому класі джерела даних:
- (UITableViewCell *)tableView: (UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath {
id object = [self tableView: tableView representedObjectAtIndexPath: indexPath];
return [object tableView: tableView representationAsCellForRowAtIndexPath: indexPath];
}
Це означає, що будь-яке джерело даних, яке повинно відображати телефонні номери, адреси тощо, може просто запитати будь-який об’єкт, представлений для комірки подання таблиці. Самому джерелу даних більше не потрібно нічого знати про об'єкт, що відображається.
"Але зачекайте, - чую гіпотетичний співрозмовник, - чи це не порушує MVC? Чи не вкладаєте ви деталі перегляду в модельний клас?"
Ні, це не порушує MVC. Ви можете вважати, що категорії в цьому випадку є реалізацією декоратора ; так PhoneNumber
само є модельним класом, але PhoneNumber(TableViewRepresentation)
є категорією перегляду. Джерело даних (об'єкт контролера) опосередковується між моделлю та поданням, тому архітектура MVC все ще зберігається.
Ви також можете бачити таке використання категорій як прикрасу в рамках Apple. NSAttributedString
- клас моделей, який містить текст та атрибути. AppKit забезпечує, NSAttributedString(AppKitAdditions)
а UIKit надає NSAttributedString(NSStringDrawing)
категорії декораторів, які додають поведінку малювання до цих класів моделей.