Unikam używania UITableViewController
, ponieważ nakłada wiele obowiązków na pojedynczy obiekt. Dlatego oddzielam UIViewController
podklasę od źródła danych i deleguję. Obowiązkiem kontrolera widoku jest przygotowanie widoku tabeli, utworzenie źródła danych z danymi i połączenie tych elementów. Zmiana sposobu reprezentacji widoku tabeli może być wykonana bez zmiany kontrolera widoku, a w rzeczywistości ten sam kontroler widoku może być używany dla wielu źródeł danych, które wszystkie stosują ten wzorzec. Podobnie zmiana przepływu pracy aplikacji oznacza zmiany w kontrolerze widoku bez obawy o to, co stanie się z tabelą.
Próbowałem rozdzielić protokoły UITableViewDataSource
i UITableViewDelegate
na różne obiekty, ale zwykle kończy się to fałszywym podziałem, ponieważ prawie każda metoda na uczestniku musi wkopać się w źródło danych (np. Po wybraniu uczestnik musi wiedzieć, który obiekt jest reprezentowany przez wybrany wiersz). W rezultacie otrzymuję pojedynczy obiekt, który jest zarówno źródłem danych, jak i delegatem. Ten obiekt zawsze zapewnia metodę, w -(id)tableView: (UITableView *)tableView representedObjectAtIndexPath: (NSIndexPath *)indexPath
której zarówno źródło danych, jak i delegat muszą wiedzieć, nad czym pracują.
To moja separacja problemów na „poziomie 0”. Poziom 1 angażuje się, jeśli muszę reprezentować różnego rodzaju obiekty w tym samym widoku tabeli. Na przykład wyobraź sobie, że musiałeś napisać aplikację Kontakty - dla jednego kontaktu możesz mieć wiersze reprezentujące numery telefonów, inne wiersze reprezentujące adresy, inne reprezentujące adresy e-mail i tak dalej. Chcę uniknąć tego podejścia:
- (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 …
}
Jak dotąd zaprezentowano dwa rozwiązania. Jednym z nich jest dynamiczne skonstruowanie selektora:
- (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
}
W tym podejściu nie trzeba edytować epickiego if()
drzewa, aby obsługiwać nowy typ - wystarczy dodać metodę, która obsługuje nową klasę. Jest to świetne podejście, jeśli ten widok tabeli jest jedynym, który musi reprezentować te obiekty lub prezentować je w specjalny sposób. Jeśli te same obiekty będą reprezentowane w różnych tabelach z różnymi źródłami danych, to podejście załamuje się, ponieważ metody tworzenia komórek wymagają współużytkowania przez źródła danych - możesz zdefiniować wspólną nadklasę, która udostępnia te metody, lub możesz to zrobić:
@interface PhoneNumber (TableViewRepresentation)
- (UITableViewCell *)tableView: (UITableView *)tableView representationAsCellForRowAtIndexPath: (NSIndexPath *)indexPath;
@end
@interface Address (TableViewRepresentation)
//more of the same…
@end
Następnie w klasie źródła danych:
- (UITableViewCell *)tableView: (UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath {
id object = [self tableView: tableView representedObjectAtIndexPath: indexPath];
return [object tableView: tableView representationAsCellForRowAtIndexPath: indexPath];
}
Oznacza to, że każde źródło danych, które musi wyświetlać numery telefonów, adresy itp., Może po prostu zapytać o dowolny obiekt reprezentowany dla komórki widoku tabeli. Samo źródło danych nie musi już nic wiedzieć o wyświetlanym obiekcie.
„Ale czekaj”, słyszę hipotetyczny wtrącający się rozmówca, „czy to nie łamie MVC? Czy nie umieszczasz szczegółów widoku w klasie modelki?”
Nie, to nie psuje MVC. W tym przypadku kategorie można traktować jako implementację programu Decorator ; podobnie PhoneNumber
jak klasa modelu, ale PhoneNumber(TableViewRepresentation)
jest kategorią widoku. Źródło danych (obiekt kontrolera) pośredniczy między modelem a widokiem, więc architektura MVC nadal obowiązuje.
To wykorzystanie kategorii można również postrzegać jako dekorację w ramach Apple. NSAttributedString
jest klasą modelową, zawierającą tekst i atrybuty. AppKit zapewnia, NSAttributedString(AppKitAdditions)
a UIKit zapewnia NSAttributedString(NSStringDrawing)
kategorie dekoratorów, które dodają zachowanie rysunkowe do tych klas modeli.