“Layout automático ainda necessário após executar -layoutSubviews” com a subclasse UITableViewCell

115

Usando o XCode 4.5 e iOS 6, estou desenvolvendo um aplicativo com uma visualização de tabela simples com células personalizadas. Já fiz isso centenas de vezes no iOS 5 e inferior, mas por algum motivo o novo sistema autoLayout está me dando muitos problemas.

Eu configurei minha visualização de tabela e célula de protótipo em IB, adicionei subvisualizações e conectei-as como IBOutlets, em seguida, configurei meu delegado e fonte de dados. No entanto, agora, sempre que a primeira célula é buscada cellForRowAtIndexPath, recebo o seguinte erro:

*** Falha de declaração em - [ShopCell layoutSublayersOfLayer:], /SourceCache/UIKit_Sim/UIKit-2372/UIView.m:5776

*** Encerrando o aplicativo devido à exceção não detectada 'NSInternalInconsistencyException', motivo: 'Layout automático ainda necessário após a execução de -layoutSubviews. A implementação de -layoutSubviews da ShopCell precisa chamar super. '

Não implementei um método -layoutSubviews em minha célula com subclasse (ShopCell) e, mesmo quando tento fazer isso e adicionar a super chamada, como sugere, ainda recebo o mesmo erro. Se eu remover as subvisualizações da célula em IB e alterá-las para um UITableViewCell padrão, tudo funcionará conforme o esperado, embora, é claro, fique sem dados em minhas células.

Tenho quase certeza de que está faltando algo simples, mas não consigo encontrar nenhuma documentação ou guia que sugira o que fiz de errado. Qualquer ajuda seria apreciada.

Edit: Apenas tentei alterá-lo para um UITableViewCell em IB e deixar todas as subvisualizações no lugar, ainda o mesmo erro.

Mike Mayo
fonte
Tente lldb [[UIWindow keyWindow] _autoLayoutTrace]na área do depurador se o layout automático for usado.
A-Live
3
Você está usando UIView para a célula personalizada em vez de UITableViewCell? Eu tive o mesmo problema. Eu tinha o UIView para a célula personalizada e estava adicionando subvisões a ele. Alterado para UITableViewCell e funcionou.
Ei, Mike, como você está definindo os pontos de venda? Eles são propriedades em seu arquivo de implementação em uma extensão de classe?
kocodude
@ A-Live Sempre que tento usar esse método, obtenho um erro no depurador .... este método ainda é válido? Edit: Nevermind, é um l minúsculo no autolayout.
borrrden
desmarque a caixa autoLayout no inspetor e limpe e execute. vai funcionar com certeza.
Nico

Respostas:

57

Eu encontrei o mesmo problema ao adicionar manualmente restrições no código. No código, eu estava fazendo o seguinte:

{
    [self setTranslatesAutoresizingMaskIntoConstraints:YES];
    [self addSubview:someView];
    [self addSubview:someOtherView];
    [self addConstraint:...];
}

Hipótese

Pelo que posso dizer, o problema é que, quando você desativa translatesAutoresizingMaskIntoConstraints, UITableViewCell começa a usar o Auto Layout e falha naturalmente porque a implementação subjacente de layoutSublayersForLayernão chama super. Alguém com Hopper ou alguma outra ferramenta pode confirmar isso. Já que você está usando IB, você provavelmente está se perguntando por que isso é um problema ... e isso porque usar IB desativa automaticamente as translatesAutoresizingMaskIntoConstraintsvisualizações às quais adiciona restrições (adicionará automaticamente uma restrição de largura e altura em seu lugar).

Solução

Minha solução foi mover tudo para o contentView.

{
   [self.contentView addSubview:someView];
   [self.contentView addSubview:someOtherView];
   [self.contentView addConstraint:...];
}

Não tenho 100% de certeza se isso vai funcionar no Interface Builder, mas se você empurrar tudo para fora de sua célula (supondo que você tenha algo diretamente nele), então deve funcionar. Espero que isso ajude você!

Malaxeur
fonte
4
Também precisei adicionar subview.translatesAutoresizingMaskIntoConstraints = NO'cada subvisualização que estava adicionando ao contentView.
Jay Peyer
5
Isso funcionou para mim. Além disso, certifique-se de não ligar self.contentView.translatesAutoresizingMaskIntoConstraints = NOpara o UITableViewCell.
Maurizio
53

Aparentemente, a implementação de layoutSubviews de UITableViewCell não chama super, o que é um problema com o layout automático. Eu estaria interessado em ver se colocar a categoria abaixo em projetos corrige as coisas. Ajudou em um projeto de teste.

#import <objc/runtime.h>
#import <objc/message.h>

@implementation UITableViewCell (FixUITableViewCellAutolayoutIHope)

+ (void)load
{
    Method existing = class_getInstanceMethod(self, @selector(layoutSubviews));
    Method new = class_getInstanceMethod(self, @selector(_autolayout_replacementLayoutSubviews));

    method_exchangeImplementations(existing, new);
}

- (void)_autolayout_replacementLayoutSubviews
{
    [super layoutSubviews];
    [self _autolayout_replacementLayoutSubviews]; // not recursive due to method swizzling
    [super layoutSubviews];
}

@end

Posso adicionar o problema que apareceu para mim ao usar um backgroundView na célula da tabela, pois isso é adicionado como uma subvisualização à célula (enquanto a maioria das subvisualizações deve ser adicionada ao contentView da célula da tabela, que geralmente deve funcionar melhor).

Observação: parece que esse bug foi corrigido no iOS7; Consegui remover este código, ou pelo menos adicionar uma verificação de tempo de execução para que seja feito apenas se estiver executando no iOS6.

Carl Lindberg
fonte
O estranho é que funciona bem com um UITableViewCell simples para mim, mas não para uma subclasse ...
borrrden
Você poderia pensar isso, mas tudo o que tenho é um método init, nada mais> <. Eu nunca ignorei layoutSubviews em minha vida haha. Acho que o problema é que a visualização personalizada que UITableViewCell usa como visualização raiz não pode usar layout automático porque substitui layoutSubviews (então, quando você tenta adicionar restrições à visualização raiz, ele falhará)
borrrden
6
Tive que criar essa categoria UITableViewpelo mesmo motivo (iOS 6.1 b1)
Joshua J. McKinnon
5
Existe uma correção semelhante para TableHeaderView, pois o problema ainda existe no ios 7?
Softlion
1
Isso funciona muito bem. Eu encontrei esse problema quando tentei centralizar um subview UIVIew em um UITableView. Mesmo no iOS 7, a afirmação ocorre. Mas isso não ocorre no iOS 8, então eles devem ter corrigido o bug.
Jordan H
33

Eu tive o mesmo bug por alguns meses. Mas descobri qual era o problema.

Quando eu crio um arquivo IB, um UIViewjá é adicionado. Se você usar esta visualização, o aplicativo não trava quando o layout automático é desabilitado (mas há outros problemas). Quando você usar o layout automático, você tem que selecionar a direita vista na biblioteca de objetos: UITableViewCell.

Na verdade, você deve sempre usar este item porque todas as subvisualizações são adicionadas ao contentViewde UITableViewCell.

Isso é tudo. Tudo vai ficar bem.

Arnaud
fonte
Essa não deve ser uma resposta aceita porque a questão não é sobre uma implementação usando IB e porque esse problema pode ocorrer quando você não está usando IB. Se você estiver fazendo suas visualizações programaticamente, a resposta de @PhilLoden é mais viável.
Eric,
Eu não entendi a resposta. alguém pode explicar mais claramente? obrigado
hasan
Acho que tenho esse direito. é o suficiente para verificar a atenção da classe. no inspetor de identidade no construtor de interface? ou o que foi adicionado era de outro tipo e classe att. foi atualizado mais tarde? isso também causa o problema?
hasan
@ hasan83 Você pode realmente retornar a célula. Um UITableViewCell é basicamente um UIView com um identificador de reutilização.
Arnaud
17

Eu tive o mesmo problema com custom UITableViewHeaderFooterView+ xib.

Vi algumas respostas aqui, mas descobri que implementação -layoutSubviewsem minha classe de visualização de rodapé personalizada corrige o problema:

-(void)layoutSubviews
{
    [super layoutSubviews];
    [self layoutIfNeeded]; // this line is key
}
Sound Blaster
fonte
1
Esteja ciente de que isso pode resultar em um loop infinito e, finalmente, EXC_BAD_ACCESS KERN_PROTECTION_FAILURE
mbi
15

Eu estava vendo isso como resultado da modificação de restrições em minha implementação de layoutSubviews. Mover a chamada para super do início ao fim do método corrigiu o problema.

Phil Loden
fonte
Isso funcionou para mim. Eu tenho um UICollectionViewCell personalizado que estou formatando em layoutSubviews. Alguém sabe por que essa solução funciona?
STANGMMX
@STANGMMX, a resposta de A'sa Dickens explica o porquê.
Fábio Oliveira
15

Tive o mesmo problema no iOS 7 (o iOS 8 parece corrigir). A solução para mim foi chamar [self.view layoutIfNeeded]no final do meu viewDidLayoutSubviewsmétodo.

Keller
fonte
Obrigado. Isso me ajuda. Encontrei esse problema ontem (no iOS 7). ajuda para iOS 7.
Alexander
@MaciejSwic veja minha resposta no topo.
Sound Blaster
Isso funcionou para mim! Usando iOS 7.1 com Swift. Eu estava removendo e adicionando uma restrição em viewDidLayoutSubviews. Removi a superchamada e ainda não estava funcionando, mas essa solução funcionou! dê uma folha a este dinossauro! :)
jomafer
Funcionou para mim também usando iOS 7.1!
fdlr
14

Eu tive o mesmo problema. O problema estava na forma como estava criando a célula Xib. Eu criei um Xib normalmente e apenas alterei o tipo do "UIView" padrão para minha classe UITableViewCell personalizada. A maneira correta de fazer isso é primeiro excluir a visualização padrão e, em seguida, arrastar o objeto de célula da visualização de tabela para o xib. Mais detalhes aqui: http://allplayers.github.io/blog/2012/11/18/Custom-UITableViewCell-With-NIB/

Trunal Bhanse
fonte
1
Visão perfeita! Eu demoraria muito para perceber isso, especialmente porque meus aplicativos não travariam se eu usasse um UIView com AutoLayout desativado.
Guilherme
Super! veja também @Arnaud respone abaixo
Lubbo
7

Resolvi o problema desativando o "Autolayout" para todas as subvisualizações de minha célula de exibição de tabela personalizada.

No xib de uma célula personalizada, selecione uma subvisualização e desmarque File Inspector> Interface Builder Document> Use Autolayout

Johno
fonte
4
Eu fiz o mesmo. Não é realmente uma solução se você quiser usar o autolayout
ajmccall
7

Tive um problema semelhante, não UITableViewCellem UITableViewsi, mas em si mesmo. Por ser o primeiro resultado no Google vou postar aqui. Acontece que esse viewForHeaderInSectionera o problema. Criei um UITableViewHeaderFooterViewe configurei translatesAutoresizingMaskIntoConstraintspara NO. Agora vem a parte interessante:

iOS 7:

// don't do this on iOS 7
sectionHeader.translatesAutoresizingMaskIntoConstraints = NO;

Se eu fizer isso, o aplicativo trava com

Layout automático ainda é necessário após executar -layoutSubviews. A implementação de -layoutSubviews do UITableView precisa chamar super.

OK, pensei que você não pode usar o layout automático em um cabeçalho de exibição de tabela e apenas nas subvisualizações. Mas essa não é toda a verdade como você verá mais tarde. Para resumir: Não desative a máscara de redimensionamento automático para o cabeçalho no iOS 7. Caso contrário, está funcionando bem.

iOS 8:

// you have to do this, otherwise you get an auto layout error
sectionHeader.translatesAutoresizingMaskIntoConstraints = NO;

Se eu não usasse isso, obteria o seguinte resultado:

Incapaz de satisfazer as restrições simultaneamente.

Para iOS 8, você deve desativar a máscara de redimensionamento automático para o cabeçalho.

Não sei por que ele se comporta dessa forma, mas parece que a Apple corrigiu algumas coisas no iOS 8 e o layout automático está funcionando de forma diferente no iOS 7 e iOS 8.

testando
fonte
Companheiro, você acabou de salvar meu dia!
Marcin Małysz
5

Como alguém acima já afirmou, quando você cria uma visualização para uso em um UITableView, você deve excluir a visualização criada por padrão e arrastar um UITableViewCell ou UITableViewHeaderFooterView como a visualização raiz. No entanto, existe uma maneira de consertar o XIB caso você tenha perdido essa parte. Você tem que abrir o arquivo XIB em um editor de texto e na tag raiz e seu filho direto adicionar / alterar o atributo translatesAutoresizingMaskIntoConstraintspara YES, por exemplo

<view contentMode="scaleToFill" horizontalHuggingPriority="1000" id="1" translatesAutoresizingMaskIntoConstraints="YES" customClass="MXWCollapsibleTableHeaderView">

Maksymilian Wojakowski
fonte
2

Estou encontrando isso e parece que está relacionado às subclasses UITableViewCell como células de protótipo que têm especificamente outras subclasses UIView personalizadas adicionadas a elas. Enfatizo o 'custom' aqui porque tive sucesso com células que têm apenas filhos UIKit, mas ele falha ao tentar construir as restrições para visualizações que criei sob medida, gerando o erro declarado na pergunta do autor.

Tive que separar minhas células em pontas independentes que não usam AutoLayout.

Vamos esperar que a Apple limpe essa bagunça.

Lee Probert
fonte
2

Adicione suas subvisualizações ao contentView da célula em vez da própria célula. Então, em vez de:

[self addSubview:someView];

você deve usar

[self.contentView addSubview:someView];

Christian Aberger
fonte
1

Eu encontrei este porque eu tinha adicionado inicialmente um UIView em vez de um UITableViewCell a um arquivo xib.

mjmdavis
fonte
1

Eliminei esse erro desacoplando o backgroundViewconector de meu plano de fundo UIImageViewe o accessoryViewconector de minhas UIButtonpersonalizações. Suspeito que eles não deveriam ser usados ​​da maneira como eu os estava usando.

Owen Godfrey
fonte
1

Eu tive esse problema pela primeira vez hoje. Até agora, tive várias experiências no uso de subclasses de protótipo UITableViewCell, mas nunca tive esse problema. O que era diferente na célula com a qual estava trabalhando era que eu tinha um IBOutlet para o -backgroundView que estava usando para colorir a célula. Descobri que, se criasse uma nova propriedade e ainda adicionasse um novo UIView que se estendesse por toda a célula, essa afirmação desapareceria. Para verificar se essa era a causa, voltei a anexar essa visualização ao outlet backgroundView e a declaração reapareceu. Até agora, nenhum outro problema com o uso do AutoLayout em um protótipo de subclasse UITableViewCell desde que fiz essa alteração.

Eric Schramm
fonte
1

Não obtive uma solução adequada para esse problema, mas você pode corrigi-lo usando frames e não definindo a propriedade translatesAutoresizingMaskIntoConstraints como No (por padrão é sim, então não defina)

CGRect headerViewFrame = CGRectMake(0,0,320,60); //Frame for your header/footer view
UIView *tableHeaderView = [[UIView alloc] initWithFrame:headerViewFrame];
tableHeaderView.translatesAutoresizingMaskIntoConstraints = Yes; //Or don't set it at all
[self.tableView setTableHeaderView:tableHeaderView];
sanjana
fonte
0

Eu tenho experimentado a mesma coisa. Descobriu-se que, se você adicionar programaticamente uma subvisualização de seu ShopCell .xib / storyboard, que usa o layout automático, como uma subvisualização para outra visão, essa exceção pode ser lançada, dependendo de como suas restrições estão configuradas. Meu palpite é que as restrições criadas no IB são o que cria os problemas ao adicionar programaticamente uma visualização como uma subvisão, uma vez que mantém as restrições de viewA -> viewB, enquanto isso você pode adicionar viewB como uma subvisão de viewC. Você entendeu (essa frase até me confunde)?

Na minha situação - visto que foram as visualizações muito simples que causaram o problema - criei as visualizações programaticamente e não no IB. Isso resolveu tudo. Você pode extrair essas visualizações para outros arquivos xib e desativar o layout automático para eles. Eu acho que funcionaria.

Kasper Munck
fonte
0

Em algumas situações, isso resolve o problema de layout facilmente (dependendo do seu layout). Dentro de sua subclasse UITableView, em awakeFromNib ou init, defina a máscara de redimensionamento automático:

self.contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

Por padrão, é definido como UIViewAutoresizingNone

Arie Litovsky
fonte
Isso resolveu o problema que eu estava enfrentando. Estou usando o layout automático em uma célula da tabela combinado com [tableViewCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].heightpara obter a altura, que uso em heightForRowAtIndexPath.
NathanAldenSr
0

No meu caso,

O UIImageView referenciado para o layout automático para UITableView é atribuído a backgroundView do UITableView.

self.tableView.backgroundView = self.tableBackgroundImageView;

Portanto, removi UIImageView para backgroundView do UIView (visualização Root) e redefina (remova) todas as referências de layout automático para esse UIImageView. Coloquei esse UIImageView para plano de fundo do lado de fora do UIView (visualização raiz). E então atribua ao backgroundView do UITableView no código.

Em seguida, corrigido.

Conan Kim
fonte
0

Eu encontrei a solução.

No meu caso, criei a visualização da célula em storyboard (com layout automático habilitado) e defini a interface UITableViewCell customizada em meu ViewController.m, tenho que mover a interface para ViewController.h.

Nim Thitipariwat
fonte
0

Eu encontrei o mesmo problema quando uso o storyboard para criar o UITableViewCell personalizado. Felizmente encontrei o problema, porque eu coloquei o acessórioView ([UITableViewCell setAccessoryView:]) no UIButton que adicionei à célula.

Assim ocorreu no meu projeto quando executado no iOS6.

Solução

Eu libero a saída entre o acessórioView e meu botão que continha a célula personalizada.

Proposta

Você não deve usar os elementos nativos do UITableViewCell e alterá-los.

Wanqiang Ji
fonte
0

Esse problema pode ser causado pelo esquecimento de ligar para [super viewDidAppear:]dentro viewDidAppear, mas tenho certeza de que não é a única causa.

Michaelsnowden
fonte
0

Eu tive exatamente o mesmo problema. Aqui está o problema com meu projeto:
Quando trabalhei no Interface Builder para criar um UITableViewCell personalizado, arrastei uma Visualização em vez de uma Célula de Visualização de Tabela do painel de coleção de objetos no Xcode
como a célula de tabela personalizada.
Se você estiver na mesma situação, aqui está a solução:
Exclua a visualização no construtor de interface, certifique-se de arrastar uma Célula de Visualização de Tabela do painel de coleção de objetos e refazer a visualização de célula da tabela customizada. Você pode copiar os objetos da visualização antiga e colá-los na tela para a nova Célula de Visualização de Tabela.

us_david
fonte
0

Tive um problema muito semelhante com a visualização do rodapé da tabela que estava configurando no Xcode 6, iOS 7 ou 7. A solução estava no formato do arquivo nib. Aparentemente, ele estava preso no formato Xcode 4 ou algo assim. Alterar as configurações do arquivo para "abre em: Xcode 6.0" (ou Padrão, nesse caso), corrigiu instantaneamente. Achei a solução por acaso: estava me deixando louco, então apaguei o arquivo inteiro e criei novamente, obviamente com as configurações padrão. Não tenho ideia de por que simplesmente editar o arquivo no Xcode mais recente não o converteu em um formato Xcode 5+, como geralmente acontece.

f

user3099609
fonte
0

Eu fui teve o mesmo problema. Eu fui para o meu DetailViewController e renomeei o identificador para UIView. Ele estava anteriormente em UITableView. Isso resolveu o problema. Este problema não precisa estar em seu DetailViewController. Pode ser em qualquer outro. Tente renomeá-lo para o identificador respeitado.

RandomDude
fonte
0

Eu tive um problema semelhante com células de visualização de tabela estática no IB. Uma das células tinha uma subvisualização que tinha uma classe que foi alterada por engano para uma subclasse de UITextfield. O compilador não deu nenhum aviso / erro. Mas, em tempo de execução, o sistema não conseguiu carregar o controlador de visualização com a falha mencionada como resultado.

Yannick Winters
fonte
0

O problema é o sequenciamento das chamadas de layout para as subvisualizações:

Verificação de saída

Aparece no iOS <8

Shanu ji
fonte
0

Solução: altere as restrições antes de chamar super layoutSubviews

- (void)layoutSubviews
{

    [self _updateConstraints];

    [super layoutSubviews];
}
Abuharsky
fonte
0

Eu modifiquei a resposta de Carl Lindberg para substituir em UITableViewvez disso e começou a funcionar para mim:

UITableView + AutoLayoutFix.h

@interface UITableView (AutoLayoutFix)
@end

UITableView + AutoLayoutFix.m

#import <objc/runtime.h>

@implementation UITableView (AutoLayoutFix)

+ (void)load
{
    Method existingMethod = class_getInstanceMethod(self, @selector(layoutSubviews));
    Method newMethod = class_getInstanceMethod(self, @selector(_autolayout_replacementLayoutSubviews));

    method_exchangeImplementations(existingMethod, newMethod);
}

- (void)_autolayout_replacementLayoutSubviews
{
    [super layoutSubviews];
    [self _autolayout_replacementLayoutSubviews]; // not recursive due to method swizzling
    [super layoutSubviews];
}

@end

Então MyViewController.m, acabei de importar a categoria:

#import "UITableView+AutoLayoutFix.h"
lavrador
fonte
0

Eu encontrei o mesmo problema e finalmente descobri que o motivo era que adicionei uma restrição ao UITableViewCell, que deveria ser o contentView do UITableViewCell . Quando mudei a restrição, tudo correu bem!

Alfy
fonte