web-dev-qa-db-fra.com

Référence de UITableViewCell au parent UITableView?

Existe-t-il un moyen d'accéder au UITableView propriétaire depuis un UITableViewCell?

67
Michael Grinich

Stockez une référence weak à tableView dans la cellule, que vous auriez définie dans -tableView:cellForRowAtIndexPath: de la source de données de votre table.

C'est mieux que de compter sur self.superview pour toujours être exactement la tableView est fragile. Qui sait comment Apple pourrait réorganiser la hiérarchie des vues de UITableView à l'avenir.

116
jbrennan

Voici une meilleure façon de le faire, qui ne repose sur aucune hiérarchie UITableView particulière. Il fonctionnera avec n'importe quelle future version iOS, à condition que UITableView ne change pas complètement le nom de classe. Non seulement cela est extrêmement improbable, mais si cela se produit, vous devrez quand même retoucher votre code.

Importez simplement la catégorie ci-dessous et obtenez votre référence avec [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.


MISE À JOUR
Il y a une discussion dans les commentaires pour savoir si le maintien d'une référence faible est une meilleure approche. Cela dépend de votre situation. La traversée de la hiérarchie des vues a une petite pénalité d'exécution pendant que vous bouclez jusqu'à ce que l'UIView cible soit identifié. Quelle est la profondeur de vos opinions? D'un autre côté, garder une référence sur chaque cellule a une pénalité mémoire minimale (une référence faible est un pointeur après tout), et généralement ajouter des relations d'objet là où elles ne sont pas nécessaires est considéré comme mauvais OO = pratique de conception pour de nombreuses raisons, et doit être évitée (voir les détails dans les commentaires ci-dessous).

Plus important encore, la conservation des références de table à l'intérieur des cellules ajoute de la complexité au code et peut entraîner des erreurs, car UITableViewCells sont réutilisables. Ce n'est pas un hasard si UIKit n'inclut pas de cell.parentTable propriété. Si vous définissez le vôtre, vous devez ajouter du code pour le gérer, et si vous ne le faites pas efficacement, vous pouvez introduire des fuites de mémoire (c'est-à-dire que les cellules vivent après la durée de vie de leur table).

Parce que généralement, vous utiliserez la catégorie ci-dessus lorsqu'un utilisateur interagit avec une cellule (exécutez pour une seule cellule), et non lors de la disposition du tableau dans [tableView:cellForRowAtIndexPath:] (exécuter pour toutes les cellules visibles), le coût d'exécution doit être insignifiant.

18
DTs

Xcode 7 beta, Swift 2.

Cela fonctionne très bien pour moi, à mon avis, cela n'a rien à voir avec la hiérarchie ou quoi que ce soit. Jusqu'à présent, je n'ai eu aucun problème avec cette approche. Je l'ai utilisé pour de nombreux rappels asynchrones (par exemple, lorsqu'une demande d'API est effectuée).

classe TableViewCell

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)

    }
}

à l'intérieur de la classe de vue de table qui implémente le DataSource et le délégué

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

Bien sûr, vous pouvez mettre n'importe quelle variable dans le updateCallback et changer sa fonction dans le tableView en conséquence.

Quelqu'un voudra peut-être me dire s'il est préférable de l'utiliser, juste pour être sûr.

13
CularBytes

Vous devez ajouter une référence à UITableView lorsque vous construisez la cellule de vue de table.

Cependant, presque certainement ce que vous voulez vraiment est une référence à votre UITableViewController ... qui nécessite la même chose, définissez-le comme délégué de la cellule lorsque vous construisez la cellule et remettez-le à la vue de table.

Une autre approche si vous connectez des actions consiste à créer les cellules dans IB, avec le contrôleur de vue de table en tant que propriétaire des fichiers, puis à câbler les boutons de la cellule aux actions du contrôleur de vue de table. Lorsque vous chargez la cellule xib avec loadNibNamed, transmettez le contrôleur de vue en tant que propriétaire et les actions des boutons seront reliées au contrôleur de vue de table.

Si vous avez des classes personnalisées pour vos UITableViewCells, vous pouvez ajouter une variable de type id dans l'en-tête de votre cellule et synthétiser la variable. Après avoir défini la variable lorsque vous chargez la cellule, vous êtes libre de faire ce que vous voulez avec la vue de table ou toute autre vue supérieure sans trop de tracas ou de surcharge.

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

Vous pouvez maintenant appeler n'importe quelle méthode de la table vue depuis votre cellule à l'aide de la variable racine. (par exemple, [root reloadData]);

Ah, me ramène au bon vieux temps de la programmation flash.

4
Sold Out Activist

Les deux méthodes dans les autres réponses sont: (A) stocker une référence à la table, ou (B) remonter les superviews.

J'utiliserais toujours quelque chose comme (A) pour les objets du modèle et (B) pour les cellules du tableau.

Cellules

Si vous traitez avec un UITableViewCell, alors AFAIK vous devez soit avoir le UITableView à portée de main (par exemple, vous êtes dans une méthode de délégué de table), ou traitez avec une cellule visible qui se trouve dans la hiérarchie des vues. Sinon, vous pourriez bien faire quelque chose de mal (veuillez noter le "peut bien").

Les cellules sont réutilisées généreusement et si vous en avez une qui n'est pas visible, la seule vraie raison pour laquelle la cellule existe est en raison de l'optimisation des performances iOS UITableView (une version iOS plus lente aurait été publiée et, espérons-le, désallouée la cellule lorsqu'elle serait sortie de l'écran) ) ou parce que vous y avez une référence spécifique. Je suppose que c'est probablement la raison pour laquelle les cellules de tableau ne sont pas dotées d'une méthode d'instance tableView.

Donc (B) donne le bon résultat pour tous les iOS jusqu'à présent, et tous les futurs jusqu'à ce qu'ils changent radicalement le fonctionnement des vues.

Bien que pour éviter d'écrire du code généralisable encore et encore, j'utiliserais ceci:

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

et une méthode pratique:

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

(ou catégories si vous le souhaitez)

BTW, si vous êtes préoccupé par l'effet d'une boucle avec environ 20 itérations de cette taille sur les performances de votre application, ne le faites pas.

Des modèles

Si vous parlez de l'objet modèle affiché dans la cellule, alors ce modèle pourrait/devrait certainement connaître son modèle parent, qui peut être utilisé pour rechercher ou déclencher des modifications dans le (s) tableau (s) que le modèle de la cellule pourrait s'affiche comme (A), mais moins fragile avec les futures mises à jour iOS (par exemple, un jour, elles pourraient faire en sorte que le cache de réutilisation UITableViewCell existe par identifiant de réutilisation, plutôt que par identifiant de réutilisation par vue de table, ce jour-là, toutes les implémentations qui utilisent le faible la méthode de référence va casser).

La méthode Th modèle serait utilisée pour les modifications des données affichées dans la cellule (c'est-à-dire les modifications du modèle) car les modifications se propageront partout où le modèle est affiché (par exemple, un autre UIViewController ailleurs dans l'application, la journalisation, ...)

La méthode cellule serait utilisée pour les actions de table, ce qui serait probablement toujours une mauvaise idée si la cellule n'est même pas une sous-vue d'une table (bien que ce soit votre code, devenez fou).

Quoi qu'il en soit, utilisez un test unitaire plutôt que de supposer qu'un code apparemment plus propre ne fonctionne que lors de la mise à jour d'iOS.

1
wils
UITableView *tv = (UITableView *) self.superview.superview;
UITableViewController *vc = (UITableViewController *) tv.dataSource;
1
Gank