Laden einer wiederverwendbaren UITableViewCell von einer Schreibfeder

89

Ich bin in der Lage, benutzerdefinierte UITableViewCells zu entwerfen und sie mit der im Thread unter http://forums.macrumors.com/showthread.php?t=545061 beschriebenen Technik einwandfrei zu laden . Mit dieser Methode können Sie die Zelle jedoch nicht mehr mit einem reuseIdentifier initiieren. Dies bedeutet, dass Sie bei jedem Aufruf ganz neue Instanzen jeder Zelle erstellen müssen. Hat jemand eine gute Möglichkeit gefunden, bestimmte Zelltypen für Wiederverwendungen zwischenzuspeichern, sie aber dennoch in Interface Builder zu entwerfen?

Greg Martin
quelle

Antworten:

74

Implementieren Sie einfach eine Methode mit der entsprechenden Methodensignatur:

- (NSString *) reuseIdentifier {
  return @"myIdentifier";
}
Louis Gerbarg
quelle
Wo soll diese Methode implementiert werden?
Krishnan
2
In Ihrer UITableViewCell-Unterklasse. developer.apple.com/library/ios/#DOCUMENTATION/UIKit/Reference/…
DenTheMan
5
Das ist riskant. Was passiert, wenn Sie zwei Unterklassen Ihrer Zellenunterklasse haben und beide in einer einzigen Tabellenansicht verwenden? Wenn sie den Aufruf der Wiederverwendungskennung an super senden, wird eine Zelle des falschen Typs aus der Warteschlange entfernt. Ich denke, Sie müssen die Methode reuseIdentifier überschreiben, aber eine ersetzte Kennung zurückgeben Zeichenfolge.
SK9
3
Um sicher zu sein, dass es einzigartig ist, können Sie tun:return NSStringFromClass([self class]);
ivanzoid
119

Da Sie die Zelle in Interface Builder erstellen, legen Sie dort einfach die Wiederverwendungskennung fest:

IB_reuse_identifier

Wenn Sie Xcode 4 ausführen, überprüfen Sie die Registerkarte Attributinspektor:

Geben Sie hier die Bildbeschreibung ein

(Bearbeiten: Nachdem Ihr XIB von XCode generiert wurde, enthält es eine leere UIView, aber wir benötigen eine UITableViewCell. Sie müssen also die UIView manuell entfernen und eine Tabellenansichtszelle einfügen. Natürlich zeigt IB keine UITableViewCell-Parameter für a an UIView.)

Tim Keating
quelle
Was setze ich die Kennungen, wenn ich diese von der Feder erstellten Zellen für mehr als eine Zelle verwende? Dies führt dazu, dass wir zwei Zellen mit denselben Kennungen haben.
Krishnan
4
Stellen Sie sich das nicht als eindeutigen Bezeichner vor, sondern eher als Typnamen.
Tim Keating
Ich sehe nicht die Option, einen Bezeichner über den in Xcode 4.3.3 integrierten Builder für Schnittstellen festzulegen. Ich setze die Klasse definitiv auf meine UITableViewCell-Unterklasse. Vermisse ich es nur oder ist es weg?
Tyler
3
Ok, ich habe mein Problem gelöst. Wenn Sie mit einem UIView-Objekt im Interface Builder (innerhalb von Xcode 4) beginnen und dessen Klasse in UITableViewCell ändern, erhalten Sie keine zellenspezifischen Eigenschaften wie die Wiederverwendungskennung. Um dies zu erreichen, sollten Sie mit einer leeren xib beginnen und ein Tabellenzellenobjekt ziehen, das dann die zellenspezifischen Eigenschaften aufweist, die Sie bearbeiten können.
Tyler
1
@Krishnan Stellen Sie sich das so vor: Wenn Sie die Tabellenansichtszelle mit der Kennung X erstellen, sagen Sie "Geben Sie mir eine Zelle aus dem Pool mit der Bezeichnung X." Wenn der Pool existiert und sich dort eine freie Zelle befindet, gibt er sie Ihnen. Andernfalls wird der Pool erstellt (falls erforderlich), die Zelle aktualisiert, mit "X" gekennzeichnet und dann an Sie übergeben. Zellen KÖNNEN also eindeutig sein - z. B. könnten Sie einen Pool mit nur einer Zelle mit einer bestimmten Kennung erstellen -, aber die Bibliothek verwendet eine kostenlose listenähnliche Strategie, um Speicherzuweisung / Freigabe zu vermeiden.
Tim Keating
66

In iOS 5 gibt es dafür eine geeignete UITableView-Methode:

- (void)registerNib:(UINib *)nib forCellReuseIdentifier:(NSString *)identifier
wzs
quelle
10
Die anderen Antworten in diesem Thread, einschließlich der akzeptierten Antwort, enthalten veraltete Ratschläge.
Kaelin Colclasure
ist das abwärtskompatibel? Ich meine, wenn ich eine App mit SDK 5.0 entwickle und mindestens 4.0 als Ziel habe, läuft die App beispielsweise auf Geräten mit iOS 4.0?
Abolfoooud
1
Nein, dies ist nicht abwärtskompatibel, wie jede neue API in iOS 5.0.
Marzapower
Beispiel für die Integration in Ihren Controller: mindfiresolutions.com/…
mblackwell8
47

Ich kann mich nicht erinnern, wo ich diesen Code ursprünglich gefunden habe, aber er hat bisher für mich hervorragend funktioniert.

- (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;
}

Beispiel für die Einrichtung des Interface Builder ...

Alt-Text

Jrainbow
quelle
12

Schauen Sie sich die Antwort an, die ich auf diese Frage gegeben habe:

Ist es möglich, NSCell-Unterklassen in Interface Builder zu entwerfen?

Es ist nicht nur möglich, eine UITableViewCell in IB zu entwerfen, es ist auch wünschenswert, da ansonsten die gesamte manuelle Verkabelung und Platzierung mehrerer Elemente sehr mühsam ist. Die Leistung ist in Ordnung, solange Sie darauf achten, dass alle Elemente nach Möglichkeit undurchsichtig sind. Die Wiederverwendungs-ID wird in IB für die Eigenschaften der UITableViewCell festgelegt. Anschließend verwenden Sie die übereinstimmende Wiederverwendungs-ID im Code, wenn Sie versuchen, die Warteschlange zu entfernen.

Ich habe auch von einigen Moderatoren auf der WWDC im letzten Jahr gehört, dass Sie in IB keine Tabellenansichtszellen erstellen sollten, aber es ist eine Menge Koje.

Kendall Helmstetter Gelner
quelle
2
Sie sollten in IB keine Tabellenansichtszellen erstellen, wenn Sie Transparenz benötigen und eine gute Bildlaufleistung wünschen. Für bestimmte Benutzeroberflächen benötigen Sie Transparenz (z. B. um Text über Grafiken zu rendern). Manchmal besteht die einzige Möglichkeit, auf älterer Hardware (vor A4) einen guten Bildlauf zu erzielen, darin, Code zu rendern, um zu vermeiden, dass die GPU mehrere transparente Ebenen zusammensetzen muss.
Nick Forge
2
Stimmt, aber selbst dann ist es möglicherweise besser, die Leistung auf alten Geräten leicht leiden zu lassen, um die Wartbarkeit der in IB eingebauten Zellen zu vereinfachen. Sie können diese Technik auch als Vorlagenzelle verwenden, aus der Sie dann Elemente zeichnen, um das Zusammensetzen mit einer benutzerdefinierten Zeichenmethode zu vermeiden.
Kendall Helmstetter Gelner
6

Hier ist eine weitere Option:

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
quelle
Beachten Sie, dass dies die einzige Lösung ist, die bisher veröffentlicht wurde und für die keine benutzerdefinierte Unterklasse erforderlich ist UITableViewCell, um einen eindeutigen Wert für festzulegen reuseIdentifer. Ich denke, das ist es, wonach die ursprüngliche Operation tatsächlich gesucht hat.
Charshep
Ich hoffe, Apple wird meine Anwendung nicht ablehnen, um dies zu verwenden ... Ich verwende dies, um "statische" Zellen zu erhalten, da in meiner Tabellenansicht das Füllen einer Zelle ziemlich langsam ist. Auf diese Weise muss ich es nur einmal ausführen (jeder Zeile einen anderen Bezeichner geben). Danke dir!
Ricard Pérez del Campo
Sehr hilfreich, da es keine UITableViewCell-Methode gibt, um dies manuell einzustellen!
Jesse
2

Ich erstelle meine benutzerdefinierten Ansichtszellen auf ähnliche Weise - außer ich verbinde die Zelle über ein IBOutlet.

Der [nib objectAt...]Ansatz ist anfällig für Änderungen der Positionen von Elementen im Array.

Der UIViewControllerAnsatz ist gut - habe es einfach versucht und es funktioniert gut genug.

ABER...

In allen Fällen wird der initWithStyleKonstruktor NICHT aufgerufen, sodass keine Standardinitialisierung durchgeführt wird.

Ich habe verschiedene Stellen über die Verwendung von initWithCoderoder gelesen awakeFromNib, aber keine schlüssigen Beweise dafür, dass beides der richtige Weg ist.

Abgesehen davon, dass cellForRowAtIndexPathich explizit eine Initialisierungsmethode in der Methode aufrufe, habe ich noch keine Antwort darauf gefunden.

sww
quelle
awakeFromNib ist der richtige Weg, um auf ein Objekt zu reagieren, das von einer NIB geladen wird.
Jon Hess
2

Vor einiger Zeit habe ich unter blog.atebits.com einen großartigen Blog-Beitrag zu diesem Thema gefunden und seitdem die ABTableViewCell-Klasse von Loren Brichter verwendet, um alle meine UITableViewCells zu erstellen.

Sie erhalten ein einfaches Container-UIView, in das Sie alle Ihre Widgets einfügen können, und das Scrollen ist blitzschnell.

Hoffe das ist nützlich.

Eric
quelle
2

Diese Technik funktioniert auch und erfordert kein funky ivar in Ihrem View Controller für die Speicherverwaltung. Hier befindet sich die Zelle der benutzerdefinierten Tabellenansicht in einer xib mit dem Namen "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
quelle
1
Archivierung und Archivierung sind völlig unnötig.
Bryan Henry
1
Das Archivieren / Aufheben der Archivierung ist nicht erforderlich, wenn Sie die Zelle immer dann von ihrer Spitze laden können, wenn sie nicht aus der Warteschlange entfernt werden kann. Wenn Sie die Zelle jedoch nur genau einmal von ihrer Spitze laden möchten, müssen Sie sie im Speicher zwischenspeichern. Ich führe dieses Caching mit NSKeyedArchiving durch, weil UITableViewCell NSCopying nicht implementiert.
Bill Garrison
1
Alles in allem wird mit UINib zum Laden der Zelle der gleiche Effekt erzielt: einmal von der Festplatte laden, danach aus dem Speicher laden.
Bill Garrison
2

Die Louis-Methode hat bei mir funktioniert. Dies ist der Code, mit dem ich die UITableViewCell aus der Feder erstelle:

- (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
quelle
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
quelle
1

Die gustavogb-Lösung funktioniert bei mir nicht. Ich habe Folgendes versucht:

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

Es scheint zu funktionieren. Die blogTableViewCell ist das IBOutlet für die Zelle und ChainesController ist der Eigentümer der Datei.

groumpf
quelle
1

In den UITableView-Dokumenten zu dequeueWithReuseIdentifierfolgenden Themen : "Eine Zeichenfolge, die das wiederverwendbare Zellenobjekt identifiziert. Standardmäßig ist der Bezeichner einer wiederverwendbaren Zelle der Klassenname. Sie können ihn jedoch in einen beliebigen Wert ändern."

Das Überschreiben von -reuseIdentifer selbst ist riskant. Was passiert, wenn Sie zwei Unterklassen Ihrer Zellenunterklasse haben und beide in einer einzigen Tabellenansicht verwenden? Wenn sie den Aufruf der Wiederverwendungskennung an super senden, wird eine Zelle des falschen Typs aus der Warteschlange entfernt. Ich denke, Sie müssen die Methode reuseIdentifier überschreiben, aber eine ersetzte Kennung zurückgeben Zeichenfolge. Wenn eine nicht angegeben wurde, geben Sie die Klasse als Zeichenfolge zurück.

SK9
quelle
0

Für das, was es wert ist, habe ich einen iPhone-Ingenieur bei einem der iPhone Tech Talks danach gefragt. Seine Antwort war: "Ja, es ist möglich, mit IB Zellen zu erstellen. Aber nicht. Bitte nicht."

August
quelle
1
Das ist seltsam. Mindestens zwei der Vorträge auf dem NY-Vortrag hatten Demo-Code, der in IB erstellte Zellen verwendete.
Shawn Craver
3
Ich bin mir nicht sicher, ob ich das glaube, da sie IB verwenden, um Zellen in Apples Beispielprojekt Advanced Table View Cells zu erstellen.
Ich wurde
Dank dafür. Jedes Mal, wenn ich es getan habe, bin ich auf Probleme gestoßen. Dies könnte der Grund sein
Skorulis
Er hatte wahrscheinlich nicht genug Wissen darüber.
Aryaxt
0

Ich habe die Anweisungen von Apple befolgt, die von Ben Mosher verlinkt wurden (danke!), Aber festgestellt, dass Apple einen wichtigen Punkt ausgelassen hat. Das Objekt, das sie in IB entwerfen, ist nur eine UITableViewCell, ebenso wie die Variable, die sie daraus laden. Wenn Sie es jedoch tatsächlich als benutzerdefinierte Unterklasse von UITableViewCell einrichten und die Codedateien für die Unterklasse schreiben, können Sie IBOutlet-Deklarationen und IBAction-Methoden in den Code schreiben und diese mit Ihren benutzerdefinierten Elementen in IB verbinden. Dann müssen Sie keine Ansichts-Tags verwenden, um auf diese Elemente zuzugreifen, und Sie können jede Art von verrückter Zelle erstellen, die Sie möchten. Es ist Cocoa Touch Himmel.

David Casseres
quelle