Implementando interfaces iOS em view code

Brenno de Moura
ioasys-voices
Published in
6 min readJul 27, 2021

--

Os aplicativos iOS com suporte ao UIKit precisam ser implementados usando o storyboard + xib para definir os elementos de interface de uma view controller. Essa regra de implementação, definida na documentação da Apple, está sujeita a vários problemas de desenvolvimento e integração dos objetos.

Além disso, trabalhar em aplicativos em storyboard exige o conhecimento de métodos para navegar entre as telas e passar os valores entre elas. Por isso, essa abordagem é precária e precisa ser refatorada para o modelo de implementação em view code.

O UIKit é um framework para iOS que permite a construção de interface de duas formas: usando os storyboards ou implementando em Swift e obj-c. Devido a isso, é possível adotar o modelo de view code nos aplicativos iOS e deixar de usar os storyboard e xib para criar interfaces.

No contexto iOS, temos dois componentes gráficos que fazem parte das regras do UIKit para manter a integridade dos softwares, a UIViewController e a UIView.

O processo de view code exige do programador conhecer essas duas classes que irão facilitar no momento de definir as regras da aplicação. Primeiro, será discutido o processo de codificação da UIView e, depois, da UIViewController.

UIView

A classe UIView é a base para todos os componentes gráficos do UIKit. Com ela, é possível implementar botões, textos e componentes complexos como listas e coleções. Além disso, o UIKit conta com o mecanismo auto layout para definir as regras de posição das views, como altura, margens e largura.

Essa variedade de componentes e ferramentas faz do UIKit um framework de interface completo e maduro que permite a implementação da interface via view code.

O primeiro passo para implementar as interfaces é definir o formato de programação das views. Neste artigo será utilizada uma sintaxe mais próxima da programação declarativa, inspirada no SwiftUI, e dos atributos de variáveis lazy var.

O UIKit é um framework que explora a programação imperativa, onde o programa segue um fluxo de instruções para chegar no resultado desejado. Por isso, é preciso fazer alguns ajustes para transformá-lo em um framework quase declarativo.

Um detalhe importante do UIKit é que todas as classes gráficas, como UIView e UIViewController, herdam a classe NSObject. Dessa forma, será feito uma abstração exclusiva nessa classe base.

Conforme a Figura 1, é definido o protocolo KeyPathSettable com dois métodos: (a) setting<Value>(_ keyPath: WritableKeyPath<Self, Value>, to value: Value) -> Self; e (b) setup(_ setup: (inout Self) -> Void) -> Self.

Por conta de uma limitação do Swift, esses métodos são definidos apenas na extensão do protocolo para permitir o uso nas classes que herdam a NSObject. O método (a) deve ser utilizado sempre para definir propriedades de layout no fluxo declarativo, como texto, cor de fundo, borda e outras. O método (b) deve ser utilizado para definir propriedades mais complexas e inacessíveis por meio do KeyPath, como constraints de altura e largura, ou funções para registrar as células da UITableView.

Figura 1 — Definição do protocolo KeyPathSettable.

Após a definição e implementação do protocolo, é preciso criar um segundo arquivo NSObject+KeyPathSettable.swift para integrá-los. Como os métodos foram implementados via extensão do protocolo, não é necessário fazer nenhuma implementação de código nesse arquivo, apenas a extensão do NSObject conformando com o protocolo KeyPathSettable. Após isso, é possível acessar as propriedades da UIView, NSLayoutConstraints por meio das funções setting(_:to:) e setup(_:).

O segundo protocolo é o ViewCodable que permitirá a configuração das subviews e das constraints de auto layout. Esse protocolo possui três métodos: (a) buildHierarchy; (b) setupConstraints; e (c) applyAdditionalChanges.

Na extensão dele, devemos adicionar o método setupView que chamará as três funções sequencialmente. Com isso, as views implementadas devem chamar o método setupView após a inicialização delas, que irá montar a hierarquia e configurar o auto layout. Conforme a Figura 2, temos a definição do ViewCodable com o método setupView.

Figura 2 — Definição do protocolo ViewCodable.

Integrando os dois protocolos, podemos criar um modelo de programação para todas as views do aplicativo especialmente quando consideramos o uso da UIStackView. A Figura 3 mostra como isso acontece.

Todos os componentes da view que mostram alguma informação na tela ou tem alguma interação com o usuário, normalmente textos e botões, são especificados separadamente usando o lazy var.

Caso a view tenha vários grupos de interface, um pedaço mostra o nome e sobrenome do usuário e outro mostra o endereço com um mapa, isso também pode ser quebrado em variáveis independentes com a lazy var.

Figura 3 — UseView explorando o ViewCodable e KeyPathSettable.

A variável final usada para montar a hierarquia é a contentView. Usamos ela e a UIStackView para montar a interface simplificando a implementação dos métodos do ViewCodable e com apenas constraints de margem zero.

Um outro recurso não explorado nesse artigo que potencializa ainda mais esse modelo de programação é a utilização de wrappers para realizar operações específicas de layout, como adicionar margens e alinhar as views no fluxo declarativo dos componentes. Com isso, a codificação da UIView em view code fica otimizada e facilmente componentizada e adaptável para o SwiftUI.

UIViewController

A implementação da view controller pode ser simplificada ao extremo e comportar apenas configurações que não incluem o layout. Basicamente, teremos a propriedade contentView e a sobrescrita do método loadView.

Em alguns casos, a view controller pode conter regras mais específicas da tela e possivelmente métodos de delegate, hierarquia mais complexa, entretanto, esse modelo básico de implementação é o suficiente para implementar qualquer tela no iOS. Além disso, podemos fazer algumas modificações para comportar o SwiftUI e manter o core do UIKit, facilitando em muito a migração para o novo framework da Apple.

A Figura 4 explora a implementação da UserViewController usando a variável contentView e fazendo a sobrescrita do loadView e viewDidLoad. É possível notar a simplicidade da controller, sem métodos complexos, além de um código limpo. O ideal é que seja sempre mantido essa implementação, caso contrário, complexidade de código devem ser transferidas para a view e subviews da controller, nesse caso a UserView.

Figura 4 — Implementação da UserViewController com contentView.

Além disso, ainda sobre a conceito de wrappers, a implementação da controller e da propriedade contentView pode ser potencializada, tornando o código ainda mais efetivo.

Para não abordar vários temas de uma vez, apesar desse tópico de uma certa forma estar contido no processo de view code, isso será discutido isoladamente em um outro artigo abordando códigos específicos que irão facilitar o processo de desenvolvimento.

A implementação da UserViewController com suporte ao SwiftUI seria feita de forma diferente. Ao invés de termos uma variável contentView, teríamos a body com o some View.

No loadView, utilizaríamos o UIHostingController(rootView:) para encapsular o body. Então, com algum método utilitário dos wrappers, podemos facilmente encapsular a UIHostingController em uma UIView para finalmente atribuir a view da controller.

Considerações

A avaliação dos exemplos neste artigo deve se concentrar apenas em modelos de código. Não há o objetivo de montar um layout funcional e de discutir as técnicas a respeito disso.

Utilizando esses modelos em conjunto com os dois protocolos discutidos, é possível criar as interfaces da aplicação usando o conceito de view code e de programação declarativa. Por último, os métodos init estão com comentário para que o programador explore conforme a arquitetura do projeto, deixando em aberto para cada caso.

Obrigado pela leitura!

--

--

Brenno de Moura
ioasys-voices

Software engineer with a passion for technology and a focus on declarative programming, experience in challenging projects and multidisciplinary teams