Carregando um UITableViewCell reutilizável de um Nib

89

Eu sou capaz de projetar UITableViewCells customizados e carregá-los perfeitamente usando a técnica descrita no tópico encontrado em http://forums.macrumors.com/showthread.php?t=545061 . No entanto, o uso desse método não permite mais a inicialização da célula com um reuseIdentifier, o que significa que você deve criar instâncias inteiras de cada célula a cada chamada. Alguém descobriu uma boa maneira de ainda armazenar em cache determinados tipos de células para reutilização, mas ainda ser capaz de projetá-los no Interface Builder?

Greg Martin
fonte

Respostas:

74

Basta implementar um método com a assinatura de método apropriada:

- (NSString *) reuseIdentifier {
  return @"myIdentifier";
}
Louis Gerbarg
fonte
Onde implementar este método?
Krishnan
2
Em sua subclasse UITableViewCell. developer.apple.com/library/ios/#DOCUMENTATION/UIKit/Reference/…
DenTheMan de
5
Isso é arriscado. O que acontece se você tiver duas subclasses de sua subclasse de célula e usar ambas em uma única exibição de tabela? Se eles enviarem a chamada do identificador de reutilização para super, você retirará da fila uma célula do tipo errado .............. Eu acho que você precisa substituir o método reuseIdentifier, mas ele retornará um identificador suplantado corda.
SK9
3
Para ter certeza de que é único, você pode fazer:return NSStringFromClass([self class]);
ivanzoid
119

Na verdade, como você está construindo a célula no Interface Builder, basta definir o identificador de reutilização lá:

IB_reuse_identifier

Ou se você estiver executando o Xcode 4, verifique a guia do inspetor de atributos:

insira a descrição da imagem aqui

(Editar: depois que seu XIB é gerado por XCode, ele contém um UIView vazio, mas precisamos de um UITableViewCell; portanto, você deve remover manualmente o UIView e inserir uma célula de visualização de tabela. Claro, IB não mostrará nenhum parâmetro UITableViewCell para um UIView.)

Tim Keating
fonte
O que eu defino os Identificadores, se eu usar essas células criadas pelo Nib para mais de uma célula? Isso nos levará a ter duas células com os mesmos identificadores.
Krishnan
4
Não pense nisso como um identificador exclusivo - pense mais como um nome de tipo.
Tim Keating
Não estou vendo a opção de definir um identificador por meio do construtor de interface integrado no Xcode 4.3.3. Definitivamente, estou definindo a classe para minha subclasse UITableViewCell. Eu estou apenas perdendo isso ou ele se foi?
Tyler de
3
Ok, resolvi meu problema. Se você começar com um objeto UIView no construtor de interface (dentro do Xcode 4) e alterar sua classe para UITableViewCell, não obterá as propriedades específicas da célula, como o identificador de reutilização. Para conseguir isso, você deve começar com um xib vazio e arrastar em um objeto de célula da tabela, que terá as propriedades específicas da célula que você pode editar.
Tyler
1
@Krishnan Pense desta forma - quando você cria a célula de exibição de tabela com o identificador X, você está dizendo "Dê-me uma célula do pool rotulada X." Se a piscina existe e há uma célula livre nela, ela a dá para você. Caso contrário, ele cria o pool (se necessário), em seguida, atualiza a célula, rotula-a como "X" e a entrega a você. Portanto, as células PODEM ser únicas - por exemplo, você pode criar um pool com apenas uma célula com um identificador específico - mas a biblioteca usa uma estratégia semelhante a uma lista livre para evitar a alocação / desalocação de memória.
Tim Keating
66

Agora, no iOS 5, há um método UITableView apropriado para isso:

- (void)registerNib:(UINib *)nib forCellReuseIdentifier:(NSString *)identifier
wzs
fonte
10
As outras respostas neste tópico, incluindo a resposta aceita, contêm conselhos desatualizados.
Kaelin Colclasure
isso é compatível com versões anteriores? Quer dizer, se eu desenvolver um aplicativo com SDK 5.0 e atingir o mínimo 4.0, o aplicativo será executado em dispositivos com iOS 4.0, por exemplo?
Abolfoooud
1
Não, isso não é compatível com versões anteriores, como qualquer nova API no iOS 5.0.
marzapower
funcionou um exemplo de como integrar isso em seu controlador: mindfiresolutions.com/…
mblackwell8
47

Não me lembro onde encontrei esse código originalmente, mas tem funcionado muito bem para mim até agora.

- (UITableViewCell *)tableView:(UITableView *)tableView 
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    static NSString *CellIdentifier = @"CustomTableCell";
    static NSString *CellNib = @"CustomTableCellView";

    UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        NSArray *nib = [[NSBundle mainBundle] loadNibNamed:CellNib owner:self options:nil];
        cell = (UITableViewCell *)[nib objectAtIndex:0];
    }

    // perform additional custom work...

    return cell;
}

Exemplo de configuração do Interface Builder ...

texto alternativo

arco-íris
fonte
12

Veja a resposta que dei a esta pergunta:

É possível projetar subclasses NSCell no Interface Builder?

Não só é possível projetar um UITableViewCell em IB, como também é desejável porque, caso contrário, toda a fiação manual e a colocação de vários elementos são muito tediosas. A performance é boa, desde que você tome o cuidado de tornar todos os elementos opacos quando possível. O reuseID é definido em IB para as propriedades do UITableViewCell, então você usa o ID de reutilização correspondente no código ao tentar desenfileirar.

Eu também ouvi de alguns dos apresentadores da WWDC no ano passado que você não deveria fazer células de table view em IB, mas é um monte de besteira.

Kendall Helmstetter Gelner
fonte
2
Você não deve criar células de visualização de tabela em IB se precisar de transparência e quiser um bom desempenho de rolagem. Para certas UIs, você precisa de transparência (para renderizar texto sobre gráficos, por exemplo). Às vezes, a única maneira de obter uma boa rolagem em hardware mais antigo (pré-A4) é renderizar em código, para evitar que a GPU tenha que compor várias camadas transparentes.
Nick Forge
2
É verdade, mas mesmo assim você pode ficar melhor deixando o desempenho sofrer um pouco em dispositivos antigos para facilitar a manutenção das células integradas ao IB. Você também pode usar essa técnica como uma célula de modelo da qual desenha elementos para evitar a composição com um método de desenho personalizado.
Kendall Helmstetter Gelner
6

Aqui está outra opção:

NSString * cellId = @"reuseCell";  
//...
NSArray * nibObjects = [[NSBundle mainBundle] loadNibNamed:@"CustomTableCell" owner:nil options:nil];

for (id obj in nibObjects)
{
    if ([obj isKindOfClass:[CustomTableCell class]])
    {
        cell = obj;
        [cell setValue:cellId forKey:@"reuseIdentifier"];
        break;
    }
}
JDL
fonte
Observe que esta é a única solução postada até agora que não requer a subclasse de seu custom UITableViewCellpara definir um valor exclusivo para reuseIdentifer. Acho que é isso que o operador original estava realmente procurando.
charshep
Espero que a Apple não negue meu pedido por usar isso ... Estou usando isso para obter células "estáticas" porque, em minha visualização de tabela, o processo de preencher uma célula é muito lento. Dessa forma, eu só tenho que executá-lo uma vez (dando um identificador diferente para cada linha). Obrigado!
Ricard Pérez del Campo
Muito útil, pois não há método UITableViewCell para definir isso manualmente!
Jesse
2

Eu crio minhas células de visualização personalizadas de maneira semelhante - exceto que conecto a célula por meio de um IBOutlet.

A [nib objectAt...]abordagem é suscetível a mudanças nas posições dos itens na matriz.

A UIViewControllerabordagem é boa - apenas experimentei e funciona bem.

MAS...

Em todos os casos, o initWithStyleconstrutor NÃO é chamado, portanto, nenhuma inicialização padrão é feita.

Eu li vários lugares sobre o uso de initWithCoderou awakeFromNib, mas nenhuma evidência conclusiva de que qualquer um deles seja o caminho certo.

Além de chamar explicitamente algum método de inicialização no cellForRowAtIndexPathmétodo, ainda não encontrei uma resposta para isso.

sww
fonte
awakeFromNib é a maneira certa de reagir a um objeto que está sendo carregado de um NIB.
Jon Hess de
2

Há algum tempo, encontrei uma ótima postagem no blog sobre esse tópico em blog.atebits.com e, desde então, comecei a usar a classe Loren Brichter ABTableViewCell para fazer todos os meus UITableViewCells.

Você acaba com um UIView simples de contêiner para colocar todos os seus widgets, e a rolagem é extremamente rápida.

Espero que isso seja útil.

Eric
fonte
2

Essa técnica também funciona e não requer um ivar funky em seu controlador de visualização para gerenciamento de memória. Aqui, a célula de visualização de tabela customizada reside em um xib denominado "CustomCell.xib".

 static NSData *sLoadedCustomCell = nil;

 cell = [tableView dequeueReusableCellWithIdentifier:@"CustomCell"];
 if (cell == nil) 
 {
   if (sLoadedCustomCell == nil) 
   {        
      // Load the custom table cell xib
      // and extract a reference to the cell object returned
      // and cache it in a static to avoid reloading the nib again.

      for (id loadedObject in [[NSBundle mainBundle] loadNibNamed:@"CustomCell" owner:nil options:nil]) 
      {
        if ([loadedObject isKindOfClass:[UITableViewCell class]]) 
        {
          sLoadedCustomCell = [[NSKeyedArchiver archivedDataWithRootObject: loadedObject] retain];
          break;
        }
    }
    cell = (UITableViewCell *)[NSKeyedUnarchiver unarchiveObjectWithData: sLoadedCustomCell];
  }
Bill Garrison
fonte
1
Arquivar e desarquivar é totalmente desnecessário.
Bryan Henry,
1
Arquivar / desarquivar é desnecessário se você quiser carregar a célula a partir de sua ponta, sempre que não puder ser desenfileirada. No entanto, se você quiser carregar a célula de sua ponta exatamente uma vez , precisará armazená-la em cache na memória. Eu realizo esse armazenamento em cache usando NSKeyedArchiving porque UITableViewCell não implementa NSCopying.
Bill Garrison,
1
Dito isso, o uso do UINib para carregar a célula obtém o mesmo efeito: carregar do disco uma vez, carregar da memória depois.
Bill Garrison,
2

O método Louis funcionou para mim. Este é o código que uso para criar o UITableViewCell a partir da ponta:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{   
    UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:@"CustomCellId"];

    if (cell == nil) 
    {
        UIViewController *c = [[UIViewController alloc] initWithNibName:@"CustomCell" bundle:nil];
        cell = (PostCell *)c.view;
        [c release];
    }

    return cell;
}
ggarber
fonte
2
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

static NSString *simpleTableIdentifier = @"CustomCell";

CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil)
{
    NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"CustomCell" owner:self options:nil];
    cell = [nib objectAtIndex:0];

    [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
}         

return cell;
}
Mohit
fonte
1

A solução gustavogb não funciona para mim, o que tentei foi:

ChainesController *c = [[ChainesController alloc] initWithNibName:@"ChainesController" bundle:nil];
[[NSBundle mainBundle] loadNibNamed:@"ChaineArticleCell" owner:c options:nil];
cell = [c.blogTableViewCell retain];
[c release];

Parece funcionar. O blogTableViewCell é o IBOutlet para a célula e ChainesController é o proprietário do arquivo.

Groumpf
fonte
1

Dos documentos UITableView sobre dequeueWithReuseIdentifier: "Uma string que identifica o objeto de célula a ser reutilizado. Por padrão, o identificador de uma célula reutilizável é seu nome de classe, mas você pode alterá-lo para qualquer valor arbitrário."

Substituir -reuseIdentifer é arriscado. O que acontece se você tiver duas subclasses de sua subclasse de célula e usar ambas em uma única exibição de tabela? Se eles enviarem a chamada do identificador de reutilização para super, você retirará da fila uma célula do tipo errado .............. Eu acho que você precisa substituir o método reuseIdentifier, mas ele retornará um identificador suplantado corda. Ou, se um não foi especificado, faça com que ele retorne a classe como uma string.

SK9
fonte
0

Pelo que vale a pena, perguntei a um engenheiro do iPhone sobre isso em uma das iPhone Tech Talks. Sua resposta foi: "Sim, é possível usar o IB para criar células. Mas não faça isso. Por favor, não".

agosto
fonte
1
Isso é estranho. Pelo menos duas das palestras na palestra de NY tiveram um código de demonstração que usou células criadas no IB.
Shawn Craver
3
Não tenho certeza se acredito nisso, pois eles usam o IB para criar células no projeto de exemplo Advanced Table View Cells da Apple.
iwasrobbed
Obrigado por isso. Cada vez que faço isso, tenho problemas. Pode ser por isso
skorulis
Ele provavelmente não tinha conhecimento suficiente sobre isso.
aryaxt
0

Segui as instruções da Apple vinculadas a Ben Mosher (obrigado!), Mas descobri que a Apple omitiu um ponto importante. O objeto que eles projetam em IB é apenas um UITableViewCell, assim como a variável que carregam dele. Mas se você realmente configurá-lo como uma subclasse customizada de UITableViewCell e escrever os arquivos de código para a subclasse, você pode escrever declarações IBOutlet e métodos IBAction no código e conectá-los aos seus elementos customizados em IB. Então não há necessidade de usar tags de visualização para acessar esses elementos e você pode criar qualquer tipo de célula maluca que desejar. É o paraíso Cocoa Touch.

David Casseres
fonte