O design atômico e o design do sistema são populares no design: é quando tudo consiste em componentes, de controles a telas. Não é difícil para um programador escrever controles separados, mas o que fazer com telas inteiras?
Vamos dar uma olhada no exemplo do ano novo:
- vamos juntar tudo;
- dividido em controladores: selecione navegação, modelo e conteúdo;
- reutilize o código para outras telas.

Tudo em um monte
A tela deste ano novo fala sobre o horário de funcionamento especial das pizzarias. Como é bastante simples, não será crime torná-lo um controlador:

Mas Da próxima vez, quando precisarmos de uma tela semelhante, teremos que repeti-la novamente e fazer as mesmas alterações em todas as telas. Bem, isso não acontece sem edições.
Portanto, é mais razoável dividi-lo em partes e usá-lo para outras telas. Eu destaquei três:
- navegação
- um modelo com uma área para conteúdo e um local para ações na parte inferior da tela,
- conteúdo exclusivo no centro.
Selecione cada parte em seu próprio UIViewController
.
Navegação em contêiner
Os exemplos mais impressionantes de contêineres de navegação são o UINavigationController
e o UITabBarController
. Cada um ocupa uma faixa na tela sob seus próprios controles e deixa o espaço restante para outro UIViewController
.
No nosso caso, haverá um contêiner para todas as telas modais com apenas um botão Fechar.
Qual é o objetivo?Se quisermos mover o botão para a direita, precisaremos apenas alterá-lo em um controlador.
Ou, se decidirmos mostrar todas as janelas modais com uma animação especial e fechar de forma interativa com um furto, como nos cartões de histórias da AppStore. Então UIViewControllerTransitioningDelegate
precisará ser definido apenas para este controlador.

Você pode usar uma container view
para separar controladores: ele criará um UIView
no pai e inserirá o UIView
controlador filho nele.

Estique a container view
até a borda da tela. Safe area
será aplicada automaticamente ao controlador filho:

Padrão de tela
O conteúdo é óbvio na tela: imagem, título, texto. O botão parece fazer parte dele, mas o conteúdo é dinâmico em diferentes iPhones e o botão está fixo. Dois sistemas com tarefas diferentes são visíveis: um exibe o conteúdo e o outro o incorpora e alinha. Eles devem ser divididos em dois controladores.

O primeiro é responsável pelo layout da tela: o conteúdo deve ser centralizado e o botão pregado na parte inferior da tela. O segundo irá desenhar o conteúdo.

Sem um modelo, todos os controladores são semelhantes, mas os elementos dançam.
Os botões na última tela são diferentes - depende do conteúdo. A delegação ajudará a resolver o problema: o modelo do controlador solicitará controles do conteúdo e os exibirá em seu UIStackView
.
Os botões podem ser conectados ao controlador através de objetos relacionados. Seus IBOutlet
e IBAction
são armazenados no controlador de conteúdo, apenas os elementos não são adicionados à hierarquia.

Você pode obter elementos do conteúdo e adicioná-los ao modelo na fase de preparação do UIStoryboardSegue
:
No setter, adicionamos controles ao UIStackView
:
Como resultado, nosso controlador foi dividido em três partes: navegação, modelo e conteúdo. Na figura, todas as container view
mostradas em cinza:

Tamanho dinâmico do controlador
O controlador de conteúdo tem seu próprio tamanho máximo, é limitado por constraints
internas.
Container view
adiciona constores com base na Autoresizing mask
e eles entram em conflito com as dimensões internas do conteúdo. O problema foi resolvido no código: no controlador de conteúdo, você precisa indicar que ele não é afetado pelos constores da Autoresizing mask
:

Há mais duas etapas para o Interface Builder:
Etapa 1. Especifique o Intrinsic size
para o UIView
. Os valores reais aparecerão após o lançamento, mas, por enquanto, colocaremos os adequados.

Etapa 2. Para o controlador de conteúdo, especifique Simulated Size
. Pode não corresponder ao tamanho anterior.
Houve erros de layout, o que devo fazer?Os erros ocorrem quando o AutoLayout
não consegue descobrir como decompor os elementos no tamanho atual.
Na maioria das vezes, o problema desaparece após a alteração das prioridades da constante. Você precisa colocá-los para que um dos UIView
possa expandir / contrair mais do que os outros.
Dividimos em partes e escrevemos em código
Dividimos o controlador em várias partes, mas até agora não podemos reutilizá-las, a interface do UIStoryboard
difícil de extrair em partes. Se precisarmos transferir alguns dados para o conteúdo, teremos que bater neles por toda a hierarquia. Deve ser o contrário: primeiro pegue o conteúdo, configure-o e depois embrulhe-o nos recipientes necessários. Como uma lâmpada.
Três tarefas aparecem no nosso caminho:
- Separe cada controlador em seu próprio
UIStoryboard
. - Recusar
container view
, adicione controladores a contêineres no código. - Amarre tudo de volta.
Compartilhando o UIStoryboard
Você precisa criar dois UIStoryboard
adicionais e copiar e colar o controlador de navegação e o controlador de modelo neles. Embed segue
será interrompida, mas a container view
do container view
com restrições configuradas será transferida. As restrições devem ser salvas e a container view
do container view
deve ser substituída por uma UIView
regular.
A maneira mais fácil é alterar o tipo de exibição Container no código UIStoryboard.- abrir o
UIStoryboard
como um código (menu de contexto do arquivo → Abrir como ... → Código fonte); altere o tipo de containerView
para view
. É necessário alterar as tags de abertura e fechamento .
Da mesma maneira, você pode alterar, por exemplo, UIView
para UIScrollView
, se necessário. E vice-versa.

Definimos o controlador como a propriedade is initial view controller
e chamaremos o UIStoryboard
como o controlador.
Carregamos o controlador do UIStoryboard.Se o nome do controlador corresponder ao nome do UIStoryboard
, o download poderá ser UIStoryboard
em um método que, por si só, encontrará o arquivo desejado:
protocol Storyboardable { } extension Storyboardable where Self: UIViewController { static func instantiateInitialFromStoryboard() -> Self { let controller = storyboard().instantiateInitialViewController() return controller! as! Self } static func storyboard(fileName: String? = nil) -> UIStoryboard { let storyboard = UIStoryboard(name: fileName ?? storyboardIdentifier, bundle: nil) return storyboard } static var storyboardIdentifier: String { return String(describing: self) } static var storyboardName: String { return storyboardIdentifier } }
Se o controlador for descrito em .xib
, o construtor padrão será carregado sem essas danças. Infelizmente, o .xib
pode conter apenas um controlador, geralmente isso não é suficiente: em um bom caso, uma tela consiste em vários. Portanto, usamos o UIStoryborad
, é fácil dividir a tela em partes.
Adicionar um controlador no código
Para que o controlador funcione corretamente, precisamos de todos os métodos de seu ciclo de vida: will/did-appear/disappear
.
Para a exibição correta, você precisa chamar 5 etapas:
willMove(toParent parent: UIViewController?) addChild(_ childController: UIViewController) addSubview(_ subivew: UIView) layout didMove(toParent parent: UIViewController?)
A Apple sugere reduzir o código para 4 etapas, porque o próprio addChild()
chamará willMove(toParent)
. Em resumo:
addChild(_ childController: UIViewController) addSubview(_ subivew: UIView) layout didMove(toParent parent: UIViewController?)
Para simplificar, você pode agrupar tudo em extension
. Para o nosso caso, precisamos de uma versão com insertSubview()
.
extension UIViewController { func insertFullframeChildController(_ childController: UIViewController, toView: UIView? = nil, index: Int) { let containerView: UIView = toView ?? view addChild(childController) containerView.insertSubview(childController.view, at: index) containerView.pinToBounds(childController.view) childController.didMove(toParent: self) } }
Para excluir, você precisa das mesmas etapas; somente em vez do controlador pai, você precisa definir nil
. Agora removeFromParent()
chama didMove(toParent: nil)
e o layout não é necessário. A versão abreviada é muito diferente:
willMove(toParent: nil) view.removeFromSuperview() removeFromParent()
Layout
Definir restrições
Para definir corretamente o tamanho do controlador, usaremos o AutoLayout
. Precisamos pregar todos os lados para todos os lados:
extension UIView { func pinToBounds(_ view: UIView) { view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ view.topAnchor.constraint(equalTo: topAnchor), view.bottomAnchor.constraint(equalTo: bottomAnchor), view.leadingAnchor.constraint(equalTo: leadingAnchor), view.trailingAnchor.constraint(equalTo: trailingAnchor) ]) } }
Adicionar um controlador filho no código
Agora tudo pode ser combinado:
Devido à frequência de uso, podemos agrupar tudo isso em extension
:
Um método semelhante também é necessário para o controlador de modelo. prepare(for segue:)
costumavam ser configuradas no prepare(for segue:)
, mas agora você pode vinculá-lo ao método de incorporação do controlador:
A criação de um controlador é assim:
Conectar uma nova tela ao modelo é simples:
- remova o que não é relevante para o conteúdo;
- especificar botões de ação implementando o protocolo OnboardingViewControllerDatasource;
- escreva um método que vincule um modelo e conteúdo.
Mais sobre contêineres
Barra de status
Geralmente, é necessário que a visibilidade da status bar
seja controlada por um controlador com conteúdo, não por um contêiner. Há algumas property
para isso:
Usando essas property
você pode criar uma cadeia de controladores, que serão responsáveis por exibir a status bar
.
Área segura
Se os botões do contêiner se sobrepuserem ao conteúdo, você deverá aumentar a zona safeArea
. Isso pode ser feito no código: defina additinalSafeAreaInsets
para controladores filhos. Você pode chamá-lo em embedController()
:
private func addSafeArea(to controller: UIViewController) { if #available(iOS 11.0, *) { let buttonHeight = CGFloat(30) let topInset = UIEdgeInsets(top: buttonHeight, left: 0, bottom: 0, right: 0) controller.additionalSafeAreaInsets = topInset } }
Se você adicionar 30 pontos na parte superior, o botão interromperá a sobreposição de conteúdo e o safeArea
ocupará a área verde:

Margens. Preservar margens de visão geral
Controladores têm margins
padrão. Geralmente, eles são iguais a 16 pontos de cada lado da tela e apenas nos tamanhos Plus são 20 pontos.
Com base nas margins
você pode criar constantes, o recuo até a borda será diferente para diferentes iPhones:

Quando colocamos uma UIView
em outra, as margins
são reduzidas pela metade: para 8 pontos. Para evitar isso, você precisa incluir Preserve superview margins
. Em seguida, as margins
UIView
filho serão iguais às margins
pai. É adequado para recipientes de tela cheia.
O fim
Os controladores de contêiner são uma ferramenta poderosa. Eles simplificam o código, separam tarefas e podem ser reutilizados. Você pode escrever controladores aninhados de qualquer maneira: no UIStoryboard
, no .xib
ou simplesmente no código. Mais importante, eles são fáceis de criar e divertidos de usar.
→ Um exemplo de um artigo no GitHub
Você tem telas nas quais valeria a pena fazer um modelo? Compartilhe nos comentários!