Problema UITableView ao usar o delegate / dataSource separado

Descrição geral:

Para começar com o que funciona, eu tenho um UITableView que foi colocado em uma visualização gerada pelo Xcode usando o Interface Builder. O proprietário do arquivo da visualização é definido como uma subclass gerada pelo Xcode do UIViewController . Para esta subclass, adicionei implementações de trabalho de numberOfSectionsInTableView: tableView:numberOfRowsInSection: e tableView:cellForRowAtIndexPath: e o tableView:cellForRowAtIndexPath: e o delegate do Table View estão conectados a essa class por meio do File Owner no Interface Builder.

A configuração acima funciona sem problemas. O problema ocorre quando eu quero mover o dataSource e delegate Implementations do Table View para uma class separada, muito provavelmente porque há outros controles no View além do Table View e gostaria de mover o código relacionado ao Table View para fora para sua própria class. Para conseguir isso, eu tentei o seguinte:

  • Crie uma nova subclass de UITableViewController no Xcode
  • Mova as implementações em bom estado de numberOfSectionsInTableView: tableView:numberOfRowsInSection: e tableView:cellForRowAtIndexPath: para a nova subclass
  • Arraste um UITableViewController para o nível superior do XIB existente no InterfaceBuilder, exclua o UIView / UITableView que é criado automaticamente para esse UITableViewController e defina a class do UITableViewController para corresponder à nova subclass
  • Remova o dataSource existente do UITableView está trabalhando anteriormente e delegate conexões e conecte-as ao novo UITableViewController

Quando concluído, não tenho um UITableView funcionamento. Eu acabo com um dos três resultados que aparentemente podem acontecer de forma aleatória:

  • Quando o UITableView carregado, recebo um erro de tempo de execução indicando que estou enviando tableView:cellForRowAtIndexPath: para um object que não o reconhece
  • Quando o UITableView carregado, o projeto entra no depurador sem erro
  • Não há erro, mas o UITableView não aparece

Com alguma debugging e tendo criado um projeto básico apenas para reproduzir esse problema, normalmente estou vendo a 3ª opção acima (sem erro, mas sem visualização de tabela visível). Eu adicionei algumas chamadas NSLog e descobri que, embora numberOfSectionsInTableView: e numberOfRowsInSection: estejam sendo chamadas, cellForRowAtIndexPath: não é. Estou convencido de que estou sentindo falta de algo realmente simples e esperava que a resposta fosse óbvia para alguém com mais experiência do que eu. Se isso não for uma resposta fácil, eu ficaria feliz em atualizar com algum código ou um projeto de amostra. Obrigado pelo seu tempo!

Conclua as etapas para reproduzir:

  • Crie um novo sistema operacional do iPhone, o aplicativo baseado em visualização no Xcode e chame-o de TableTest
  • Abra o TableTestViewController.xib no Interface Builder e arraste um UITableView para a superfície de visualização fornecida.
  • Conecte o dataSource do UITableView e delegate -outlets ao proprietário do arquivo, que já deve representar a class TableTestViewController . Salve suas alterações
  • De volta ao Xcode, adicione o seguinte código ao TableTestViewController.m:

 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { NSLog(@"Returning num sections"); return 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { NSLog(@"Returning num rows"); return 1; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { NSLog(@"Trying to return cell"); static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease]; } cell.text = @"Hello"; NSLog(@"Returning cell"); return cell; } 
  • Build and Go, e você deve ver a palavra Hello aparecer no UITableView

  • Agora, para tentar mover essa lógica do UITableView para uma class separada, primeiro crie um novo arquivo no Xcode, escolhendo a subclass UITableViewController e chamando a class TableTestTableViewController

  • Remova o trecho de código acima de TableTestViewController.m e coloque-o em TableTestTableViewController.m , substituindo a implementação padrão desses três methods pelos nossos.
  • De volta ao Interface Builder dentro do mesmo TableTestViewController.xib , arraste um UITableViewController para a janela principal do IB e exclua o novo object UITableView que veio automaticamente com ele
  • Definir a class para este novo UITableViewController para TableTestTableViewController
  • Remova o dataSource e delegate ligações do UITableView já existente e reconecte as mesmas duas ligações ao novo TableTestTableViewController que criamos.
  • Salvar alterações, Build and Go, e se você está recebendo os resultados que estou recebendo, observe que o UITableView não funciona mais corretamente

Solução: Com mais algumas soluções de problemas e alguma ajuda dos fóruns de desenvolvedores do iPhone , eu documentei uma solução! A subclass principal do UIViewController do projeto precisa de uma saída apontando para a instância do UITableViewController . Para fazer isso, basta adicionar o seguinte ao header da visualização principal ( TableTestViewController.h ):

 #import "TableTestTableViewController.h" 

e

 IBOutlet TableTestTableViewController *myTableViewController; 

Em seguida, no Interface Builder, conecte a nova tomada do proprietário do arquivo ao TableTestTableViewController na janela principal do IB. Nenhuma alteração é necessária na parte da interface do usuário do XIB. Basta ter esta tomada em funcionamento, mesmo que nenhum código de usuário a use diretamente, resolve o problema completamente. Obrigado a todos que ajudaram e deram crédito ao BaldEagle nos Fóruns de Desenvolvedores do iPhone para encontrar a solução.

Eu segui seus passos, recriou o projeto e se deparou com o mesmo problema. Basicamente você está quase lá. Há duas coisas que faltam (uma vez corrigido, funciona):

  • Você precisa conectar o tableView do TableTestTableViewController ao UITableView você tem na canvas. Como eu disse antes, porque não é IBOutlet você pode sobrescrever a propriedade tableView e fazer isso e IBOutlet :

     @interface TableTestTableViewController : UITableViewController { UITableView *tableView; } @property (nonatomic, retain) IBOutlet UITableView *tableView; 
  • A próxima coisa é adicionar uma referência ao TableTestTableViewController e retê-lo no TableTestViewController . Caso contrário, seu TableTestTableViewController pode ser liberado (depois de carregar o bico com nada pendurado nele) e é por isso que você está vendo os resultados erráticos, falhas ou nada aparecendo. Para isso, adicione:

     @interface TableTestViewController : UIViewController { TableTestTableViewController *tableViewController; } @property (nonatomic, retain) IBOutlet TableTestTableViewController *tableViewController; 

    e conecte-o no Construtor de Interface à instância TableTestTableViewController .

Com o acima exposto isso funcionou bem na minha máquina.

Também acho que seria bom afirmar a motivação por trás de tudo isso (em vez de apenas usar o UITableViewController com seu próprio UITableView ). No meu caso, foi para usar outras visões que apenas o UITableView na mesma canvas cheia de conteúdo. Então eu posso adicionar outros UILabels ou UIImages sob o UIView e mostrar o UITableView sob eles ou acima deles.

Passei muitas horas puxando o cabelo para tentar descobrir por que um UITableView não aparecia quando eu o tinha embutido em uma ponta separada, em vez de na ponta principal. Eu finalmente encontrei sua discussão acima e percebi que era porque o meu UITableViewController não estava sendo mantido! Aparentemente, as propriedades delegate e datasource do UITableView não estão marcadas como “reter” e então meu nib estava carregando, mas o controlador estava sendo lançado … E devido às maravilhas do objective-c, não recebi nenhuma mensagem de erro … Eu ainda não entendi porque isso não aconteceu. Eu sei que eu vi “mensagem enviada para liberado xxx” antes … por que não estava me dando uma dessas?!?

Eu acho que a maioria dos desenvolvedores presumiria que a estrutura que eles constroem em um construtor de interfaces seria mantida em um contexto maior (o Nib) e não sujeita a liberação. Eu acho que eu sei por que eles fazem isso .. para que o iPhone pode soltar e recarregar partes da ponta na memory baixa. Mas cara, isso era difícil de descobrir.

Alguém pode me dizer onde eu deveria ter lido sobre esse comportamento nos documentos?

Além disso – sobre ligar a visão. Primeiro, se você arrastar um a partir do construtor de UI, verá que eles IBOutlet a propriedade view (que é um IBOutlet ) à visualização de tabela. Não é necessário expor o tableView , que parece ser configurado internamente. Na verdade, nem parece ser necessário definir a exibição a menos que você queira a notificação viewDidLoad . Acabei de quebrar a conexão de visualização entre o meu uitableview e o uitableviewcontroller (apenas delegado e conjunto de fonts de dados) e aparentemente está funcionando bem.

Sim, por algum motivo (por favor, entre em contato se alguém souber por quê …) A propriedade tableView do UITableViewController não é exposta como um IBOutlet , embora seja uma propriedade pública. Portanto, quando você usa o Interface Builder, não consegue ver essa propriedade para se conectar ao seu outro UITableView . Portanto, na sua subclass, você pode criar uma propriedade tableView marcada como um IBOutlet e conectá-la.

Isso tudo parece hacky e uma solução para mim, mas parece ser a única maneira de separar UITableViewController's UITableView e colocá-lo em outro lugar na hierarquia da interface do usuário. Eu me deparei com o mesmo problema quando tentei projetar a visualização onde há outras coisas além do UITableView e foi assim que resolvi isso … Essa é a abordagem correta ???

Eu consegui fazer isso funcionar. Eu construí um painel muito bom com 4 TableViews e um webview com vídeo. A chave é ter os controladores tableView separados e os IBOutlets para os outros controladores de visualização de tabela definidos no controlador de visualização. No UIB, você só precisa conectar os outros controladores de visualização de tabela ao proprietário do arquivo do controlador de visualização. Em seguida, conecte as tabelas aos controladores de visualização correspondentes para a origem de dados e delegado.