Quando o layoutSubviews é chamado?

264

Eu tenho uma exibição personalizada que não está recebendo layoutSubviewmensagens durante a animação.

Eu tenho uma visão que preenche a tela. Ele possui uma subvisão personalizada na parte inferior da tela que é redimensionada corretamente no Interface Builder se eu alterar a altura da barra de navegação. layoutSubviewsé chamado quando a exibição é criada, mas nunca mais. Minhas subvisões estão dispostas corretamente. Se eu desativar a barra de status de chamada, a subvisão layoutSubviewsnão é chamada, mesmo que a visualização principal anima seu redimensionamento.

Em que circunstâncias é layoutSubviewsrealmente chamado?

Eu autoresizesSubviewsconfigurei NOpara o meu modo de exibição personalizado. E no Interface Builder, tenho os suportes superior e inferior e a seta vertical definida.


Outra parte do quebra-cabeça é que a janela deve ser tornada chave:

[window makeKeyAndVisible];

caso contrário, as subvisões não serão redimensionadas automaticamente.

Steve Weller
fonte

Respostas:

492

Eu tinha uma pergunta semelhante, mas não estava satisfeito com a resposta (ou qualquer outra que eu pudesse encontrar na rede), então tentei na prática e eis o que obtive:

  • initnão faz com layoutSubviewsque seja chamado (duh)
  • addSubview:faz layoutSubviewscom que a visualização seja adicionada, a visualização à qual está sendo adicionada (visualização de destino) e todas as subvisões do destino
  • O modo de exibição setFrame inteligente chama layoutSubviewsa exibição com seu quadro definido apenas se o parâmetro de tamanho do quadro for diferente
  • rolar um UIScrollView faz layoutSubviewscom que seja chamado no scrollView e sua superview
  • girar um dispositivo chama apenas layoutSubviewa visualização principal (a visualização primária de controladores de resposta)
  • O redimensionamento de uma exibição chamará layoutSubviewssua superview

Meus resultados - http://blog.logichigh.com/2011/03/16/when-does-layoutsubviews-get-called/

BadPirate
fonte
1
Ótima resposta. Eu sempre me perguntei layoutSubviews. Faz initWithFrame:com layoutSubviewsque seja chamado?
Robert
2
@ Robert - Eu estava usando initWithFrame ... então não.
BadPirate 5/07
8
@BadPirate: sim . De acordo com meus experimentos, se você redimensioná- view1.1lo chama layoutSubviewsde view1e então layoutSubviewsde view1.1. Esta chamada não propaga indefinidamente às superviews, chamando-a em view1.1.1apenas chamadas layoutSubviewsno view1.1e view1.1.1. Apenas mover-se sem alterar seu tamanho não exige layoutSubviewsnenhum deles.
João Portela
1
O viewDidLoad não é chamado no UIView (mas no UIViewController). View O carregamento foi chamado depois que o UIView foi iniciado.
21413 BadPirate
2
Baseado na minha experiência, a segunda regra pode não ser exato: quando eu adiciono view1.2para view1, layoutSubviewsde view1.2e view1são chamados, mas layoutSubviewsde view1.1não é chamado. ( view1.1e view1.2são subvisões de view1). Ou seja, nem todas as subvisões da visualização de destino são chamadas de layoutSubviewsmétodo .
HongchaoZhang
96

Com base na resposta anterior do @BadPirate, experimentei um pouco mais e criei alguns esclarecimentos / correções. Eu descobri que layoutSubviews:será chamado em uma exibição se e somente se:

  • Seus próprios limites (sem moldura) foram alterados.
  • Os limites de uma de suas subvisões diretas foram alterados.
  • Uma subvisão é adicionada à exibição ou removida da exibição.

Alguns detalhes relevantes:

  • Os limites são considerados alterados apenas se o novo valor for diferente, incluindo uma origem diferente . Observe especificamente que é por isso que layoutSubviews:é chamado sempre que um UIScrollView rola, pois ele executa a rolagem alterando a origem de seus limites.
  • Alterar o quadro só mudará os limites se o tamanho tiver sido alterado, pois essa é a única coisa propagada para a propriedade de limites.
  • Uma alteração nos limites de uma exibição que ainda não está em uma hierarquia de exibição resultará em uma chamada para layoutSubviews: quando a exibição for eventualmente adicionada a uma hierarquia de exibição .
  • E apenas para completar: esses gatilhos não chamam diretamente layoutSubviews, mas chamam setNeedsLayout, o que define / gera uma sinalização. Cada iteração do loop de execução, para todas as visualizações na hierarquia de visualizações , esse sinalizador é verificado. Para cada visualização em que a bandeira é encontrada levantada, ela layoutSubviews:é chamada e a bandeira é redefinida. As visualizações acima da hierarquia serão verificadas / chamadas primeiro.
Patrick Pijnappel
fonte
5
Não é possível aprovar esta resposta o suficiente. deve ser a melhor resposta. as três regras dadas são tudo o que você precisa. nunca encontrei nenhum comportamento de layoutSubview que essas regras não descrevessem perfeitamente.
Pärserk
1
Eu não acho que layoutSubviews é chamado quando os limites de uma subvisão direta são alterados. Acho que a chamada de layoutSubviews é apenas um efeito colateral do seu teste. Em alguns casos, layoutSubviews não será chamado quando os limites de uma subviews mudam. Por favor, verifique a resposta de frogcjn, porque a resposta dele é baseada na documentação da Apple, e não apenas em experimentos.
Simon Backx
19

https://developer.apple.com/library/prerelease/tvos/documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/CreatingViews/CreatingViews.html#//apple_ref/doc/uid/TP40009503-CH5-SW1

As alterações de layout podem ocorrer sempre que um dos seguintes eventos ocorrer em uma exibição:

uma. O tamanho do retângulo de limites de uma vista é alterado.
b. Ocorre uma alteração na orientação da interface, que geralmente aciona uma alteração no retângulo de limites da visualização raiz.
c. O conjunto de subcamadas do Core Animation associadas à camada da exibição é alterado e requer layout.
d. Seu aplicativo força o layout a ocorrer chamando o   método setNeedsLayout ou  layoutIfNeededde uma exibição.
e Seu aplicativo força o layout chamando o  setNeedsLayout método do objeto de camada subjacente da visualização.

frogcjn
fonte
Além disso, é importante ressaltar que nenhum desses eventos é chamado quando a visualização ainda não foi adicionada à pilha de visualizações. Você inclui isso com a palavra "pode", mas especificamente é chamado no próximo ciclo disponível do encadeamento principal do aplicativo quando ele é marcado como necessitando de layout por um desses eventos.
user1122069
13

Alguns dos pontos na resposta do BadPirate são apenas parcialmente verdadeiros:

  1. Para addSubViewponto

    addSubview faz com que layoutSubviews seja chamado na exibição que está sendo adicionada, a exibição à qual está sendo adicionada (exibição de destino) e todas as subvisões do destino.

    Depende da máscara de redimensionamento automático da exibição (exibição de destino). Se a máscara de redimensionamento automático estiver ativada, o layoutSubview será chamado em cada um addSubview. Se não houver máscara de redimensionamento automático, o layoutSubview será chamado somente quando o tamanho do quadro da vista (vista de destino) mudar.

    Exemplo: se você criou o UIView programaticamente (por padrão, não possui máscara de redimensionamento automático), o LayoutSubview será chamado apenas quando o quadro do UIView não for alterado a todos addSubview.

    É através dessa técnica que o desempenho do aplicativo também aumenta.

  2. Para o ponto de rotação do dispositivo

    A rotação de um dispositivo chama apenas layoutSubview na visualização pai (a visualização principal do viewView do controlador que responde)

    Isso pode ser verdade apenas quando o seu VC está na hierarquia do VC (raiz em window.rootViewController), bem, este é o caso mais comum. No iOS 5, se você criar um VC, mas ele não for adicionado a nenhum outro VC, esse VC não será notado quando o dispositivo girar. Portanto, sua visão não seria notada chamando layoutSubviews.

Mohit Nigam
fonte
9

Rastreei a solução até a insistência do Interface Builder de que as molas não podem ser alteradas em uma exibição com os elementos simulados da tela ativados (barra de status, etc.). Como as molas estavam desativadas para a visualização principal, essa visualização não podia mudar de tamanho e, portanto, foi rolada para baixo por completo quando a barra de chamada apareceu.

Desativar os recursos simulados, redimensionar a exibição e definir as molas corretamente causou a animação e o método foi chamado.

Um problema extra na depuração é que o simulador sai do aplicativo quando o status de chamada é alternado pelo menu. Saia do aplicativo = sem depurador.

Steve Weller
fonte
Você está dizendo que layoutSubviews é chamado quando a exibição é redimensionada? Eu sempre assumi que não é ...
Andrey Tarantsov
Isto é. Quando uma visão é redimensionada, ela precisa fazer algo com suas subvisões. Se você não fornecê-la, em seguida, ele se move-los automaticamente usando molas, amortecedores etc.
Steve Weller
8

chamando [self.view setNeedsLayout]; no viewController faz com que seja chamado viewDidLayoutSubviews

bademi
fonte
5

você olhou para layoutIfNeeded?

O trecho da documentação está abaixo. A animação funciona se você chamar esse método explicitamente durante a animação?

layoutIfNeeded Apresenta as subvisões, se necessário.

- (void)layoutIfNeeded

Discussão Use este método para forçar o layout das subvisões antes de desenhar.

Disponibilidade Disponível no iPhone OS 2.0 e posterior.

Willi Ballenthin
fonte
2

Ao migrar um aplicativo OpenGL do SDK 3 para 4, o layoutSubviews não era mais chamado. Após várias tentativas e erros, eu finalmente abri o MainWindow.xib, selecionei o objeto Window, no inspetor escolheu a guia Window Attributes (mais à esquerda) e marquei "Visible at launch". Parece que no SDK 3 ele ainda usava uma chamada layoutSubViews, mas não no 4.

6 horas de frustração terminadas.

Tal Yaniv
fonte
Você fez a chave da janela? Caso contrário, isso pode fazer com que todo tipo de coisa interessante não aconteça.
Steve Weller
-2

Um caso bastante obscuro, mas potencialmente importante, quando layoutSubviewsnunca é chamado, é:

import UIKit

class View: UIView {

    override class var layerClass: AnyClass { return Layer.self }

    class Layer: CALayer {
        override func layoutSublayers() {
            // if we don't call super.layoutSublayers()...
            print(type(of: self), #function)
        }
    }

    override func layoutSubviews() {
        // ... this method never gets called by the OS!
        print(type(of: self), #function)
    }
}

let view = View(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
milos
fonte
1
Por que essa resposta está aqui?
Dominik Bucher