Você tem um grande UIViewController? Para muitos, sim. Por um lado, ele trabalha com dados, por outro - com a interface.
As tarefas de separação da lógica da interface são descritas em centenas de artigos sobre arquitetura: MVP, MVVM, VIPER. Eles resolvem o problema do fluxo de dados, mas não respondem à questão de como trabalhar com a interface: em um local resta a criação de elementos, layout, configuração, processamento de entrada e animação.
Vamos separar a visualização do controlador e ver como o loadView () nos ajuda.

A interface do aplicativo para iOS é a hierarquia do
UIView
. As tarefas de cada
view
: criar elementos, personalizar, organizar em locais, animar. Isso pode ser visto nos métodos que estão na classe
UIView: addSubview(), drawRect(), layoutSubviews().
Se você observar os métodos da classe
UIViewController
, poderá ver que ele gerencia a
view:
carrega, responde ao carregamento de telas e ações do usuário e mostra novas telas. Frequentemente, o código que deveria estar no
UIView
, escrevemos nas subclasses do
UIViewController
, o que o torna muito grande. Separe.
loadView ()
O ciclo de vida de um
UIViewController
começa com
loadView()
. Uma implementação simplificada se parece com isso:
Podemos substituir o método e especificar nossa classe.
super.loadView()
não precisa ser chamado!
Implementação CustomView.swift O controlador irá carregar o
CustomView,
adicioná-lo à hierarquia, expor
.frame
. A propriedade
.view
será a classe que precisamos:
Mas enquanto o compilador não conhece a classe e acredita que existe um
UIView
normal. Vamos corrigir isso com uma função de conversão de tipo:
Agora você pode ver as variáveis
CustomView
:
Simplifique com o tipo associadoRuslan Kavetsky propôs remover a duplicação de código usando a expansão do protocolo:
protocol ViewSpecificController { associatedtype RootView: UIView } extension ViewSpecificController where Self: UIViewController { func view() -> RootView { return self.view as! RootView } }
Para cada novo controlador, você só precisa especificar o protocolo e a subclasse para o seu UIView
por meio de typealias
:
Código em uma subclasse de UIView
Criando e configurando controles
Fontes, cores, constantes e hierarquia podem ser definidas diretamente no construtor CustomView:
layoutSubviews ()
O melhor lugar para um layout manual é o método
layoutSubviews()
. Ele é chamado toda vez que o tamanho da
view
é alterado, para que você possa confiar no tamanho dos
bounds
para os cálculos corretos:
Controles privados, propriedades públicas
Se houver tempo, tornarei os controles de
property
privados, mas os gerencio por meio de variáveis ou funções públicas "no campo do conhecimento". Um exemplo mais simples:
A vantagem do encapsulamento: a lógica interna está oculta por trás da interface. Por exemplo, a validade de um objeto pode ser indicada pela cor da área, não pelo quadrado, mas o controlador não saberá nada sobre ele.
O que resta em viewDidLoad ()?
Se você usa o Interface Builder, geralmente o
viewDidLoad()
vazio. Se você criar uma
view
no código, precisará vincular suas ações através do padrão de ação de destino, adicionar um
UIGestureRecognizer
ou vincular delegados.
Personalizável através do Interface Builder
A subclasse para
view
pode ser configurada através do Interface Builder (a seguir IB).
Você precisa selecionar o objeto de
view
(não o controlador) e definir sua classe. Não é necessário escrever seu próprio
loadView()
, o controlador fará isso sozinho. Mas
UIView
ainda precisa
UIView
tipo
UIView
.

IBOutlet em UIView
Se você selecionar o controle dentro da
view
, o Editor do Assistente reconhecerá a classe
UIView
e a oferecerá como o segundo arquivo no modo Automático. Então você pode transferir o
IBOutlet
para
view
.

Se não estiver funcionandoAbra a classe
CustomView
manualmente, escreva
IBOutlet
. Agora você pode arrastar pelo marcador e passar o mouse sobre um elemento no IB.

Se você criar uma interface no código, todos os objetos estarão acessíveis após o
init()
, mas ao trabalhar com o IB, o acesso ao
IBOutlet
aparecerá apenas após o carregamento da interface do
UIStoryboard
no método
awakeFromNib()
:
IBAction no UIViewController
Para meu gosto, o controlador deve deixar todas as ações do usuário. Do padrão:
- ação-alvo dos controles
- delegar implementação no
UIViewController
- implementação de bloco
- reação à
Notification
Nesse caso, o
UIViewController
controla apenas a interface. Tudo relacionado à lógica de negócios deve ser retirado do controlador, mas esta é uma opção: MVP, VIPER etc.
Objetivo-c
No Objective-C, você pode substituir completamente o tipo de
UIView
. Para fazer isso, declare a propriedade com a classe desejada, substitua
setter
e
getter
, especificando a classe:
O fim
No exemplo do GitHub, você pode observar a separação de classes para uma tarefa simples: a cor do quadrado depende da sua posição (na área verde é verde, fora de vermelho).
Quanto mais complexa a tela, melhor o efeito: o controlador é reduzido, o código é transferido para seu lugar. O código é simplesmente portado para
view
, mas o encapsulamento facilita a interação e a leitura do código. Às vezes, a
view
pode ser reutilizada com outro controlador. Por exemplo, diferentes controladores para iPhone e iPad reagem de maneira própria à aparência do teclado, mas isso não altera o código de
view
.
Usei esse código em diferentes projetos e com pessoas diferentes, toda vez que a equipe aceitava a simplificação e adotava a prática. Espero que você goste também. Tudo fácil
UIViewController
!