Come ottenere UITableView da UITableViewCell?


100

Ho un UITableViewCellche è collegato a un oggetto e ho bisogno di dire se la cella è visibile. Dalla ricerca che ho fatto, questo significa che ho bisogno di accedere in qualche modo a quello UITableViewche lo contiene (da lì, ci sono diversi modi per verificare se è visibile). Quindi mi chiedo se UITableViewCellabbia un puntatore a UITableView, o se ci fosse un altro modo per ottenere un puntatore dalla cella?


2
Qual è lo scopo di questo?
max_

[cell superView]può essere?
Chris Loonam

6
Vale la pena spiegare perché pensi di averne bisogno, poiché potrebbe essere un segno di cattivo design poiché non riesco davvero a pensare a molte ragioni legittime per cui una cella sa se è sullo schermo o meno.
Paul.

@ Paul.s Abbiamo un riconoscimento dei gesti su un'immagine in una cella e quando la cella viene toccata, si apre un'altra vista sovrapposta, pensa allo stile popover, che dovrebbe sovrapporsi a tutte le celle necessarie per essere visualizzata correttamente. Affinché funzioni, ha bisogno del TableView o di un'altra vista assegnatagli per essere visualizzato. Non molto soddisfatto delle soluzioni, ma per ottenere l'effetto desiderato, ottenere l'UITableView di UITableViewCell è il meglio che abbiamo trovato.
chadbag

1
@chadbag non preoccuparti, spero di aver dato un'idea a qualcun altro con lo stesso problema.
PJ_Finnegan

Risposte:


153

Per evitare di controllare la versione iOS, esegui iterativamente le superview dalla visualizzazione della cella fino a quando non viene trovato un UITableView:

id view = [tableViewCellInstance superview];

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

UITableView *tableView = (UITableView *)view;

1
Grazie. Sembrerebbe che questo sia cambiato ancora una volta in iOS 8 e questo si prende cura di tutte le versioni in modo piacevole.
djskinner

2
Suggerirei di aggiungere un riferimento debole a tableview alla cella per evitare problemi di compatibilità negli aggiornamenti futuri.
Cenny

1
Piuttosto un modo debole. Non funzionerà se la gerarchia della vista cambia in futuro.
RomanN

2
Sarebbe meglio creare semplicemente una proprietà debole che in realtà contiene un puntatore alla tableview. Nella sottoclasse @property (weak, nonatomic) UITableView *tableView;e in tableView:cellForRowAtIndexPath:appena impostato cell.tableView = tableView;.
Alejandro Iván

L'esperienza attuale è che dopo aver rimosso una cella dalla coda di una cella, una vista tabella non appare subito come una superview della cella - se scorro la cella fuori dalla vista e di nuovo dentro trovo una vista tabella come una superview (ricerca ricorsiva come descritto in altre risposte). Probabili ragioni sono il layout automatico e forse altri fattori.
Jonny

48

In iOS7 beta 5 UITableViewWrapperViewè la superview di un file UITableViewCell. Inoltre UITableViewè la superview di un file UITableViewWrapperView.

Quindi per iOS 7 la soluzione è

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

Quindi per iOS fino a iOS 6 la soluzione è

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


5
Oh amico, questo è un cambiamento brutale dell'API. Qual è la best practice di Apple per la ramificazione se supporti sia il 6 che il 7?
Ryan Romanchuk

@RyanRomanchuk Ecco un buon suggerimento: devforums.apple.com/message/865550#865550 - crea un debole puntatore al tuo tableView correlato quando la cella viene creata. In alternativa, crea una UITableViewCellcategoria con un nuovo metodo, relatedTableViewche verifica la presenza di una versione iOS e restituisce il superView appropriato.
memmons

3
Scrivi una categoria ricorsiva su UIView per questo. Non è necessario controllare la versione; chiama semplicemente superview finché non trovi una cella della vista tabella o la parte superiore dello stack della vista. Questo è quello che ho usato in iOS 6 e ha funzionato senza modifiche in iOS 7. E dovrebbe funzionare perfettamente in iOS 8.
Steven Fisher

Chiedere perdono; Intendevo finché non trovi la vista tabella. :)
Steven Fisher

39

Estensione Swift 5

ricorsivamente

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

Utilizzando loop

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

Una regola pratica è quella di non usare la forza di scartare
thibaut noah

1
@thibautnoah C'è un view != nilcontrollo prima di scartare. Aggiornato al codice più pulito però
Orkhan Alikhanov

25

Prima di iOS7, la superview della cella era quella UITableViewche lo conteneva. A partire da iOS7 GM (quindi presumibilmente sarà anche in versione pubblica) la superview della cellula è una UITableViewWrapperViewcon la sua superview che è la UITableView. Esistono due soluzioni al problema.

Soluzione n. 1: crea una UITableViewCellcategoria

@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

Questo è un buon sostituto cell.superviewimmediato dell'utilizzo , semplifica il refactoring del codice esistente: basta cercare e sostituire con e inserire [cell relatedTable]un'asserzione per garantire che se la gerarchia della vista cambia o si ripristina in futuro, verrà visualizzata immediatamente nei tuoi test.

Soluzione n. 2: aggiungi un UITableViewriferimento debole aUITableViewCell

@interface SOUITableViewCell

   @property (weak, nonatomic) UITableView *tableView;

@end

Questo è un design molto migliore, anche se richiederà un po 'più di refactoring del codice da utilizzare nei progetti esistenti. Nel tuo tableView:cellForRowAtIndexPathuso SOUITableViewCell come classe di cella o assicurati che la tua classe di cella personalizzata sia sottoclasse SOUITableViewCelle assegna il tableView alla proprietà tableView della cella. All'interno della cella puoi quindi fare riferimento alla tableview contenente usando self.tableView.


Non sono d'accordo sul fatto che la soluzione 2 sia un design migliore. Ha più punti di fallimento e richiede un intervento manuale disciplinato. La prima soluzione è in realtà abbastanza affidabile, anche se la implementerei come suggerito da @idris nella sua risposta per un po 'più di protezione dal futuro.
devios1

1
Il test [self.superview.superview isKindOfClass:[UITableView class]]dovrebbe essere il primo, perché iOS 7 è sempre di più.
CopperCash

7

Se è visibile, ha una superview. E ... sorpresa ... la superview è un oggetto UITableView.

Tuttavia, avere una superview non è una garanzia per essere sullo schermo. Ma UITableView fornisce metodi per determinare quali celle sono visibili.

E no, non esiste un riferimento dedicato da una cella a una tabella. Ma quando si sottoclasse UITableViewCell è possibile introdurne uno e impostarlo al momento della creazione. (L'ho fatto io stesso molto prima di pensare alla gerarchia delle sottoview.)

Aggiornamento per iOS7: Apple ha modificato la gerarchia delle sottoview qui. Come al solito quando si lavora con cose che non sono documentate dettagliatamente, c'è sempre il rischio che le cose cambino. È molto più conveniente "eseguire la ricerca per indicizzazione" nella gerarchia di visualizzazione finché non viene trovato un oggetto UITableView.


14
Questo non è più vero in iOS7, in iOS7 beta5 UITableViewWrapperView è il superview di un UITableViewCell ... mi sta causando problemi in questo momento
Gabe

Grazie per il commento. Allora dovrò controllare parte del mio codice.
Hermann Klecker

7

Soluzione Swift 2.2.

Un'estensione per UIView che cerca in modo ricorsivo una vista con un tipo specifico.

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

o più compatto (grazie a kabiroberai):

import UIKit

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

Nel tuo cellulare lo chiami semplicemente:

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

Tieni presente che UITableViewCell viene aggiunto a UITableView solo dopo l'esecuzione di cellForRowAtIndexPath.


Puoi compattare l'intero lookForSuperviewOfType:corpo del metodo in una riga, rendendolo anche veloce:return superview as? T ?? superview?.superviewOfType(type)
kabiroberai

@kabiroberai, grazie. Ho aggiunto il tuo consiglio alla risposta.
Mehdzor

Il nome utilizzato dalla chiamata ricorsiva deve corrispondere. Quindi questo deve leggere: ... ?? superview? .lookForSuperviewOfType (tipo)
robert

7

Qualunque cosa tu possa finire per fare chiamando super view o tramite la catena di risponditori sarà molto fragile. Il modo migliore per farlo, se le celle vogliono sapere qualcosa, è passare un oggetto alla cella che risponde a un metodo che risponde alla domanda che la cella vuole porre e chiedere al controllore di implementare la logica per determinare cosa rispondere (dalla tua domanda immagino che la cella voglia sapere se qualcosa è visibile o meno).

Creare un protocollo delegato nella cella, impostare il delegato della cella tableViewController e spostare tutta la logica di "controllo" dell'interfaccia utente nel tableViewCotroller.

Le celle della vista tabella dovrebbero essere una vista dum che mostrerà solo le informazioni.


Forse anche meglio tramite i delegati. Sto temporaneamente utilizzando un riferimento passato alla tabella poiché il genitore mi ha dato problemi.
Cristi Băluță

1
Quello che ho descritto sta fondamentalmente usando il modello delegato :)
Javier Soto

Questa dovrebbe essere la risposta accettata, le persone vedono questa domanda, vedono la risposta di 150+ voti positivi e pensano che vada bene per ottenere la tableView dalla cella e ancora di più forse mantengono un forte riferimento ad essa sfortunatamente.
Alex Terente

6

Ho creato una categoria su UITableViewCell per ottenere il suo tableView padre:

@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

Migliore,


3

Ecco la versione Swift basata sulle risposte precedenti. Ho generalizzato ExtendedCellper un utilizzo successivo.

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

Spero che questo aiuto :)


2

Ho basato questa soluzione sul suggerimento di Gabe che l' UITableViewWrapperViewoggetto è la supervisualizzazione UITableViewCelldell'oggetto in iOS7 beta5.

Sottoclasse UITableviewCell:

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

2

Ho preso in prestito e modificato un po 'dalla risposta sopra e ho trovato il seguente frammento.

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

Passare in classe offre la flessibilità per attraversare e ottenere visualizzazioni diverse da UITableView in alcune altre occasioni.


2

La mia soluzione a questo problema è in qualche modo simile ad altre soluzioni, ma utilizza un elegante ciclo for ed è breve. Dovrebbe anche essere a prova di futuro:

- (UITableView *)tableView
{
    UIView *view;
    for (view = self.superview; ![view isKindOfClass:UITableView.class]; view = view.superview);
    return (UITableView *)view;
}

Questo verrà ripetuto all'infinito se la cella non è ancora nella vista tabella quando viene chiamata. Per risolvere il problema, aggiungi un controllo per zero: 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;

1

Invece di superview, prova a utilizzare ["UItableViewvariable" visibleCells].

L'ho usato in un ciclo foreach per scorrere le celle che l'app ha visto e ha funzionato.

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

Funziona come un fascino.


1

Minimamente testato ma questo esempio Swift 3 non generico sembra funzionare:

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

0

questo codice `UITableView *tblView=[cell superview];ti fornirà un'istanza della UItableview che contiene la cella della visualizzazione tabe


Questo non funzionerà, poiché la visualizzazione immediata di un UITableViewCell non è un UITableView. A partire da iOS 7, la superview UITableViewCell è una UITableViewWrapperView. Vedi le altre risposte qui per approcci meno fragili e più affidabili.
JaredH

0

Ti suggerisco di attraversare la gerarchia della vista in questo modo per trovare il genitore 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 );
}

In caso contrario, il codice si interromperà quando Apple cambierà nuovamente la gerarchia di visualizzazione .

Un'altra risposta che attraversa anche la gerarchia è ricorsiva.


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 Grazie


0

Puoi ottenerlo con una riga di codice.

UITableView *tableView = (UITableView *)[[cell superview] superview];

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

0

dalla risposta di @idris ho scritto un'espansione per UITableViewCell in 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
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.