Olá Habr! Apresento a você a tradução do artigo
Criando elementos da interface do usuário programaticamente usando o PureLayout por Aly Yaka.

Bem-vindo à segunda parte do artigo sobre como criar programaticamente uma interface usando o PureLayout.
Na primeira parte, criamos a interface do usuário de um aplicativo móvel simples completamente em código, sem usar Storyboards ou NIBs. Neste guia, abordaremos alguns dos elementos da interface do usuário mais usados em todos os aplicativos:
- UINavigationController / Bar
- UITableView
- UITableViewCell de auto-dimensionamento
UINavigationController
Em nosso aplicativo, você provavelmente precisará de uma barra de navegação para que o usuário possa ir da lista de contatos para informações detalhadas sobre um contato específico e retornar à lista.
UINavigationController
pode resolver esse problema facilmente usando a barra de navegação.
UINavigationController
é apenas uma pilha na qual você move muitas visualizações. O usuário vê a visualização superior (a que foi movida pela última vez) agora (exceto quando você tem outra visualização apresentada, digamos favoritas). E quando você pressiona os controladores da vista superior do controlador de navegação, o controlador de navegação cria automaticamente um botão "voltar" (lado superior esquerdo ou direito, dependendo das preferências de idioma atuais do dispositivo) e pressionar esse botão retorna à visualização anterior.
Tudo isso é tratado imediatamente pelo controlador de navegação. E adicionar mais um terá apenas uma linha de código adicional (se você não quiser personalizar a barra de navegação).
Vá para AppDelegate.swift e adicione a seguinte linha de código abaixo, deixe
viewController = ViewController ():
let navigationController = UINavigationController(rootViewController: viewController)
Agora mude
self.window? .RootViewController = viewController self.window? .RootViewController = navigationController
self.window? .RootViewController = viewController self.window? .RootViewController = navigationController
self.window? .RootViewController = viewController self.window? .RootViewController = navigationController
. Na primeira linha, criamos uma instância do
UINavigationController
e passamos a ele nosso
viewController
como
rootViewController
, que é o controlador de exibição na parte inferior da pilha, o que significa que nunca haverá um botão voltar na barra de navegação dessa exibição. Em seguida, damos à nossa janela um controlador de navegação como
rootViewController
, porque agora ele conterá todas as visualizações no aplicativo.
Agora execute seu aplicativo. O resultado deve ficar assim:
Infelizmente, algo deu errado. Parece que a barra de navegação se sobrepõe ao upperView e temos várias maneiras de corrigir isso:
- Aumente o tamanho do
upperView
para ajustar a altura da barra de navegação. - Defina a propriedade
isTranslucent
da barra de navegação como false
. Isso tornará a barra de navegação opaca (se você não percebeu que é um pouco transparente), e agora a borda superior da superview se tornará a parte inferior da barra de navegação.
Eu pessoalmente escolherei a segunda opção, mas você estudará a primeira. Também recomendo verificar e ler atentamente os documentos da Apple no
UINavigationController
e
UINavigationBar
:
Agora vá para o método viewDidLoad e adicione esta linha
self.navigationController? .NavigationBar.isTranslucent = false super.viewDidLoad ()
self.navigationController? .NavigationBar.isTranslucent = false super.viewDidLoad ()
, para que
self.navigationController? .NavigationBar.isTranslucent = false super.viewDidLoad ()
assim:
override func viewDidLoad() { super.viewDidLoad() self.navigationController?.navigationBar.isTranslucent = false self.view.backgroundColor = .white self.addSubviews() self.setupConstraints() self.view.bringSubview(toFront: avatar) self.view.setNeedsUpdateConstraints() }
Você também pode adicionar esta linha
self.title = "John Doe" viewDidLoad
, que adicionará um "Perfil" à barra de navegação para que o usuário saiba onde ele está localizado atualmente. Execute o aplicativo e o resultado deve ficar assim:
Refatorando nosso View Controller
Antes de continuar, precisamos reduzir nosso arquivo
ViewController.swift
que possamos usar apenas lógica real, não apenas codificar para elementos da interface do usuário. Podemos fazer isso criando uma subclasse do
UIView
e movendo todos os nossos elementos da interface do usuário para lá. A razão pela qual fazemos isso é seguir o padrão de arquitetura Model-View-Controller ou MVC em breve. Saiba mais sobre o MVC
Model-View-Controller (MVC) no iOS: uma abordagem moderna .
Agora clique com o botão direito do mouse na pasta
ContactCard
no Project Navigator e selecione "Novo arquivo":

Clique em Cocoa Touch Class e depois em Next. Agora escreva “ProfileView” como o nome da classe e, ao lado de “Subclasse de:”, certifique-se de inserir “UIView”. Ele apenas diz ao Xcode para herdar automaticamente nossa classe do
UIView
e adicionará algum código padrão. Agora clique em Avançar, em seguida, crie e exclua o código comentado:
E agora estamos prontos para refatoração.
Corte e cole todas as variáveis preguiçosas do controlador de exibição em nossa nova exibição.
Abaixo da última variável pendente, substitua
init(frame :)
digitando
init
e selecionando o primeiro resultado de preenchimento automático do Xcode.

Aparecerá um erro informando que o inicializador “necessário” “init (codificador :)” deve ser fornecido por uma subclasse de “UIView”:

Você pode corrigir isso clicando no círculo vermelho e depois em Corrigir.

Em qualquer inicializador substituído, você quase sempre deve chamar o inicializador de superclasse; portanto, adicione esta linha de código na parte superior do método:
super.init (frame: frame)
.
Recorte e cole o método
addSubviews()
nos inicializadores e exclua
self.view
antes de cada chamada
addSubview
.
func addSubviews() { addSubview(avatar) addSubview(upperView) addSubview(segmentedControl) addSubview(editButton) }
Em seguida, chame este método do inicializador:
override init(frame: CGRect) { super.init(frame: frame) addSubviews() bringSubview(toFront: avatar) }
Para restrições, substitua
updateConstraints()
e adicione uma chamada no final desta função (onde ela sempre permanecerá):
override func updateConstraints() {
Ao substituir qualquer método, é sempre útil verificar sua documentação visitando documentos da Apple ou, mais simplesmente, mantendo pressionada a tecla Option (ou Alt) e clicando no nome da função:

Recorte e cole o código de restrição do controlador de exibição em nosso novo método:
override func updateConstraints() { avatar.autoAlignAxis(toSuperviewAxis: .vertical) avatar.autoPinEdge(toSuperviewEdge: .top, withInset: 64.0) upperView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .bottom) segmentedControl.autoPinEdge(toSuperviewEdge: .left, withInset: 8.0) segmentedControl.autoPinEdge(toSuperviewEdge: .right, withInset: 8.0) segmentedControl.autoPinEdge(.top, to: .bottom, of: avatar, withOffset: 16.0) editButton.autoPinEdge(.top, to: .bottom, of: upperView, withOffset: 16.0) editButton.autoPinEdge(toSuperviewEdge: .right, withInset: 8.0) super.updateConstraints() }
Agora volte ao controlador de exibição e inicialize a instância
viewDidLoad
método
viewDidLoad
,
let profileView = ProfileView(frame: .zero)
, adicione-o como uma
ViewController
ao
ViewController
.
Agora, nosso controlador de exibição foi reduzido para algumas linhas de código!
import UIKit import PureLayout class ViewController: UIViewController { let profileView = ProfileView(frame: .zero) override func viewDidLoad() { super.viewDidLoad() self.navigationController?.navigationBar.isTranslucent = false self.title = "Profile" self.view.backgroundColor = .white self.view.addSubview(self.profileView) self.profileView.autoPinEdgesToSuperviewEdges() self.view.layoutIfNeeded() } }
Para garantir que tudo funcione conforme o esperado, inicie o aplicativo e verifique como ele está.
Ter um controlador de revisão fino e limpo sempre deve ser seu objetivo. Isso pode levar muito tempo, mas evitará problemas desnecessários durante a manutenção.
UITableView
Em seguida, adicionaremos um UITableView para apresentar informações de contato, como número de telefone, endereço etc.
Se você ainda não o fez, acesse a documentação da Apple para ver UITableView, UITableViewDataSource e UITableViewDelegate.
Vá para
ViewController.swift
e adicione
lazy var
para
tableView
acima
viewDidLoad()
:
lazy var tableView: UITableView = { let tableView = UITableView() tableView.translatesAutoresizingMaskIntoConstraints = false tableView.delegate = self tableView.dataSource = self return tableView }()
Se você tentar executar o aplicativo, o Xcode reclamará que essa classe não é um delegado nem uma fonte de dados para o
UITableViewController
e, portanto, adicionaremos esses dois protocolos à classe:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { . . .
Mais uma vez, o Xcode reclamará de uma classe que não esteja em conformidade com o protocolo
UITableViewDataSource
, o que significa que existem métodos obrigatórios nesse protocolo que não estão definidos na classe. Para descobrir qual desses métodos você deve implementar enquanto mantém pressionado Cmd + Control, clique no protocolo
UITableViewDataSource
na definição de classe e você passará para a definição de protocolo. Para qualquer método que não seja precedido pela palavra
optional
, uma classe correspondente a este protocolo deve ser implementada.
Aqui temos dois métodos que precisamos implementar:
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
- esse método informa à visualização de tabela quantas linhas queremos mostrar.public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
- esse método consulta a célula em cada linha. Aqui inicializamos (ou reutilizamos) a célula e inserimos as informações que queremos mostrar ao usuário. Por exemplo, a primeira célula exibirá o número de telefone, a segunda célula exibirá o endereço e assim por diante.
Agora volte para
ViewController.swift
, comece a digitar
numberOfRowsInSection
e, quando o preenchimento automático aparecer, selecione a primeira opção.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { <#code#> }
Exclua o código da palavra e retorne agora 1.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 1 }
Sob essa função, comece a digitar
cellForRowAt
e selecione o primeiro método no preenchimento automático novamente.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { <#code#> }
E, novamente, por enquanto, retorne um
UITableViewCell
.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { return UITableViewCell() }
Agora, para conectar nossa visualização de tabela ao
ProfileView
, definiremos um novo inicializador que usará a visualização de tabela como parâmetro, para que possa adicioná-la como uma subvisualização e definir as restrições apropriadas.
Vá para
ProfileView.swift
e adicione o atributo para a visualização da tabela logo acima do inicializador:
var tableView: UITableView!
determinado, portanto, não temos certeza de que será constante.
Agora substitua a antiga implementação
init (frame :)
por:
init(tableView: UITableView) { super.init(frame: .zero) self.tableView = tableView addSubviews() bringSubview(toFront: avatar) }
O Xcode agora reclamará do
init (frame :)
ProfileView
para o
ProfileView
, então volte para
ViewController.swift
e substitua
let profileView = ProfileView (frame: .zero)
por
lazy var profileView: UIView = { return ProfileView(tableView: self.tableView) }()
Agora, o nosso
ProfileView
tem um link para uma visualização de tabela, e podemos adicioná-lo como uma subvisão e definir as restrições corretas para ele.
De volta ao
ProfileView.swift
, adicione
addSubview(tableView)
no final de
addSubviews()
e defina essas restrições para
updateConstraints()
em
super.updateConstraints
:
tableView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .top) tableView.autoPinEdge(.top, to: .bottom, of: segmentedControl, withOffset: 8)
A primeira linha adiciona três restrições entre a visualização da tabela e sua superview: os lados direito, esquerdo e inferior da visualização da tabela são anexados aos lados direito, esquerdo e inferior da visualização do perfil.
A segunda linha anexa a parte superior da exibição da tabela à parte inferior do controle segmentado com um intervalo de oito pontos entre eles. Inicie o aplicativo e o resultado deve ficar assim:
Ótimo, agora tudo está no lugar e podemos começar a apresentar nossas células.
UITableViewCell
Para implementar o
UITableViewCell
, quase sempre precisamos subclassificar essa classe; portanto, clique com o botão direito do mouse na pasta
ContactCard
no Project Navigator, depois em "New file ...", depois em "Cocoa Touch Class" e "Next".
Digite "UITableViewCell" no campo "Subclasse de:" e o Xcode preencherá automaticamente o nome da classe "TableViewCell". Digite “ProfileView” antes do preenchimento automático, para que o nome final seja “ProfileInfoTableViewCell”, depois clique em “Next” e “Create”. Vá em frente e exclua os métodos criados, pois não precisaremos deles. Se desejar, você pode primeiro ler as descrições delas para entender por que não precisamos delas agora.
Como dissemos anteriormente, nossa célula conterá informações básicas, que são o nome do campo e sua descrição, e, portanto, precisamos de rótulos para eles.
lazy var titleLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.text = "Title" return label }() lazy var descriptionLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.text = "Description" label.textColor = .gray return label }()
E agora vamos redefinir o inicializador para que a célula possa ser configurada:
override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) contentView.addSubview(titleLabel) contentView.addSubview(descriptionLabel) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
Com relação às limitações, vamos fazer um pouco diferente, mas, no entanto, muito útil:
override func updateConstraints() { let titleInsets = UIEdgeInsetsMake(16, 16, 0, 8) titleLabel.autoPinEdgesToSuperviewEdges(with: titleInsets, excludingEdge: .bottom) let descInsets = UIEdgeInsetsMake(0, 16, 4, 8) descriptionLabel.autoPinEdgesToSuperviewEdges(with: descInsets, excludingEdge: .top) descriptionLabel.autoPinEdge(.top, to: .bottom, of: titleLabel, withOffset: 16) super.updateConstraints() }
Aqui começamos a usar o
UIEdgeInsets
para definir o espaçamento em torno de cada rótulo. Um objeto
UIEdgeInsets
pode ser criado usando o método
UIEdgeInsetsMake(top:, left:, bottom:, right:)
. Por exemplo, para
titleLabel
dizemos que queremos que o limite superior seja de quatro pontos, e o direito e esquerdo de oito. Não nos preocupamos com o fundo, porque o excluímos, pois o anexamos ao topo da marca de descrição. Reserve um minuto para ler e visualizar todas as restrições em sua cabeça.
Ok, agora podemos começar a desenhar células em nossa visualização de tabela. Vamos seguir para
ViewController.swift
e alterar a inicialização lenta de nossa visualização de tabela para registrar essa classe de células na visualização de tabela e definir a altura de cada célula.
let profileInfoCellReuseIdentifier = "profileInfoCellReuseIdentifier" lazy var tableView: UITableView = { ... tableView.register(ProfileInfoTableViewCell.self, forCellReuseIdentifier: profileInfoCellReuseIdentifier) tableView.rowHeight = 68 return tableView }()
Também adicionamos uma constante para o identificador de reutilização de célula. Esse identificador é usado para remover células da exibição da tabela quando elas são exibidas. Essa é uma otimização que pode (e deve) ser usada para ajudar o
UITableView
reutilizar células que foram apresentadas anteriormente para exibir novo conteúdo em vez de redesenhar a nova célula do zero.
Agora, deixe-me mostrar como reutilizar células em uma linha de código no método
cellForRowAt
:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: profileInfoCellReuseIdentifier, for: indexPath) as! ProfileInfoTableViewCell return cell }
Aqui, informamos a visualização da tabela da retirada de uma célula reutilizável da fila usando o identificador sob o qual registramos o caminho para a célula que o usuário está prestes a aparecer. Em seguida, forçamos a célula a
ProfileInfoTableViewCell
para poder acessar suas propriedades para que possamos, por exemplo, definir o título e a descrição. Isso pode ser feito usando o seguinte:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { ... switch indexPath.row { case 0: cell.titleLabel.text = "Phone Number" cell.descriptionLabel.text = "+234567890" case 1: cell.titleLabel.text = "Email" cell.descriptionLabel.text = "john@doe.co" case 2: cell.titleLabel.text = "LinkedIn" cell.descriptionLabel.text = "www.linkedin.com/john-doe" default: break } return cell }
Agora defina
numberOfRowsInSection
para retornar “3” e inicie seu aplicativo.
Incrível né?
Células de auto-dimensionamento
Talvez, e muito provavelmente, ocorra um caso em que você deseja que células diferentes tenham alturas de acordo com as informações contidas nelas, as quais não são conhecidas antecipadamente. Para fazer isso, você precisa de uma exibição de tabela com dimensões calculadas automaticamente e, de fato, existe uma maneira muito simples de fazer isso.
Primeiro de tudo, em
ProfileInfoTableViewCell
adicione esta linha à
descriptionLabel
inicializador lento
label.numberOfLines = 0
Retorne ao
ViewController
e adicione essas duas linhas ao inicializador de exibição de tabela:
lazy var tableView: UITableView = { ... tableView.estimatedRowHeight = 64 tableView.rowHeight = UITableViewAutomaticDimension return tableView }()
Aqui, informamos a visualização da tabela que a altura da linha deve ter um valor calculado automaticamente com base em seu conteúdo.
Em relação à altura estimada da linha:
“Fornecer uma estimativa não negativa da altura das linhas pode melhorar o desempenho do carregamento da exibição da tabela.” - Documentos da Apple
No
ViewDidLoad
precisamos recarregar a exibição da tabela para que essas alterações entrem em vigor:
override func viewDidLoad() { super.viewDidLoad() ... DispatchQueue.main.async { self.tableView.reloadData() } }
Agora vá em frente e adicione outra célula, aumentando o número de linhas para quatro e adicionando outra
switch
ao
cellForRow
:
case 3: cell.titleLabel.text = "Address" cell.descriptionLabel.text = "45, Walt Disney St.\n37485, Mickey Mouse State"
Agora execute o aplicativo e ele deve ser algo como isto:
Conclusão
Incrível né? E como um lembrete do motivo pelo qual realmente codificamos nossa interface do usuário, aqui está uma postagem de blog escrita por nossa equipe móvel sobre o motivo de não estarmos
usando storyboards no Instabug .
O que você fez em duas partes desta lição:
- Removido o arquivo
main.storyboard
do seu projeto. - Criamos o
UIWindow
programaticamente e rootViewController
a ele um rootViewController
. - Criaram vários elementos da interface do usuário no código, como rótulos, visualizações de imagem, controles segmentados e visualizações de tabela com suas células.
UINavigationBar
aninhado no seu aplicativo.- Criou um tamanho dinâmico
UITableViewCell
.