Ładowanie wielokrotnego użytku UITableViewCell z końcówki

89

Jestem w stanie zaprojektować niestandardowe komórki UITableViewCell i załadować je dobrze, korzystając z techniki opisanej w wątku znajdującym się na stronie http://forums.macrumors.com/showthread.php?t=545061 . Jednak użycie tej metody nie pozwala już na inicjowanie komórki za pomocą reuseIdentifier, co oznacza, że ​​musisz tworzyć zupełnie nowe instancje każdej komórki przy każdym wywołaniu. Czy ktoś wymyślił dobry sposób, aby nadal buforować określone typy komórek do ponownego wykorzystania, ale nadal móc je zaprojektować w Interface Builder?

Greg Martin
źródło

Odpowiedzi:

74

Wystarczy zaimplementować metodę z odpowiednią sygnaturą metody:

- (NSString *) reuseIdentifier {
  return @"myIdentifier";
}
Louis Gerbarg
źródło
Gdzie wdrożyć tę metodę?
Krishnan
5
To jest ryzykowne. Co się stanie, jeśli masz dwie podklasy swojej podklasy komórki i używasz obu w jednym widoku tabeli? Jeśli wyślą wywołanie ponownego użycia identyfikatora do super, usuniesz z kolejki komórkę niewłaściwego typu .............. Myślę, że musisz zastąpić metodę reuseIdentifier, ale zwróć zastąpiony identyfikator strunowy.
SK9
3
Aby mieć pewność, że jest wyjątkowy, możesz zrobić:return NSStringFromClass([self class]);
ivanzoid
119

Właściwie, ponieważ budujesz komórkę w Interface Builder, po prostu ustaw tam identyfikator ponownego użycia:

IB_reuse_identifier

Lub, jeśli używasz Xcode 4, sprawdź kartę Inspektor atrybutów:

wprowadź opis obrazu tutaj

(Edycja: po wygenerowaniu XIB przez XCode zawiera on pusty UIView, ale potrzebujemy UITableViewCell; więc musisz ręcznie usunąć UIView i wstawić komórkę widoku tabeli. Oczywiście IB nie pokaże żadnych parametrów UITableViewCell dla UIView.)

Tim Keating
źródło
Co mam ustawić identyfikatory, jeśli używam tej Nib utworzonej komórki dla więcej niż jednej komórki? To doprowadzi nas do dwóch komórek o takich samych identyfikatorach.
Krishnan
4
Nie myśl o tym jako o unikalnym identyfikatorze - myśl o tym bardziej jak o nazwie typu.
Tim Keating
Nie widzę opcji ustawienia identyfikatora za pomocą wbudowanego konstruktora interfejsu w Xcode 4.3.3. Zdecydowanie ustawiam klasę na moją podklasę UITableViewCell. Brakuje mi tego, czy już go nie ma?
Tyler
3
Ok, rozwiązałem swój problem. Jeśli zaczniesz od obiektu UIView w konstruktorze interfejsu (w Xcode 4) i zmienisz jego klasę na UITableViewCell, nie otrzymasz właściwości specyficznych dla komórki, takich jak identyfikator ponownego użycia. Aby to uzyskać, powinieneś zacząć od pustego xib i przeciągnąć obiekt komórki tabeli, który będzie miał właściwości specyficzne dla komórki, które możesz edytować.
Tyler,
1
@Krishnan Pomyśl o tym w ten sposób - kiedy tworzysz komórkę widoku tabeli z identyfikatorem X, mówisz „Daj mi komórkę z puli oznaczonej X”. Jeśli pula istnieje i jest tam wolna komórka, to daje ci ją. W przeciwnym razie tworzy pulę (jeśli to konieczne), następnie przesyła nową informację do komórki, nadaje jej etykietę „X”, a następnie przekazuje ją tobie. Zatem komórki MOGĄ być unikalne - np. Możesz stworzyć pulę z tylko jedną komórką z określonym identyfikatorem - ale biblioteka używa strategii podobnej do wolnej listy, aby uniknąć przydzielania / zwalniania pamięci.
Tim Keating
66

Teraz w iOS 5 jest do tego odpowiednia metoda UITableView:

- (void)registerNib:(UINib *)nib forCellReuseIdentifier:(NSString *)identifier
wzs
źródło
10
Pozostałe odpowiedzi w tym wątku, w tym zaakceptowana odpowiedź, zawierają nieaktualne porady.
Kaelin Colclasure,
czy to jest kompatybilne wstecz? Mam na myśli to, że jeśli stworzę aplikację z SDK 5.0 i docelowo minimum 4.0, aplikacja będzie działać na przykład na urządzeniach z iOS 4.0?
Abolfoooud
1
Nie, to nie jest kompatybilne wstecz, jak każdy nowy interfejs API w iOS 5.0.
marzapower
działał przykład, jak zintegrować to ze swoim kontrolerem: mindfiresolutions.com/…
mblackwell8
47

Nie pamiętam, gdzie pierwotnie znalazłem ten kod, ale jak na razie działa świetnie.

- (UITableViewCell *)tableView:(UITableView *)tableView 
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    static NSString *CellIdentifier = @"CustomTableCell";
    static NSString *CellNib = @"CustomTableCellView";

    UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        NSArray *nib = [[NSBundle mainBundle] loadNibNamed:CellNib owner:self options:nil];
        cell = (UITableViewCell *)[nib objectAtIndex:0];
    }

    // perform additional custom work...

    return cell;
}

Przykładowa konfiguracja programu Interface Builder ...

tekst alternatywny

jrainbow
źródło
12

Spójrz na odpowiedź, której udzieliłem na to pytanie:

Czy można zaprojektować podklasy NSCell w programie Interface Builder?

Zaprojektowanie UITableViewCell w IB jest nie tylko możliwe, jest to pożądane, ponieważ w przeciwnym razie całe ręczne okablowanie i umieszczanie wielu elementów jest bardzo żmudne. Wydajność jest dobra, o ile starasz się, aby wszystkie elementy były nieprzezroczyste, gdy to możliwe. Identyfikator ponownego użycia jest ustawiony w IB dla właściwości UITableViewCell, a następnie podczas próby usunięcia z kolejki należy użyć pasującego identyfikatora ponownego wykorzystania w kodzie.

Słyszałem również od niektórych prezenterów WWDC w zeszłym roku, że nie powinno się tworzyć komórek widoku tabeli w IB, ale jest to mnóstwo bunkra.

Kendall Helmstetter Gelner
źródło
2
Nie powinieneś tworzyć komórek widoku tabeli w IB, jeśli potrzebujesz przejrzystości i chcesz dobrej wydajności przewijania. W przypadku niektórych interfejsów użytkownika potrzebna jest przezroczystość (np. Do renderowania tekstu na grafice). Czasami jedynym sposobem uzyskania dobrego przewijania na starszym sprzęcie (starszym niż A4) jest renderowanie w kodzie, aby uniknąć konieczności łączenia przez GPU wielu przezroczystych warstw.
Nick Forge
2
To prawda, ale nawet z tym lepiej byłoby pozwolić, aby wydajność na starych urządzeniach nieco ucierpiała, aby ułatwić konserwację komórek wbudowanych w IB. Możesz również użyć tej techniki jako komórki szablonu, z której następnie rysujesz elementy, aby uniknąć tworzenia kompozycji za pomocą niestandardowej metody rysowania.
Kendall Helmstetter Gelner
6

Oto inna opcja:

NSString * cellId = @"reuseCell";  
//...
NSArray * nibObjects = [[NSBundle mainBundle] loadNibNamed:@"CustomTableCell" owner:nil options:nil];

for (id obj in nibObjects)
{
    if ([obj isKindOfClass:[CustomTableCell class]])
    {
        cell = obj;
        [cell setValue:cellId forKey:@"reuseIdentifier"];
        break;
    }
}
JDL
źródło
Zauważ, że jest to jedyne opublikowane do tej pory rozwiązanie, które nie wymaga podklasy niestandardowej UITableViewCellw celu ustawienia unikalnej wartości dla reuseIdentifer. Myślę, że właśnie tego szukał oryginalny op.
charshep
Mam nadzieję, że Apple nie odrzuci mojej aplikacji do korzystania z tego… Używam tego do uzyskania „statycznych” komórek, ponieważ w moim widoku tabeli proces wypełniania komórki przebiega powoli. W ten sposób muszę to wykonać tylko raz (nadając każdemu wierszowi inny identyfikator). Dziękuję Ci!
Ricard Pérez del Campo
Bardzo pomocne, ponieważ nie ma metody UITableViewCell do ręcznego ustawiania tego!
Jesse
2

Tworzę własne komórki widoku w podobny sposób - z wyjątkiem tego, że łączę komórkę przez IBOutlet.

[nib objectAt...]Podejście jest podatne na zmiany położeń elementów w tablicy.

UIViewControllerPodejście jest dobra - tylko próbował go i działa dość miłe.

ALE...

We wszystkich przypadkach initWithStylekonstruktor NIE jest wywoływany, więc nie jest wykonywana domyślna inicjalizacja.

Czytałem różne miejsca na temat używania initWithCoderlub awakeFromNib, ale nie ma rozstrzygających dowodów, że którykolwiek z tych sposobów jest właściwy.

Oprócz jawnego wywołania metody inicjalizacji w cellForRowAtIndexPathmetodzie nie znalazłem jeszcze odpowiedzi na to pytanie.

sww
źródło
awakeFromNib to właściwy sposób reagowania na ładowanie obiektu z NIB.
Jon Hess
2

Jakiś czas temu znalazłem świetny wpis na ten temat na blog.atebits.com i od tego czasu zacząłem używać klasy Loren Brichter ABTableViewCell do wykonywania wszystkich moich UITableViewCells.

Otrzymujesz prosty kontener UIView, w którym możesz umieścić wszystkie swoje widżety, a przewijanie jest błyskawiczne.

Mam nadzieję, że to jest przydatne.

eric
źródło
2

Ta technika również działa i nie wymaga funky ivar w kontrolerze widoku do zarządzania pamięcią. W tym przypadku komórka niestandardowego widoku tabeli znajduje się w pliku xib o nazwie „CustomCell.xib”.

 static NSData *sLoadedCustomCell = nil;

 cell = [tableView dequeueReusableCellWithIdentifier:@"CustomCell"];
 if (cell == nil) 
 {
   if (sLoadedCustomCell == nil) 
   {        
      // Load the custom table cell xib
      // and extract a reference to the cell object returned
      // and cache it in a static to avoid reloading the nib again.

      for (id loadedObject in [[NSBundle mainBundle] loadNibNamed:@"CustomCell" owner:nil options:nil]) 
      {
        if ([loadedObject isKindOfClass:[UITableViewCell class]]) 
        {
          sLoadedCustomCell = [[NSKeyedArchiver archivedDataWithRootObject: loadedObject] retain];
          break;
        }
    }
    cell = (UITableViewCell *)[NSKeyedUnarchiver unarchiveObjectWithData: sLoadedCustomCell];
  }
Bill Garrison
źródło
1
Archiwizacja i cofanie archiwizacji jest całkowicie niepotrzebne.
Bryan Henry,
1
Archiwizacja / przywracanie z archiwum jest niepotrzebne, jeśli możesz ładować komórkę z jej końcówki, gdy nie można jej usunąć z kolejki. Jeśli jednak chcesz załadować komórkę ze stalówki tylko raz dokładnie , musisz umieścić ją w pamięci podręcznej. Osiągam to buforowanie przy użyciu NSKeyedArchiving, ponieważ UITableViewCell nie implementuje NSCopying.
Bill Garrison,
1
Wszystko to powiedziawszy, użycie UINib do załadowania komórki daje ten sam efekt: ładowanie z dysku raz, później ładowanie z pamięci.
Bill Garrison,
2

Metoda Louisa zadziałała na mnie. Oto kod, którego używam do tworzenia UITableViewCell z końcówki:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{   
    UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:@"CustomCellId"];

    if (cell == nil) 
    {
        UIViewController *c = [[UIViewController alloc] initWithNibName:@"CustomCell" bundle:nil];
        cell = (PostCell *)c.view;
        [c release];
    }

    return cell;
}
ggarber
źródło
2
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

static NSString *simpleTableIdentifier = @"CustomCell";

CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil)
{
    NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"CustomCell" owner:self options:nil];
    cell = [nib objectAtIndex:0];

    [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
}         

return cell;
}
Mohit
źródło
1

Rozwiązanie Gustavogb nie działa dla mnie, wypróbowałem:

ChainesController *c = [[ChainesController alloc] initWithNibName:@"ChainesController" bundle:nil];
[[NSBundle mainBundle] loadNibNamed:@"ChaineArticleCell" owner:c options:nil];
cell = [c.blogTableViewCell retain];
[c release];

Wydaje się, że działa. BlogTableViewCell jest IBOutlet dla komórki, a ChainesController jest właścicielem pliku.

groumpf
źródło
1

Z dokumentacji UITableView dotyczącej dequeueWithReuseIdentifier: „Ciąg znaków identyfikujący obiekt komórki do ponownego wykorzystania. Domyślnie identyfikatorem komórki wielokrotnego użytku jest nazwa jej klasy, ale można ją zmienić na dowolną wartość”.

Zastępowanie -reuseIdentifer siebie jest ryzykowne. Co się stanie, jeśli masz dwie podklasy swojej podklasy komórki i używasz obu w jednym widoku tabeli? Jeśli wyślą wywołanie ponownego użycia identyfikatora do super, usuniesz z kolejki komórkę niewłaściwego typu .............. Myślę, że musisz zastąpić metodę reuseIdentifier, ale zwróć zastąpiony identyfikator strunowy. Lub, jeśli nie został określony, powinien zwrócić klasę jako ciąg.

SK9
źródło
0

O ile to jest warte, zapytałem o to inżyniera iPhone'a na jednym z iPhone Tech Talks. Jego odpowiedź brzmiała: „Tak, można używać IB do tworzenia komórek. Ale nie rób tego. Proszę, nie rób tego”.

sierpień
źródło
1
To dziwne. Co najmniej dwie z prelekcji na nowojorskim wykładzie miały kod demonstracyjny, który wykorzystywał komórki utworzone w IB.
Shawn Craver
3
Nie jestem pewien, czy w to wierzę, ponieważ używają IB do tworzenia komórek w przykładowym projekcie Apple Advanced Table View Cells.
iwasrobbed
Dziękuję za to. Za każdym razem, gdy to robiłem, napotykam problemy. To może być powód
skorulis
Prawdopodobnie nie miał na ten temat wystarczającej wiedzy.
aryaxt
0

Postępowałem zgodnie z instrukcjami Apple podanymi przez Bena Moshera (dzięki!), Ale stwierdziłem, że Apple pominął ważną kwestię. Obiekt, który projektują w IB, to po prostu UITableViewCell, podobnie jak zmienna, którą z niego ładują. Ale jeśli faktycznie skonfigurujesz go jako niestandardową podklasę UITableViewCell i napiszesz pliki kodu dla podklasy, możesz napisać deklaracje IBOutlet i metody IBAction w kodzie i połączyć je z niestandardowymi elementami w IB. Wtedy nie ma potrzeby używania tagów widoku, aby uzyskać dostęp do tych elementów i możesz stworzyć dowolną szaloną komórkę. To niebo Cocoa Touch.

David Casseres
źródło