Verweis von UITableViewCell auf übergeordnetes UITableView?

68

Gibt es eine Möglichkeit, UITableViewinnerhalb von a auf das Eigentum zuzugreifen UITableViewCell?

Michael Grinich
quelle

Antworten:

120

Speichern Sie einen weakVerweis auf die tableView in der Zelle, die Sie in -tableView:cellForRowAtIndexPath:der dataSource Ihrer Tabelle festgelegt haben.

Dies ist besser, als sich darauf self.superviewzu verlassen , dass immer genau die tableView zerbrechlich ist. Wer weiß, wie Apple UITableViewin Zukunft die Ansichtshierarchie neu organisieren könnte.

jbrennan
quelle
12
Aus Sicherheitsgründen möchten Sie möglicherweise eine Prüfung '[self.superview isKindOfClass: [UITableView class]]' hinzufügen, falls sich die Ansichtshierarchie in Zukunft ändert.
Frank Szczerba
3
Tatsächlich scheint dies ein bisschen zu hacken, denn was ist, wenn einige Tabellenentwürfe zuerst einige Container haben und diese die Zellen enthalten cell.superview? In diesem Fall handelt es sich möglicherweise nicht um die Tabelle. Daher wird hier von der UITableView-Implementierung
ausgegangen
4
Ja, sie haben dies mit IOS 7
geändert
2
Sie können Unteransichten durchlaufen, ohne sich auf eine bestimmte Ansichtshierarchie verlassen zu müssen, wie im CategoryFolgenden vorgeschlagen. Dies garantiert, dass es in zukünftigen Versionen funktioniert. Auf diese Weise müssen Sie Referenzen nicht über Ihren gesamten Code verteilen und die Objektkopplung erhöhen.
DTs
2
@JamesDonald Apple hat auch keine "parentTableView" -Methode hinzugefügt, daher ist nicht ganz klar, was Apple so oder so motiviert hat. In vielen Fällen wird kein Verweis auf die Tabelle benötigt, sodass das Hinzufügen eines Verweises Bytes zerkauen würde. Das Hinzufügen einer Referenz kann zu Problemen führen, da Benutzer dazu neigen, auf die Tabelle in der Zelle zu verweisen. Das Konfigurieren der Zelle kann dann versehentlich die Tabelle aufrufen, wenn die Zelle wiederverwendet wird. Angesichts der Einfachheit des Codes gehe ich mit einer schwachen Referenz. Verwenden Sie lieber ein wenig Speicher, als die Batterie einer Person zu zerkauen, die ständig in der Hierarchie aufsteigt.
Gerard
18

Hier ist eine schönere Möglichkeit, die sich nicht auf eine bestimmte UITableView-Hierarchie stützt. Es funktioniert mit jeder zukünftigen iOS-Version, vorausgesetzt, UITableViewder Klassenname wird nicht vollständig geändert. Nicht nur das ist äußerst unwahrscheinlich, auch wenn dies passiert, müssen Sie Ihren Code trotzdem retuschieren.

Importieren Sie einfach die unten stehende Kategorie und erhalten Sie Ihre Referenz mit [myCell parentTableView]

@implementation UIView (FindUITableView)

-(UITableView *) parentTableView {
    // iterate up the view hierarchy to find the table containing this cell/view
    UIView *aView = self.superview;
    while(aView != nil) {
        if([aView isKindOfClass:[UITableView class]]) {
            return (UITableView *)aView;
        }
        aView = aView.superview;
    }
    return nil; // this view is not within a tableView
}

@end


// To use it, just import the category and invoke it like so:
UITableView *myTable = [myTableCell parentTableView];

// It can also be used from any subview within a cell, from example
// if you have a UILabel within your cell, you can also do:
UITableView *myTable = [myCellLabel parentTableView];

// NOTE:
// If you invoke this on a cell that is not part of a UITableView yet
// (i.e., on a cell that you just created with [[MyCell alloc] init]),
// then you will obviously get nil in return. You need to invoke this on cells/subviews
// that are already part of a UITableView.


UPDATE
In den Kommentaren wird diskutiert, ob es besser ist, eine schwache Referenz beizubehalten. Es hängt von Ihren Umständen ab. Das Durchlaufen der Ansichtshierarchie hat eine kleine Laufzeitstrafe, da Sie eine Schleife ausführen, bis die Ziel-UIView identifiziert ist. Wie tief sind deine Ansichten? Auf der anderen Seite hat das Behalten einer Referenz in jeder Zelle eine minimale Speicherstrafe (eine schwache Referenz ist schließlich ein Zeiger), und das Hinzufügen von Objektbeziehungen, bei denen sie nicht benötigt werden, wird aus vielen Gründen als schlechte OO-Entwurfspraxis angesehen und sollte dies auch tun vermieden werden (siehe Details in den Kommentaren unten).

Noch wichtiger ist, dass das Beibehalten von Tabellenreferenzen in Zellen die Codekomplexität erhöht und zu Fehlern führen kann, da sie UITableViewCellswiederverwendbar sind. Es ist kein Zufall, dass UIKitkeine cell.parentTableEigenschaft enthalten ist. Wenn Sie Ihren eigenen definieren, müssen Sie Code hinzufügen, um ihn zu verwalten. Wenn Sie dies nicht effektiv tun, können Sie Speicherlecks einführen (dh Zellen leben über die Lebensdauer ihrer Tabelle hinaus).

Da Sie normalerweise die oben genannte Kategorie verwenden, wenn ein Benutzer mit einer Zelle interagiert (für eine einzelne Zelle ausführen) und nicht, wenn Sie die Tabelle [tableView:cellForRowAtIndexPath:]auslegen (für alle sichtbaren Zellen ausführen), sollten die Laufzeitkosten unbedeutend sein.

DTs
quelle
3
@mattcurtis Wenn Sie zu Fuß gehen, sollten Sie sich fragen, warum? Und diese schlechte Idee lässt sich am besten durch die Änderung von iOS 7 in UITableViewCell veranschaulichen, die dazu führt, dass dies immer Null zurückgibt. Eine schwache Referenz ist der richtige Weg, wenn Sie die Tabellenansicht aus der Zelle abrufen möchten.
Cameron Lowell Palmer
4
@CameronLowellPalmer Nein, dies funktioniert in iOS7 immer noch einwandfrei und wird auch in zukünftigen iOS-Versionen funktionieren. Viele unserer Apps im App Store verwenden diesen Code weiterhin problemlos. Platzieren Sie ihn einfach an der richtigen Stelle, wie in den Codekommentaren vorgeschlagen. Das Speichern von Referenzen überall fördert kein sauberes Design, und viele Designbücher schlagen vor, dass dies vermieden wird.
DTs
3
@CameronLowellPalmer Wenn Sie möchten, dass die Leser Ihre Meinung berücksichtigen, ist es hilfreich, dies zu begründen. Eine Idee als Dogma zu bezeichnen, ohne das Problem zu veranschaulichen, hilft niemandem, sie zu bewerten oder daraus zu lernen. Der obige Code ist allgemein gehalten und bremst nicht. Ich habe Kommentare abgegeben, um die Vor- und Nachteile des schwachen Referenzansatzes aufzuzeigen. Wenn Sie noch etwas beizutragen haben, gehen Sie bitte näher darauf ein. Bisher haben wir gesehen, dass Sie die Funktionsweise des obigen Codes nicht vollständig verstanden haben, da Sie fälschlicherweise behauptet haben, dass er in iOS7 nicht funktioniert.
DTs
3
@CameronLowellPalmer gutes OO-Design und insbesondere das Demeter-Gesetz besagen, dass "Einheiten nur begrenzte Kenntnisse über andere Einheiten haben sollten und dies nur tun, wenn dies unbedingt erforderlich ist". Dies soll die lose Kopplung und Wiederverwendbarkeit der Einheit fördern. Das Beibehalten von Verweisen auf Unteransichten stellt in der Tat eine Verletzung von LoD dar und durchläuft die Ansichten nicht unabhängig voneinander in einer Ansichtshierarchie. Letzteres fördert eine lose Kopplung und ein gutes wiederverwendbares OO-Design und respektiert tatsächlich die LoD. Vielleicht gefällt es Ihnen nicht, Unteransichten zu durchlaufen, während Sie sich auf eine feste, fest codierte Hierarchie verlassen, was hier nicht der Fall ist.
DTs
3
@CameronLowellPalmer a UITableViewCellist durch das Design eingeschränkt UIKit, unter a zu leben UITableView. Wenn Sie auf eine Weise nach einer Instanz suchen, entstehen keine zusätzlichen Beziehungen. Das soll dann in Software Eng sein. Im Gegensatz dazu wird das Beibehalten eines Verweises auf eine bestimmte Instanz innerhalb von a als enge Kopplung angesehen, da eine direkte Beziehung eingeführt wird. Es ist kein Zufall, dass in UIKit keine Immobilie angeboten wird. UITableViewUIView hierarchy-agnosticUITableViewCellloosely coupled UITableViewUITableViewCellUITableViewCell.parentTable
DTs
14

Xcode 7 Beta, Swift 2.0

Das funktioniert gut für mich, meiner Meinung nach hat es nichts mit der Hierarchie oder was auch immer zu tun. Ich hatte bisher keine Probleme mit diesem Ansatz. Ich habe dies für viele asynchrone Rückrufe verwendet (z. B. wenn eine API-Anforderung ausgeführt wird).

TableViewCell-Klasse

class ItemCell: UITableViewCell {

    var updateCallback : ((updateList: Bool)-> Void)? //add this extra var

    @IBAction func btnDelete_Click(sender: AnyObject) {
        let localStorage = LocalStorage()
        if let description = lblItemDescription.text
        {
            //I delete it here, but could be done at other class as well.
            localStorage.DeleteItem(description) 
        }
        updateCallback?(updateList : true)

    }
}

Innerhalb der Tabellenansichtsklasse, die DataSource und Delegate implementiert

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell: ItemCell = self.ItemTableView.dequeueReusableCellWithIdentifier("ItemCell") as! ItemCell!
    cell.updateCallback = UpdateCallback //add this extra line
    cell.lblItemDescription?.text = self.SomeList[indexPath.row].Description
    return cell
}

func UpdateCallback(updateTable : Bool) //add this extra method
{
    licensePlatesList = localStorage.LoadNotificationPlates()
    LicenseTableView.reloadData()
}

Natürlich können Sie jede Variable in die einfügen updateCallbackund ihre Funktion tableViewentsprechend ändern .

Jemand möchte mir vielleicht sagen, ob es sicher ist, es zu verwenden, nur um sicherzugehen.

CularBytes
quelle
Ich habe diesen Code für zwei verschiedene Zellen implementiert. In der ersten Zelle habe ich updateCallback, für die zweite Zelle habe ich updateCallback2. Ich weiß zu 100%, dass alle meine Methoden und Variablen korrekt benannt sind. UpdateCallback2 ist jedoch null (updateCallback ist in Ordnung). Was könnte dazu führen, dass es gleich Null ist?
Schuey999
Stellen Sie es ein? cell.updateCallback2 = UpdateCallback2?
CularBytes
Vielen Dank für diese Antwort. Wenn ich hinzufügen darf, können Sie die Überprüfung von updateCallback durch die optionale Verkettung wie folgt ersparen : updateCallback?(updateList : true). Wenn der Rückruf eingestellt ist, erfolgt der Anruf.
Vguerra
7

Sie müssen einen Verweis zurück zur UITableView hinzufügen, wenn Sie die Tabellenansichtszelle erstellen.

Mit ziemlicher Sicherheit möchten Sie jedoch einen Verweis auf Ihren UITableViewController, der dasselbe erfordert. Legen Sie ihn beim Erstellen der Zelle als Delegaten der Zelle fest und übergeben Sie ihn der Tabellenansicht.

Ein alternativer Ansatz, wenn Sie Aktionen verkabeln, besteht darin, die Zellen in IB mit dem Tabellenansichts-Controller als Dateieigentümer zu erstellen und dann die Schaltflächen in der Zelle mit den Aktionen im Tabellenansichts-Controller zu verbinden. Wenn Sie die Zelle xib mit loadNibNamed laden, übergeben Sie den Ansichts-Controller als Eigentümer, und die Schaltflächenaktionen werden mit dem Tabellenansichts-Controller verbunden.

Kendall Helmstetter Gelner
quelle
4

Wenn Sie benutzerdefinierte Klassen für Ihre UITableViewCells haben, können Sie eine ID vom Typ id in den Header Ihrer Zelle einfügen und die Variable synthetisieren. Nachdem Sie die Variable beim Laden der Zelle festgelegt haben, können Sie mit der Tabellenansicht oder einer anderen höheren Ansicht ohne großen Aufwand und Aufwand tun, was Sie möchten.

cell.h

 // interface
 id root;

 // propery 
 @property (nonatomic, retain) id root;

cell.m

@synthesize root;

tableviewcontroller.m

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  // blah blah, traditional cell declaration
  // but before return cell;
  cell.root = tableView;
}

Jetzt können Sie mithilfe der Stammvariablen eine der Methoden der Tabellenansicht aus Ihrer Zelle heraus aufrufen. (zB [root reloadData]);

Ah, bringt mich zurück zu den guten alten Zeiten der Flash-Programmierung.


quelle
6
Sie möchten jedoch nicht, dass es beibehalten wird. Die Tabellenansicht besitzt normalerweise die Zelle. Das wäre besser @property (nonatomic, assign) id root;
João Josézinho
1
UITableView *tv = (UITableView *) self.superview.superview;
UITableViewController *vc = (UITableViewController *) tv.dataSource;
Gank
quelle
1

Die beiden Methoden in anderen Antworten sind: (A) Speichern eines Verweises auf die Tabelle oder (B) Aufrufen der Übersichten.

Ich würde immer so etwas wie (A) für Modellobjekte und (B) für Tabellenzellen verwenden.

Zellen

Wenn Sie mit einer UITableViewCell arbeiten, müssen Sie bei AFAIK entweder die UITableView zur Hand haben (sagen wir, Sie befinden sich in einer Tabellendelegatmethode) oder mit einer sichtbaren Zelle, die sich in der Ansichtshierarchie befindet. Andernfalls machen Sie möglicherweise etwas falsch (bitte beachten Sie das "möglicherweise").

Zellen werden großzügig wiederverwendet. Wenn Sie eine Zelle haben, die nicht sichtbar ist, liegt der einzige wirkliche Grund für die Existenz der Zelle in der Leistungsoptimierung von iOS UITableView (eine langsamere iOS-Version hätte die Zelle freigegeben und hoffentlich freigegeben, wenn sie vom Bildschirm verschoben wurde ) oder weil Sie einen bestimmten Verweis darauf haben. Ich denke, dies ist wahrscheinlich der Grund, warum Tabellenzellen nicht mit einer tableView-Instanzmethode ausgestattet sind.

(B) liefert also das richtige Ergebnis für alle bisherigen iOS-Versionen und alle zukünftigen, bis sie die Funktionsweise von Ansichten radikal ändern.

Um zu vermeiden, dass verallgemeinerbarer Code immer wieder geschrieben wird, würde ich Folgendes verwenden:

+ (id)enclosingViewOfView:(UIView *)view withClass:(Class)returnKindOfClass {
  while (view&&![view isKindOfClass:returnKindOfClass]) view=view.superview;
  return(view);
}

und eine bequeme Methode:

+ (UITableView *)tableForCell:(UITableViewCell *)cell {
  return([self enclosingViewOfView:cell.superview withClass:UITableView.class]);
}

(oder Kategorien, wenn Sie möchten)

Übrigens, wenn Sie über die Auswirkung einer Schleife mit etwa 20 Iterationen dieser Größe auf die Leistung Ihrer App besorgt sind, tun Sie dies nicht.

Modelle

Wenn Sie über das Modellobjekt sprechen, das in der Zelle angezeigt wird, dann könnte / sollte dieses Modell definitiv über sein übergeordnetes Modell Bescheid wissen, das verwendet werden kann, um Änderungen in den Tabellen zu finden oder auszulösen, die das Modell der Zelle möglicherweise enthält angezeigt werden in. Dies ist wie (A), jedoch weniger spröde bei zukünftigen iOS-Updates (z. B. kann es vorkommen, dass der UITableViewCell-Wiederverwendungscache eines Tages pro Wiederverwendungskennung und nicht pro Wiederverwendungskennung pro Tabellenansicht an diesem Tag für alle Implementierungen vorhanden ist, die die Schwachen verwenden Referenzmethode wird brechen).

Die Modellmethode wird für Änderungen an den in der Zelle angezeigten Daten verwendet (dh Modelländerungen), da sich Änderungen überall dort ausbreiten, wo das Modell angezeigt wird (z. B. ein anderer UIViewController an einer anderen Stelle in der App, Protokollierung, ...).

Die Zelle Methode würde für Tableview Aktionen verwendet werden, die wahrscheinlich immer eine schlechte Idee wäre , wenn die Zelle nicht einmal ein Subview einer Tabelle ist (obwohl es ist der Code, geht Nüsse).

Verwenden Sie in beiden Fällen einen Komponententest, anstatt davon auszugehen, dass scheinbar sauberer Code nur funktioniert, wenn iOS aktualisiert wird.

wils
quelle