Como obter UITableView de UITableViewCell?

100

Tenho um UITableViewCellque está vinculado a um objeto e preciso saber se a célula está visível. A partir da pesquisa que fiz, isso significa que preciso acessar de alguma forma o UITableViewque o contém (a partir daí, existem várias maneiras de verificar se ele está visível). Então, eu gostaria de saber se UITableViewCelltem um ponteiro para o UITableView, ou se havia alguma outra maneira de obter um ponteiro da célula?

sinθ
fonte
2
Qual é o propósito disso?
máx_
[cell superView]talvez?
Chris Loonam
6
Vale a pena explicar por que você acha que precisa disso - pois isso pode ser um sinal de design ruim, pois não consigo pensar em muitos motivos legítimos para uma célula saber se está na tela ou não.
Paul.s
@ Paul.s Temos um reconhecedor de gestos em uma imagem em uma célula e quando a célula é tocada, ele abre outra visualização de sobreposição, no estilo popover, que deve sobrepor quantas células forem necessárias para exibir corretamente. Para que isso funcione, é necessário que o TableView ou outra visualização fornecida a ele seja exibida. Não estou muito feliz com as soluções, mas para obter o efeito desejado, obter o UITableView do UITableViewCell é o melhor que encontramos.
chadbag de
1
@chadbag não se preocupe, espero ter dado uma ideia para outra pessoa com o mesmo problema.
PJ_Finnegan

Respostas:

153

Para evitar a verificação da versão do iOS, percorra iterativamente as visualizações da célula até que um UITableView seja encontrado:

id view = [tableViewCellInstance superview];

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

UITableView *tableView = (UITableView *)view;
Muhammad Idris
fonte
1
Obrigado. Parece que isso mudou mais uma vez no iOS 8 e isso cuida bem de todas as versões.
djskinner
2
Eu recomendaria adicionar uma referência fraca ao tableview à célula para evitar problemas de compatibilidade em atualizações futuras.
Cenny
1
Um jeito bastante fraco. Não funcionará se a hierarquia da visão mudar no futuro.
RomanN
2
Seria melhor simplesmente criar uma propriedade fraca que realmente contivesse um ponteiro para tableview. Na subclasse @property (weak, nonatomic) UITableView *tableView;e tableView:cellForRowAtIndexPath:apenas no conjunto cell.tableView = tableView;.
Alejandro Iván
A experiência atual é que, depois de retirar da fila uma célula, uma visualização de tabela não aparece como uma supervisualização da célula imediatamente - se eu rolar a célula para fora da visualização e de volta para dentro novamente, encontro uma visualização de tabela como uma supervisão (pesquisa recursiva conforme descrito em outras respostas). Autolayout e possivelmente outros fatores são razões prováveis.
Jonny
48

No iOS7 beta 5 UITableViewWrapperViewé a visão geral de a UITableViewCell. Também UITableViewé superview de a UITableViewWrapperView.

Portanto, para iOS 7, a solução é

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

Portanto, para iOSes até iOS 6, a solução é

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

KGS-12
fonte
5
Oh cara, esta é uma mudança brutal de API. Qual é a prática recomendada da apple para ramificação se você oferece suporte a 6 e 7?
Ryan Romanchuk
@RyanRomanchuk Aqui está uma boa sugestão: devforums.apple.com/message/865550#865550 - crie um ponteiro fraco para seu tableView relacionado quando a célula for criada. Como alternativa, crie uma UITableViewCellcategoria com um novo método, relatedTableViewque faz uma verificação para uma versão do iOS e retorna o superView apropriado.
memmons
3
Escreva uma categoria recursiva no UIView para isso. Você não precisa verificar a versão; apenas chame superview até encontrar uma célula de visualização de tabela ou o topo da pilha de visualização. Foi o que usei no iOS 6 e funcionou sem modificações no iOS 7. E deve funcionar ainda no iOS 8.
Steven Fisher
Peço perdão; Eu quis dizer até você encontrar a visualização da tabela. :)
Steven Fisher,
39

Extensão 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 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
    }
}
Orkhan Alikhanov
fonte
Uma regra prática é não usar o desembrulhamento forçado
thibaut noah
1
@thibautnoah Há um view != nilcheque antes de desembrulhar. Atualizado para um código mais limpo
Orkhan Alikhanov
25

Antes do iOS7, a visão geral da célula era o UITableViewque o continha. A partir do iOS7 GM (provavelmente também estará no lançamento público), a supervisão da célula é uma, UITableViewWrapperViewcom sua supervisão sendo o UITableView. Existem duas soluções para o problema.

Solução nº 1: crie uma 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

Este é um bom substituto imediato para usar cell.superview, torna mais fácil refatorar seu código existente - apenas pesquise e substitua [cell relatedTable], e coloque uma declaração para garantir que, se a hierarquia de visualização mudar ou reverter no futuro, ela aparecerá imediatamente em seus testes.

Solução # 2: Adicionar uma UITableViewreferência fraca aUITableViewCell

@interface SOUITableViewCell

   @property (weak, nonatomic) UITableView *tableView;

@end

Este é um design muito melhor, embora exija um pouco mais de refatoração de código para usar em projetos existentes. Em seu tableView:cellForRowAtIndexPathuso SOUITableViewCell como sua classe de célula ou certifique-se de que sua classe de célula personalizada é uma subclasse de SOUITableViewCelle atribua tableView à propriedade tableView da célula. Dentro da célula, você pode então referir-se ao tableview que o contém usando self.tableView.

memmons
fonte
Não concordo que a solução 2 seja um design melhor. Possui mais pontos de falha e requer intervenção manual disciplinada. A primeira solução é na verdade bastante confiável, embora eu a implementasse como @idris sugeriu em sua resposta para um pouco mais à prova de futuro.
devios1
1
O teste [self.superview.superview isKindOfClass:[UITableView class]]deve ser o primeiro, porque o iOS 7 é cada vez mais.
CopperCash
7

Se estiver visível, tem uma visão geral. E ... surpresa ... o superview é um objeto UITableView.

No entanto, ter uma supervisualização não é garantia de estar na tela. Mas UITableView fornece métodos para determinar quais células são visíveis.

E não, não há nenhuma referência dedicada de uma célula a uma tabela. Mas quando você subclasse UITableViewCell você pode introduzir um e configurá-lo na criação. (Eu mesmo fiz muito isso antes de pensar na hierarquia de subvisualização.)

Atualização para iOS7: a Apple mudou a hierarquia de subvisualização aqui. Como de costume, ao trabalhar com coisas que não são documentadas detalhadamente, há sempre o risco de que as coisas mudem. É muito mais seguro "rastrear" a hierarquia de visualizações até que um objeto UITableView seja finalmente encontrado.

Hermann Klecker
fonte
14
Isso não é mais verdade no iOS7, no iOS7 beta5 UITableViewWrapperView é a visão geral de um UITableViewCell ... me causando problemas agora
Gabe
Obrigado pelo comentário. Vou ter que verificar um pouco do meu código então.
Hermann Klecker
7

Solução Swift 2.2.

Uma extensão para UIView que procura recursivamente por uma visualização com um 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
    }
}

ou mais compacto (graças ao kabiroberai):

import UIKit

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

No seu celular você apenas chama:

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

Lembre-se de que UITableViewCell é adicionado ao UITableView somente após a execução de cellForRowAtIndexPath.

Mehdzor
fonte
Você pode compactar todo o lookForSuperviewOfType:corpo do método em uma linha, tornando-o ainda mais rápido:return superview as? T ?? superview?.superviewOfType(type)
kabiroberai
@kabiroberai, obrigado. Acrescentei seu conselho à resposta.
Mehdzor
O nome usado pela chamada recursiva precisa corresponder. Portanto, isso precisa ser lido: ... ?? superview? .lookForSuperviewOfType (type)
robert
7

O que quer que você consiga fazer ao chamar super view ou por meio da cadeia de resposta será muito frágil. A melhor maneira de fazer isso, se as células querem saber algo, é passar um objeto para a célula que responde a algum método que responda à pergunta que a célula quer fazer e fazer com que o controlador implemente a lógica de determinar o que responder (pela sua pergunta, acho que a célula quer saber se algo está visível ou não).

Crie um protocolo de delegado na célula, defina o delegado da célula como tableViewController e mova toda a lógica de "controle" da interface do usuário no tableViewCotroller.

As células da visualização da tabela devem ser uma visualização que exibirá apenas informações.

Javier Soto
fonte
Talvez ainda melhor por meio de delegados. estou temporariamente usando uma referência passada à tabela, pois o pai me deu problemas.
Cristi Băluță
1
O que descrevi está basicamente usando o padrão delegado :)
Javier Soto de
Essa deve ser a resposta aceita, as pessoas veem essa pergunta, veem a resposta de 150+ votos positivos e acham que é bom pegar o tableView da célula e ainda mais talvez manter uma referência forte a ele, infelizmente.
Alex Terente
6

Criei uma categoria em UITableViewCell para obter seu tableView pai:

@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

melhor,

Boliva
fonte
3

Aqui está a versão do Swift com base nas respostas acima. Eu generalizei ExtendedCellpara 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 esta ajuda :)

hqt
fonte
2

Eu baseei essa solução na sugestão de Gabe de que o UITableViewWrapperViewobjeto é a supervisão do UITableViewCellobjeto no iOS7 beta5.

Subclasse 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
fonte
2

Eu peguei emprestado e modifiquei um pouco da resposta acima e cheguei ao seguinte trecho.

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

A passagem em classe oferece a flexibilidade para percorrer e obter visualizações diferentes de UITableView em algumas outras ocasiões.

Azu
fonte
2

Minha solução para esse problema é um pouco semelhante a outras soluções, mas usa um elegante for-loop e é curto. Também deve ser à prova de futuro:

- (UITableView *)tableView
{
    UIView *view;
    for (view = self.superview; ![view isKindOfClass:UITableView.class]; view = view.superview);
    return (UITableView *)view;
}
Bensge
fonte
Isso irá repetir para sempre se a célula ainda não estiver na visualização de tabela quando chamada. Para corrigir, adicione um cheque para 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;
Gank
fonte
1

Em vez de superview, tente usar ["UItableViewvariable" visibleCells].

Usei isso em loops foreach para percorrer as células que o aplicativo viu e funcionou.

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 como um encanto.

user2497493
fonte
1

Testado minimamente, mas este exemplo não genérico de Swift 3 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
fonte
0

este código `UITableView *tblView=[cell superview];lhe dará uma instância da UItableview que contém a célula de visualização da guia

Singh
fonte
Isso não funcionará, pois a supervisão imediata de um UITableViewCell não é um UITableView. No iOS 7, o UITableViewCell superview é um UITableViewWrapperView. Veja as outras respostas aqui para abordagens menos frágeis e mais confiáveis.
JaredH de
0

Eu sugiro que você atravesse a hierarquia de visualizações desta maneira para encontrar o UITableView pai:

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

Caso contrário, seu código será interrompido quando a Apple alterar a hierarquia de visualização novamente .

Outra resposta que também atravessa a hierarquia é recursiva.

Bob Wakefield
fonte
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 Obrigado

jbchitaliya
fonte
0

Você pode obtê-lo com uma linha de código.

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

da resposta de @idris escrevi uma expansão para UITableViewCell em 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
fonte