Prática adequada para subclassificar o UIView?

158

Estou trabalhando em alguns controles de entrada personalizados baseados em UIView e tentando verificar a prática adequada para configurar a exibição. Ao trabalhar com um UIViewController, é bastante simples de usar os loadViewe relacionados viewWill, viewDidmétodos, mas quando subclasse um UIView, os methosds mais próximos que eu tenho são `awakeFromNib, drawRecte layoutSubviews. (Estou pensando em termos de configuração e retornos de chamada de desmontagem.) No meu caso, estou configurando meu quadro e visualizações internas layoutSubviews, mas não estou vendo nada na tela.

Qual é a melhor maneira de garantir que minha visualização tenha a altura e a largura corretas que eu quero? (Minha pergunta se aplica independentemente de eu estar usando o layout automático, embora possa haver duas respostas.) Qual é a "melhor prática" adequada?

Moshe
fonte

Respostas:

298

A Apple definiu com bastante clareza como subclasse UIViewno documento.

Confira a lista abaixo, especialmente dê uma olhada initWithFrame:e layoutSubviews. O primeiro se destina a configurar o quadro do seu, UIViewenquanto o último se destina a configurar o quadro e o layout de suas subvisões.

Lembre-se também de que isso initWithFrame:é chamado apenas se você estiver instanciando sua UIViewprogramaticamente. Se você estiver carregando de um arquivo de ponta (ou um storyboard), initWithCoder:será usado. E no initWithCoder:quadro ainda não foi calculado, portanto, você não pode modificar o quadro que configurou no Interface Builder. Conforme sugerido nesta resposta, você pode pensar em chamar initWithFrame:de initWithCoder:para configurar o quadro.

Por fim, se você carregar a UIViewpartir de uma ponta (ou de um storyboard), também terá a awakeFromNiboportunidade de executar inicializações personalizadas de quadro e layout, pois quando awakeFromNibé chamado, é garantido que todas as visualizações na hierarquia foram desarquivadas e inicializadas.

Do documento de NSNibAwaking(agora substituído pelo documento de awakeFromNib):

As mensagens para outros objetos podem ser enviadas com segurança a partir do awakeFromNib - quando é garantido que todos os objetos sejam desarquivados e inicializados (embora não necessariamente despertados, é claro)

Também é importante notar que, com o autolayout, você não deve definir explicitamente o quadro da sua exibição. Em vez disso, você deve especificar um conjunto de restrições suficientes, para que o quadro seja calculado automaticamente pelo mecanismo de layout.

Diretamente da documentação :

Métodos para substituir

Inicialização

  • initWithFrame:É recomendável que você implemente esse método. Você também pode implementar métodos de inicialização personalizados, além ou em vez deste método.

  • initWithCoder: Implemente esse método se você carregar sua visualização de um arquivo de ponta do Interface Builder e sua visualização exigir inicialização personalizada.

  • layerClassImplemente esse método apenas se você desejar que sua exibição use uma camada diferente de Core Animation para seu armazenamento de suporte. Por exemplo, se você estiver usando o OpenGL ES para fazer seu desenho, substitua esse método e retorne a classe CAEAGLLayer.

Desenho e impressão

  • drawRect:Implemente esse método se sua visualização desenhar conteúdo personalizado. Se sua vista não faz nenhum desenho personalizado, evite substituir esse método.

  • drawRect:forViewPrintFormatter: Implemente esse método apenas se desejar desenhar o conteúdo da sua exibição de maneira diferente durante a impressão.

Restrições

  • requiresConstraintBasedLayout Implemente esse método de classe se sua classe de exibição exigir restrições para funcionar corretamente.

  • updateConstraints Implemente esse método se sua visualização precisar criar restrições personalizadas entre suas subvisões.

  • alignmentRectForFrame:, frameForAlignmentRect:Implemente esses métodos para substituir como suas visualizações estão alinhadas com outras visualizações.

Layout

  • sizeThatFits:Implemente esse método se desejar que sua visualização tenha um tamanho padrão diferente do que normalmente seria durante as operações de redimensionamento. Por exemplo, você pode usar esse método para impedir que sua exibição diminua até o ponto em que as subvisões não possam ser exibidas corretamente.

  • layoutSubviews Implemente esse método se você precisar de um controle mais preciso sobre o layout de suas subvisões do que os comportamentos de restrição ou de redimensionamento automático fornecem.

  • didAddSubview:, willRemoveSubview:Implemente esses métodos conforme necessário para rastrear as adições e remoções de subvisões.

  • willMoveToSuperview:, didMoveToSuperviewImplemente esses métodos conforme necessário para rastrear o movimento da visualização atual em sua hierarquia de visualizações.

  • willMoveToWindow:, didMoveToWindowImplemente esses métodos conforme necessário para rastrear o movimento da sua exibição para uma janela diferente.

Manipulação de eventos:

  • touchesBegan:withEvent:, touchesMoved:withEvent:, touchesEnded:withEvent:, touchesCancelled:withEvent:Implementar esses métodos se você precisa para lidar com eventos de toque diretamente. (Para entrada baseada em gestos, use reconhecedores de gestos.)

  • gestureRecognizerShouldBegin: Implemente esse método se sua visualização manipular eventos de toque diretamente e desejar impedir que os reconhecedores de gestos anexados acionem ações adicionais.

Gabriele Petronella
fonte
e quanto ao quadro (vazio) setFrame: (CGRect)?
usar o seguinte comando
bem, você pode definitivamente substituí-lo, mas com que finalidade?
Gabriele Petronella 28/09
a disposição da mudança / desenho a qualquer hora o tamanho ou a localização muda de quadro
PFrank
1
Que tal layoutSubviews?
Gabriele Petronella
Em stackoverflow.com/questions/4000664/… , "o problema é que as subviews podem não apenas alterar seu tamanho, mas também podem animar essa alteração de tamanho. Quando o UIView executa a animação, ele não chama layoutSubviews de cada vez." Não testei isso pessoalmente embora
PFrank
38

Isso ainda aparece no Google. Abaixo está um exemplo atualizado para o swift.

A didLoadfunção permite que você coloque todo o seu código de inicialização personalizado. Como outros já mencionaram, didLoadserá chamado quando uma visualização for criada programaticamente via init(frame:)ou quando o desserializador XIB mesclar um modelo XIB à sua visualização viainit(coder:)

Além : layoutSubviewse updateConstraintssão chamados várias vezes para a maioria das visualizações. Isso se destina a layouts e ajustes avançados de várias passagens quando os limites de uma exibição são alterados. Pessoalmente, evito layouts de várias passagens quando possível, porque eles queimam os ciclos da CPU e tornam tudo uma dor de cabeça. Além disso, insiro o código de restrição nos próprios inicializadores, pois raramente os invalido.

import UIKit

class MyView: UIView {
  //-----------------------------------------------------------------------------------------------------
  //Constructors, Initializers, and UIView lifecycle
  //-----------------------------------------------------------------------------------------------------
  override init(frame: CGRect) {
      super.init(frame: frame)
      didLoad()
  }

  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    didLoad()
  }

  convenience init() {
    self.init(frame: CGRectZero)
  }

  func didLoad() {
    //Place your initialization code here

    //I actually create & place constraints in here, instead of in
    //updateConstraints
  }

  override func layoutSubviews() {
     super.layoutSubviews()

     //Custom manually positioning layout goes here (auto-layout pass has already run first pass)
  }

  override func updateConstraints() {
    super.updateConstraints()

    //Disable this if you are adding constraints manually
    //or you're going to have a 'bad time'
    //self.translatesAutoresizingMaskIntoConstraints = false

    //Add custom constraint code here
  }
}
seo
fonte
Você pode explicar quando / por que você dividiria o código de restrição de layout entre um método chamado de seu processo init e layoutSubviews e updateConstraints? Parece que eles são os três possíveis locais candidatos para colocar o código de layout. Então, como você sabe quando / o que / por que dividir o código de layout entre os três?
argila Ellis
3
Eu nunca uso updateConstraints; updateConstraints pode ser bom porque você sabe que sua hierarquia de visualizações foi totalmente configurada no init, portanto, não é possível gerar uma exceção adicionando uma restrição entre duas visualizações que não estão na hierarquia :) layoutSubviews nunca deve ter modificações de restrição; ele pode facilmente causar uma recursão infinita, pois layoutSubviews é chamado se as restrições forem 'invalidadas' durante a passagem do layout. A configuração manual do layout (como na definição direta de quadros, o que raramente é necessário fazer, a menos que por motivos de desempenho) ocorre nas visualizações de layout. Pessoalmente, eu coloco criação restrições em init
seo
Para código de renderização personalizado, devemos substituir o drawmétodo?
Petrus Theron
14

Há um resumo decente na documentação da Apple , e isso é abordado bem na versão gratuita curso Stanford, disponível no iTunes. Apresento minha versão TL; DR aqui:

Se sua classe consiste principalmente de subvisões, o lugar certo para alocá-las é nos initmétodos. Para visualizações, existem dois initmétodos diferentes que podem ser chamados, dependendo se a visualização está sendo instanciada a partir do código ou de uma ponta / storyboard. O que faço é escrever meu próprio setupmétodo e chamá-lo de ambos osinitWithFrame:initWithCoder: métodos e .

Se você está fazendo um desenho personalizado, você realmente deseja substituir drawRect: sua exibição. Porém, se sua visualização personalizada for principalmente um contêiner para subvisões, provavelmente você não precisará fazer isso.

Substitua apenas layoutSubViewsse você quiser fazer algo como adicionar ou remover uma subvisão, dependendo se estiver na orientação retrato ou paisagem. Caso contrário, você poderá deixá-lo em paz.

passagem
fonte
Eu uso sua resposta para alterar o quadro do subView do modo de exibição (que está acordado) layoutSubViews, ele funcionou.
aeronaves
1

layoutSubviews deve definir o quadro nas visualizações filho, não na exibição em si.

Pois UIView, o construtor designado é tipicamente initWithFrame:(CGRect)framee você deve definir o quadro lá (ou no initWithCoder:), possivelmente ignorando o valor do quadro passado. Você também pode fornecer um construtor diferente e definir o quadro lá.

proxi
fonte
você poderia dar mais detalhes? Eu não sabia o que você queria dizer. Como definir o quadro do subView de uma exibição? a visão éawakeFromNib
aeronave
No avanço para 2016, você provavelmente não deve definir quadros e usar o autolayout (restrições). Se a visualização for proveniente de XIB (ou storyboard), a subvisão já deverá estar configurada.
11786