Comment obtenir UITableView de UITableViewCell?

100

J'ai un UITableViewCellqui est lié à un objet et j'ai besoin de dire si la cellule est visible. D'après les recherches que j'ai effectuées, cela signifie que je dois accéder d'une manière ou d'une autre au UITableViewqui le contient (à partir de là, il existe plusieurs façons de vérifier s'il est visible). Donc je me demande si UITableViewCella un pointeur vers le UITableView, ou s'il y avait un autre moyen d'obtenir un pointeur de la cellule?

sinθ
la source
2
Quel est le but de cela?
max_
[cell superView]peut être?
Chris Loonam
6
Cela vaut la peine d'expliquer pourquoi vous pensez en avoir besoin - car cela peut être un signe de mauvaise conception car je ne peux pas vraiment penser à de nombreuses raisons légitimes pour qu'une cellule sache si elle est à l'écran ou non.
Paul.s
@ Paul.s Nous avons un outil de reconnaissance de gestes sur une image dans une cellule et lorsque la cellule est touchée, il ouvre une autre vue de superposition, pensez au style popover, qui devrait superposer autant de cellules que nécessaire pour s'afficher correctement. Pour que cela fonctionne, il a besoin de la TableView ou d'une autre vue qui lui est donnée pour s'afficher. Pas vraiment satisfait des solutions, mais pour obtenir l'effet souhaité, obtenir le UITableView de UITableViewCell est le meilleur que nous ayons proposé.
chadbag
1
@chadbag pas de soucis, j'espère avoir donné une idée à quelqu'un d'autre avec le même problème.
PJ_Finnegan

Réponses:

153

Pour éviter de vérifier la version iOS, remontez de manière itérative les superviews depuis la vue de la cellule jusqu'à ce qu'un UITableView soit trouvé:

id view = [tableViewCellInstance superview];

while (view && [view isKindOfClass:[UITableView class]] == NO) {
    view = [view superview]; 
}

UITableView *tableView = (UITableView *)view;
Muhammad Idris
la source
1
Merci. Il semblerait que cela ait à nouveau changé dans iOS 8 et que cela prend en charge toutes les versions bien.
djskinner
2
Je recommanderais d'ajouter une référence faible à la tableview à la cellule pour éviter les problèmes de compatibilité dans les futures mises à jour.
Cenny
1
Plutôt une manière faible. Cela ne fonctionnera pas si la hiérarchie de la vue change à l'avenir.
RomanN
2
Il serait préférable de créer simplement une propriété faible qui contient en fait un pointeur vers la vue table. Dans la sous-classe @property (weak, nonatomic) UITableView *tableView;et dans tableView:cellForRowAtIndexPath:juste ensemble cell.tableView = tableView;.
Alejandro Iván
L'expérience actuelle est qu'après avoir retiré la file d'attente d'une cellule, une vue de table n'apparaît pas comme une vue de supervision de la cellule tout de suite - si je fais défiler la cellule hors de la vue et de nouveau, je trouve une vue de tableau en tant que vue de supervision (recherche récursive comme décrit dans autres réponses). La mise en page automatique et éventuellement d'autres facteurs sont des raisons probables.
Jonny
48

Dans iOS7 beta 5 UITableViewWrapperViewest la supervision d'un UITableViewCell. Est également UITableViewsuperview d'un UITableViewWrapperView.

Donc, pour iOS 7, la solution est

UITableView *tableView = (UITableView *)cell.superview.superview;

Donc, pour les iOS jusqu'à iOS 6, la solution est

UITableView *tableView = (UITableView *)cell.superview;

KGS-12
la source
5
Oh mec, c'est un changement brutal d'API. Quelle est la meilleure pratique d'Apple pour la création de branches si vous prenez en charge à la fois 6 et 7?
Ryan Romanchuk du
@RyanRomanchuk Voici une bonne suggestion: devforums.apple.com/message/865550#865550 - créez un pointeur faible vers votre tableView associée lorsque la cellule est créée. Sinon, créez une UITableViewCellcatégorie avec une nouvelle méthode, relatedTableViewqui vérifie une version iOS et renvoie la superView appropriée.
memmons le
3
Écrivez une catégorie récursive sur UIView pour cela. Vous n'avez pas besoin de vérifier la version; appelez simplement superview jusqu'à ce que vous trouviez une cellule de vue de tableau ou le haut de la pile de vues. C'est ce que j'ai utilisé dans iOS 6, et cela a fonctionné sans modification dans iOS 7. Et devrait fonctionner dans iOS 8.
Steven Fisher
Demander pardon; Je voulais dire jusqu'à ce que vous trouviez la vue de la table. :)
Steven Fisher
39

Extension Swift 5

Récursivement

extension UIView {
    func parentView<T: UIView>(of type: T.Type) -> T? {
        guard let view = superview else {
            return nil
        } 
        return (view as? T) ?? view.parentView(of: T.self)
    }
}

extension UITableViewCell {
    var tableView: UITableView? {
        return parentView(of: UITableView.self)
    }
}

Utilisation de la boucle

extension UITableViewCell {
    var tableView: UITableView? {
        var view = superview
        while let v = view, v.isKind(of: UITableView.self) == false {
            view = v.superview
        }
        return view as? UITableView
    }
}
Orkhan Alikhanov
la source
Une règle de base est de ne pas utiliser la force pour déballer
thibaut noah
1
@thibautnoah Il y a une view != nilvérification avant de déballer. Mis à jour pour un code plus propre cependant
Orkhan Alikhanov
25

Avant iOS7, la supervision de la cellule était celle UITableViewqui la contenait. À partir d'iOS7 GM (donc probablement dans la version publique), la supervision de la cellule est une UITableViewWrapperViewavec sa supervision étant le UITableView. Il existe deux solutions au problème.

Solution n ° 1: créer une UITableViewCellcatégorie

@implementation UITableViewCell (RelatedTable)

- (UITableView *)relatedTable
{
    if ([self.superview isKindOfClass:[UITableView class]])
        return (UITableView *)self.superview;
    else if ([self.superview.superview isKindOfClass:[UITableView class]])
        return (UITableView *)self.superview.superview;
    else
    {
        NSAssert(NO, @"UITableView shall always be found.");
        return nil;
    }

}
@end

Il s'agit d'un bon remplacement rapide de l'utilisation cell.superview, il est facile de refactoriser votre code existant - il suffit de rechercher et de remplacer par [cell relatedTable], et de lancer une assertion pour vous assurer que si la hiérarchie des vues change ou revient à l'avenir, elle apparaîtra immédiatement dans vos tests.

Solution n ° 2: ajouter une UITableViewréférence faible àUITableViewCell

@interface SOUITableViewCell

   @property (weak, nonatomic) UITableView *tableView;

@end

Il s'agit d'une conception bien meilleure, même si elle nécessitera un peu plus de refactorisation du code à utiliser dans les projets existants. Dans votre tableView:cellForRowAtIndexPathutilisation SOUITableViewCell comme classe de cellule ou assurez-vous que votre classe de cellule personnalisée est sous SOUITableViewCell-classée à partir de et affectez le tableView à la propriété tableView de la cellule. À l'intérieur de la cellule, vous pouvez ensuite faire référence à la vue table contenant en utilisant self.tableView.

Memmons
la source
Je ne suis pas d'accord pour dire que la solution 2 est une meilleure conception. Il a plus de points de défaillance et nécessite une intervention manuelle disciplinée. La première solution est en fait assez fiable, même si je la mettrais en œuvre comme @idris l'a suggéré dans sa réponse pour un peu plus de pérennité.
devios1
1
Le test [self.superview.superview isKindOfClass:[UITableView class]]devrait être le premier, car l'iOS 7 l'est de plus en plus.
CopperCash
7

S'il est visible, il a un superview. Et ... surprise ... le superview est un objet UITableView.

Cependant, avoir une supervision n'est pas une garantie d'être à l'écran. Mais UITableView fournit des méthodes pour déterminer quelles cellules sont visibles.

Et non, il n'y a pas de référence dédiée d'une cellule à une table. Mais lorsque vous sous-classez UITableViewCell, vous pouvez en introduire un et le définir lors de la création. (Je l'ai souvent fait moi-même avant de penser à la hiérarchie des sous-vues.)

Mise à jour pour iOS7: Apple a modifié la hiérarchie des sous-vues ici. Comme d'habitude lorsque vous travaillez avec des choses qui ne sont pas documentées de manière détaillée, il y a toujours un risque que les choses changent. Il est beaucoup plus économique de «remonter» la hiérarchie des vues jusqu'à ce qu'un objet UITableView soit finalement trouvé.

Hermann Klecker
la source
14
Ce n'est plus vrai dans iOS7, dans iOS7 beta5 UITableViewWrapperView est la supervision d'un UITableViewCell ... me causant des problèmes en ce moment
Gabe
Merci pour le commentaire. Je devrai alors vérifier une partie de mon code.
Hermann Klecker
7

Solution Swift 2.2.

Une extension pour UIView qui recherche de manière récursive une vue avec un type spécifique.

import UIKit

extension UIView {
    func lookForSuperviewOfType<T: UIView>(type: T.Type) -> T? {
        guard let view = self.superview as? T else {
            return self.superview?.lookForSuperviewOfType(type)
        }
        return view
    }
}

ou plus compact (grâce à kabiroberai):

import UIKit

extension UIView {
    func lookForSuperviewOfType<T: UIView>(type: T.Type) -> T? {
        return superview as? T ?? superview?.superviewOfType(type)
    }
}

Dans votre cellule, vous l'appelez simplement:

let tableView = self.lookForSuperviewOfType(UITableView)
// Here we go

Notez que UITableViewCell est ajouté sur UITableView uniquement après l'exécution de cellForRowAtIndexPath.

Mehdzor
la source
Vous pouvez compacter l'ensemble du lookForSuperviewOfType:corps de la méthode en une seule ligne, ce qui le rend encore plus rapide:return superview as? T ?? superview?.superviewOfType(type)
kabiroberai
@kabiroberai, merci. J'ai ajouté votre conseil à la réponse.
Mehdzor
Le nom utilisé par l'appel récursif doit correspondre. Il faut donc lire: ... ?? superview? .lookForSuperviewOfType (type)
robert
7

Tout ce que vous réussirez à faire en appelant Super View ou via la chaîne de répondeurs sera très fragile. La meilleure façon de le faire, si les cellules veulent savoir quelque chose, est de passer un objet à la cellule qui répond à une méthode qui répond à la question que la cellule veut poser, et de demander au contrôleur de mettre en œuvre la logique de déterminer ce qu'il faut répondre. (d'après votre question, je suppose que la cellule veut savoir si quelque chose est visible ou non).

Créez un protocole délégué dans la cellule, définissez le délégué de la cellule le tableViewController et déplacez toute la logique de «contrôle» de l'interface utilisateur dans le tableViewCotroller.

Les cellules de la vue tableau doivent être une vue dum qui n'affichera que des informations.

Javier Soto
la source
Peut-être même mieux grâce aux délégués. J'utilise temporairement une référence passée à la table car le parent m'a posé des problèmes.
Cristi Băluță
1
Ce que j'ai décrit utilise essentiellement le modèle de délégué :)
Javier Soto
Cela devrait être la réponse acceptée, les gens voient cette question, voient la réponse de plus de 150 votes positifs et ils pensent qu'il est bon d'obtenir la tableView à partir de la cellule et même plus peut-être de garder une forte référence malheureusement.
Alex Terente
6

J'ai créé une catégorie sur UITableViewCell pour obtenir sa table parentView:

@implementation UITableViewCell (ParentTableView)


- (UITableView *)parentTableView {
    UITableView *tableView = nil;
    UIView *view = self;
    while(view != nil) {
        if([view isKindOfClass:[UITableView class]]) {
            tableView = (UITableView *)view;
            break;
        }
        view = [view superview];
    }
    return tableView;
}


@end

Meilleur,

boliva
la source
3

Voici la version Swift basée sur les réponses ci-dessus. J'ai généralisé ExtendedCellpour une utilisation ultérieure.

import Foundation
import UIKit

class ExtendedCell: UITableViewCell {

    weak var _tableView: UITableView!

    func rowIndex() -> Int {
        if _tableView == nil {
            _tableView = tableView()
        }

        return _tableView.indexPathForSelectedRow!.row
    }

    func tableView() -> UITableView! {
        if _tableView != nil {
            return _tableView
        }

        var view = self.superview
        while view != nil && !(view?.isKindOfClass(UITableView))! {
            view = view?.superview
        }

        self._tableView = view as! UITableView
        return _tableView
    }
}

J'espère que cette aide :)

hqt
la source
2

J'ai basé cette solution sur la suggestion de Gabe selon laquelle l' UITableViewWrapperViewobjet est le superview de l' UITableViewCellobjet dans iOS7 beta5.

Sous UITableviewCell- classe :

- (UITableView *)superTableView
{
    return (UITableView *)[self findTableView:self];
}

- (UIView *)findTableView:(UIView *)view
{
    if (view.superview && [view.superview isKindOfClass:[UITableView class]]) {
        return view.superview;
    }
    return [self findTableView:view.superview];
}
Carina
la source
2

J'ai emprunté et modifié un peu la réponse ci-dessus et j'ai trouvé l'extrait suivant.

- (id)recursivelyFindSuperViewWithClass:(Class)clazz fromView:(id)containedView {
    id containingView = [containedView superview];
    while (containingView && ![containingView isKindOfClass:[clazz class]]) {
        containingView = [containingView superview];
    }
    return containingView;
}

Le passage en classe offre la flexibilité de parcourir et d'obtenir des vues autres que UITableView dans d'autres occasions.

Azu
la source
2

Ma solution à ce problème est quelque peu similaire à d'autres solutions, mais utilise une boucle for élégante et est courte. Il doit également être à l'épreuve du temps:

- (UITableView *)tableView
{
    UIView *view;
    for (view = self.superview; ![view isKindOfClass:UITableView.class]; view = view.superview);
    return (UITableView *)view;
}
Bensge
la source
Cela bouclera indéfiniment si la cellule n'est pas encore dans la vue tableau lorsqu'elle est appelée. Pour corriger, ajoutez un chèque de zéro: for (view = self.superview; view && ![view isKindOfClass:UITableView.class]; view = view.superview);
Antonio Nunes
2
UITableView *tv = (UITableView *) self.superview.superview;
BuyListController *vc = (BuyListController *) tv.dataSource;
Gank
la source
1

Au lieu de superview, essayez d'utiliser ["UItableViewvariable" visibleCells].

Je l'ai utilisé dans une boucle foreach pour parcourir les cellules que l'application a vues et cela a fonctionné.

for (UITableView *v in [orderItemTableView visibleCells])//visibleCell is the fix.
{
  @try{
    [orderItemTableView reloadData];
    if ([v isKindOfClass:[UIView class]]) {
        ReviewOrderTableViewCell *cell = (ReviewOrderTableViewCell *)v;
        if (([[cell deleteRecord] intValue] == 1) || ([[[cell editQuantityText] text] intValue] == 0))
            //code here 
    }
  }
}

Fonctionne comme un charme.

user2497493
la source
1

Testé au minimum, mais cet exemple de Swift 3 non générique semble fonctionner:

extension UITableViewCell {
    func tableView() -> UITableView? {
        var currentView: UIView = self
        while let superView = currentView.superview {
            if superView is UITableView {
                return (superView as! UITableView)
            }
            currentView = superView
        }
        return nil
    }
}
Murray Sagal
la source
0

ce code `UITableView *tblView=[cell superview];vous donnera une instance de l'UItableview qui contient la cellule de vue tabe

Singh
la source
Cela ne fonctionnera pas, car la supervision immédiate d'un UITableViewCell n'est pas un UITableView. Depuis iOS 7, le superview UITableViewCell est un UITableViewWrapperView. Voir les autres réponses ici pour des approches moins fragiles et plus fiables.
JaredH
0

Je vous suggère de parcourir la hiérarchie des vues de cette façon pour trouver le parent UITableView:

- (UITableView *) findParentTableView:(UITableViewCell *) cell
{
    UIView *view = cell;
    while ( view && ![view isKindOfClass:[UITableView class]] )
    {
#ifdef DEBUG
        NSLog( @"%@", [[view  class ] description] );
#endif
        view = [view superview];
    }

    return ( (UITableView *) view );
}

Sinon, votre code sera interrompu lorsque Apple modifiera à nouveau la hiérarchie des vues .

Une autre réponse qui traverse également la hiérarchie est récursive.

Bob Wakefield
la source
0
UITableViewCell Internal View Hierarchy Change in iOS 7

Using iOS 6.1 SDK

    <UITableViewCell>
       | <UITableViewCellContentView>
       |    | <UILabel>

Using iOS 7 SDK

    <UITableViewCell>
       | <UITableViewCellScrollView>
       |    | <UIButton>
       |    |    | <UIImageView>
       |    | <UITableViewCellContentView>
       |    |    | <UILabel>


The new private UITableViewCellScrollView class is a subclass of UIScrollView and is what allows this interaction:


![enter image description here][1]


  [1]: http://i.stack.imgur.com/C2uJa.gif

http://www.curiousfind.com/blog/646 Merci

jbchitaliya
la source
0

Vous pouvez l'obtenir avec une seule ligne de code.

UITableView *tableView = (UITableView *)[[cell superview] superview];
Karthik damodara
la source
0
extension UIView {
    func parentTableView() -> UITableView? {
        var viewOrNil: UIView? = self
        while let view = viewOrNil {
            if let tableView = view as? UITableView {
                return tableView
            }
            viewOrNil = view.superview
        }
        return nil
    }
}
neoneye
la source
0

à partir de la réponse @idris, j'ai écrit une extension pour UITableViewCell dans Swift

extension UITableViewCell {
func relatedTableView() -> UITableView? {
    var view = self.superview
    while view != nil && !(view is UITableView) {
        view = view?.superview
    }

    guard let tableView = view as? UITableView else { return nil }
    return tableView
}
Kiattisak Anoochitarom
la source