¿Cómo obtener UITableView de UITableViewCell?

100

Tengo un UITableViewCellque está vinculado a un objeto y necesito saber si la celda es visible. Según la investigación que he realizado, esto significa que necesito acceder de alguna manera al UITableViewque lo contiene (desde allí, hay varias formas de verificar si está visible). Entonces, me pregunto si UITableViewCelltiene un puntero al UITableView, o si hay alguna otra forma de obtener un puntero desde la celda.

pecado
fuente
2
Cual es el proposito de esto?
max_
[cell superView]¿tal vez?
Chris Loonam
6
Vale la pena explicar por qué cree que necesita esto, ya que esto puede ser un signo de mal diseño, ya que realmente no puedo pensar en muchas razones legítimas para que una celda sepa si está en la pantalla o no.
Paul.s
@ Paul.s Tenemos un reconocedor de gestos en una imagen en una celda y cuando se toca la celda, se abre otra vista superpuesta, piense en el estilo popover, que debería superponer tantas celdas como sea necesario para mostrarse correctamente. Para que esto funcione, necesita TableView u otra vista que se le proporcione para mostrar. No estoy muy contento con las soluciones, pero para obtener el efecto deseado, lo mejor que hemos encontrado es la UITableView de UITableViewCell.
chadbag
1
@chadbag no te preocupes, espero haberle dado una idea a otra persona con el mismo problema.
PJ_Finnegan

Respuestas:

153

Para evitar verificar la versión de iOS, recorra iterativamente las supervistas desde la vista de la celda hasta que se encuentre un UITableView:

id view = [tableViewCellInstance superview];

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

UITableView *tableView = (UITableView *)view;
Muhammad Idris
fuente
1
Gracias. Parece que esto ha cambiado una vez más en iOS 8 y esto se encarga muy bien de todas las versiones.
djskinner
2
Recomendaría agregar una referencia débil a la vista de tabla en la celda para evitar problemas de compatibilidad en futuras actualizaciones.
Cenny
1
Más bien de una manera débil. No funcionará si la jerarquía de la vista cambia en el futuro.
RomanN
2
Sería mejor simplemente crear una propiedad débil que realmente tenga un puntero a la vista de tabla. En la subclase @property (weak, nonatomic) UITableView *tableView;y en tableView:cellForRowAtIndexPath:recién establecido cell.tableView = tableView;.
Alejandro Iván
La experiencia actual es que después de quitar la cola de una celda, una vista de tabla no aparece como una supervista de la celda de inmediato; si desplazo la celda fuera de la vista y la vuelvo a ingresar, encuentro una vista de tabla como una supervista (búsqueda recursiva como se describe en Otras respuestas). El diseño automático y posiblemente otros factores son posibles razones.
Jonny
48

En iOS7 beta 5 UITableViewWrapperViewes la supervista de un UITableViewCell. También UITableViewhay una supervista de a UITableViewWrapperView.

Entonces, para iOS 7, la solución es

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

Entonces, para iOS hasta iOS 6, la solución es

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

KGS-12
fuente
5
Oh hombre, este es un cambio brutal de API. ¿Cuál es la mejor práctica de Apple para la ramificación si admite tanto 6 como 7?
Ryan Romanchuk
@RyanRomanchuk Aquí hay una buena sugerencia: devforums.apple.com/message/865550#865550 : crea un puntero débil a tu tableView relacionado cuando se crea la celda. Alternativamente, cree una UITableViewCellcategoría con un nuevo método, relatedTableViewque verifica una versión de iOS y devuelve la supervista adecuada.
memmons
3
Escriba una categoría recursiva en UIView para esto. No es necesario comprobar la versión; simplemente llame a superview hasta que encuentre una celda de vista de tabla o la parte superior de la pila de vista. Esto es lo que usé en iOS 6, y funcionó sin modificaciones en iOS 7. Y debería funcionar en iOS 8.
Steven Fisher
Pedir perdón; Quería decir hasta que encuentres la vista de tabla. :)
Steven Fisher
39

Extensión Swift 5

Recursivamente

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

Usando bucle

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
fuente
Una regla general es no usar la fuerza para desenvolverlo
thibaut noah
1
@thibautnoah Hay un view != nilcheque antes de desenvolver. Sin embargo
Orkhan Alikhanov
25

Antes de iOS7, la supervista de la celda era la UITableViewque la contenía. A partir de iOS7 GM (es de suponer que también estará en el lanzamiento público), la supervista de la celda es una UITableViewWrapperViewcon su supervista como laUITableView . Hay dos soluciones al problema.

Solución n. ° 1: cree una UITableViewCellcategoría

@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

Este es un buen reemplazo para el uso cell.superview, facilita la refactorización de su código existente: simplemente busque y reemplace [cell relatedTable]y agregue una afirmación para asegurarse de que si la jerarquía de la vista cambia o se revierte en el futuro, se mostrará de inmediato. en tus pruebas.

Solución # 2: agregue una UITableViewreferencia débil aUITableViewCell

@interface SOUITableViewCell

   @property (weak, nonatomic) UITableView *tableView;

@end

Este es un diseño mucho mejor, aunque requerirá un poco más de refactorización de código para usarlo en proyectos existentes. En su tableView:cellForRowAtIndexPathuso SOUITableViewCell como su clase de celda o asegúrese de que su clase de celda personalizada sea subclasificada SOUITableViewCelly asigne el tableView a la propiedad tableView de la celda. Dentro de la celda, puede consultar la vista de tabla que contiene usando self.tableView.

memmons
fuente
No estoy de acuerdo con que la solución 2 sea un mejor diseño. Tiene más puntos de falla y requiere una intervención manual disciplinada. La primera solución es bastante confiable, aunque la implementaría como sugirió @idris en su respuesta para un poco más de protección para el futuro.
devios1
1
La prueba [self.superview.superview isKindOfClass:[UITableView class]]debería ser la primera, porque iOS 7 es cada vez más.
CopperCash
7

Si es visible, entonces tiene una supervista. Y ... sorpresa ... la supervista es un objeto UITableView.

Sin embargo, tener una supervista no es garantía de estar en pantalla. Pero UITableView proporciona métodos para determinar qué celdas son visibles.

Y no, no hay una referencia dedicada de una celda a una tabla. Pero cuando crea una subclase de UITableViewCell, puede introducir uno y configurarlo al crearlo. (Lo hice mucho antes de pensar en la jerarquía de subvista).

Actualización para iOS7: Apple ha cambiado la jerarquía de subvista aquí. Como es habitual cuando se trabaja con cosas que no están documentadas detalladamente, siempre existe el riesgo de que las cosas cambien. Es mucho más económico "rastrear" la jerarquía de vistas hasta que finalmente se encuentre un objeto UITableView.

Hermann Klecker
fuente
14
Esto ya no es cierto en iOS7, en iOS7 beta5, UITableViewWrapperView es la supervista de UITableViewCell ... lo que me causa problemas en este momento
Gabe
Gracias por el comentario. Entonces tendré que comprobar algo de mi código.
Hermann Klecker
7

Solución Swift 2.2.

Una extensión para UIView que busca de forma recursiva una vista con un tipo específico.

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 más compacto (gracias a kabiroberai):

import UIKit

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

En tu celular simplemente lo llamas:

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

Tenga en cuenta que UITableViewCell se agrega en UITableView solo después de la ejecución de cellForRowAtIndexPath.

Mehdzor
fuente
Puede compactar todo el lookForSuperviewOfType:cuerpo del método en una línea, haciéndolo incluso rápido:return superview as? T ?? superview?.superviewOfType(type)
kabiroberai
@kabiroberai, gracias. Agregué tu consejo a la respuesta.
Mehdzor
El nombre utilizado por la llamada recursiva debe coincidir. Entonces, esto necesita leer: ... ?? superview? .lookForSuperviewOfType (tipo)
robert
7

Lo que sea que pueda hacer al llamar a SuperView oa través de la cadena de respuesta, será muy frágil. La mejor manera de hacer esto, si las celdas quieren saber algo, es pasar un objeto a la celda que responda a algún método que responda a la pregunta que la celda quiere hacer, y hacer que el controlador implemente la lógica de determinar qué responder. (a partir de su pregunta, supongo que la celda quiere saber si algo es visible o no).

Cree un protocolo de delegado en la celda, establezca el delegado de la celda en tableViewController y mueva toda la lógica de "control" de la interfaz de usuario en el tableViewCotroller.

Las celdas de la vista de tabla deben ser una vista dum que solo mostrará información.

Javier Soto
fuente
Quizás incluso mejor a través de delegados. Estoy usando temporalmente una referencia pasada a la tabla ya que el padre me dio problemas.
Cristi Băluță
1
Lo que describí es básicamente usar el patrón de delegado :)
Javier Soto
Esta debería ser la respuesta aceptada, las personas ven esta pregunta, ven la respuesta de más de 150 votos a favor y piensan que está bien obtener el tableView de la celda y aún más tal vez mantener una fuerte referencia a ella, desafortunadamente.
Alex Terente
6

Creé una categoría en UITableViewCell para obtener su tableView principal:

@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

Mejor,

boliva
fuente
3

Aquí está la versión Swift basada en las respuestas anteriores. He generalizado ExtendedCellpara su uso posterior.

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

Espero que esto ayude :)

hqt
fuente
2

UITableViewWrapperViewBasé esta solución en la sugerencia de Gabe de que el objeto es la supervista del UITableViewCellobjeto en iOS7 beta5.

Subclase 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];
}
Carina
fuente
2

Tomé prestado y modifiqué un poco la respuesta anterior y se me ocurrió el siguiente fragmento.

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

Pasar en clase ofrece la flexibilidad para atravesar y obtener vistas distintas de UITableView en otras ocasiones.

Azu
fuente
2

Mi solución a este problema es algo similar a otras soluciones, pero usa un bucle for elegante y es corta. También debería estar preparado para el futuro:

- (UITableView *)tableView
{
    UIView *view;
    for (view = self.superview; ![view isKindOfClass:UITableView.class]; view = view.superview);
    return (UITableView *)view;
}
Bensge
fuente
Esto se repetirá para siempre si la celda aún no está en la vista de tabla cuando se llama. Para solucionarlo, agregue un cheque por cero: 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
fuente
1

En lugar de supervista, intente usar ["UItableViewvariable" visibleCells].

Lo usé en un bucle foreach para recorrer las celdas que vio la aplicación y funcionó.

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

Funciona de maravilla.

usuario2497493
fuente
1

Mínimamente probado, pero este ejemplo de Swift 3 no genérico parece funcionar:

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
fuente
0

este código `UITableView *tblView=[cell superview];le dará una instancia de UItableview que contiene la celda de vista de tabulación

Singh
fuente
Esto no funcionará, ya que la supervista inmediata de un UITableViewCell no es un UITableView. A partir de iOS 7, la supervista UITableViewCell es UITableViewWrapperView. Vea las otras respuestas aquí para obtener enfoques menos frágiles y más confiables.
JaredH
0

Le sugiero que recorra la jerarquía de vistas de esta manera para encontrar el UITableView principal:

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

De lo contrario, su código se romperá cuando Apple cambie la jerarquía de vista nuevamente .

Otra respuesta que también atraviesa la jerarquía es recursiva.

Bob Wakefield
fuente
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 Gracias

jbchitaliya
fuente
0

Puede obtenerlo con una línea de código.

UITableView *tableView = (UITableView *)[[cell superview] superview];
Karthik damodara
fuente
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
fuente
0

de la respuesta de @idris escribí una expansión para UITableViewCell en 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
fuente