UIKit — Hierarquia de navegação básica com ViewCode

Crie uma hierarquia de telas e navegue facilmente entre elas

Bia Férre
5 min readSep 30, 2022

Quando implementamos um app, especialmente quando o design dele não inclui tab bars, queremos garantir de alguma forma que o usuário possa passar por entre as diferentes telas para utilizar suas funcionalidades. Com o Storyboard, é bem simples: aperta um comando, puxa uma seta e pronto, duas telas com ViewControllers distintas estão conectadas e são navegáveis entre si.

Mas e quando usamos ViewCode? Será que é tão difícil assim? A resposta é não, e aqui está o curtíssimo passo a passo para implementar uma hierarquia simples:

0. Vocabulário relevante 📖

  • UINavigationController: Controller pronta do UIKit que abstrai a hierarquia pra gente
  • @objc func (funções do tipo Objective-C): funções chamadas nos UIButton, por exemplo, quando são apertados
  • popViewController: remove tela do topo da pilha (que é a atualmente sendo mostrada) de navegação e o display volta à anterior
  • pushViewController: adiciona tela ao topo da pilha e o display vai pra ela

1. Estabelecendo a raíz da hierarquia 🌳

Precisamos definir quem é a mãe, ou o ponto de partida de uma hierarquia específica.

Digamos que meu app tenha um onboarding, e, assim que o usuário sair dele, eu queira que a tela inicial ou Home inicie uma hierarquia.

class OnboardingPageController: UIViewController {// criando botão pra ir pra home
let goToHomeButton() = {
let button: UIButton(type: .roundRect)
button.addTarget(self, action: #selector(goToHome), for: .touchUpInside)
}()
// implementando função chamada ao apertar no botão @objc private func goToHome() {
let rootViewController = HomeViewController()
let nav = UINavigationController(rootViewController: rootViewController)
nav.modalPresentationStyle = .fullScreen present(nav, animated: true) // linha que só define que haverá animação e a tela será completa
}
}

Dessa forma, assim que eu aperto pra ir pra Home, eu inicializo uma UINavigationController (com nome de variável nav) e passo como sua controller raiz, ou seja, rootViewController, uma instância da HomeViewController(), que eu criei e que controla minha Home.

Pronto, definimos que nossa HomeViewController é a raiz da navegação. Agora, vamos ver como adicionar outra tela a ser acessada depois da Home.

2. Adicionando telas à hierarquia 🖼

Quero ir da tela da Home para uma próxima tela, que vamos chamar de actionOne, que tem sua controller própria chamada actionOneViewController. Pra isso, vou usar a função pushViewController e colocá-la como ação a ser chamada quando eu apertar no meu botão nextButton.

class HomeViewController: UIViewController {
let nextButton() = {
let button: UIButton(type: .roundRect)
button.addTarget(self, action: #selector(nextButtonAction), for: .touchUpInside)
}()
@objc func nextButtonAction(_ sender: UIButton) {
let nextViewController = actionOneViewController()
navigationController?.navigationBar.tintColor = .white // essa linha apenas define o estilo da barra de navegação criada automaticamente //
navigationController?.pushViewController(nextViewController, animated: true)
}
}

Instanciando uma actionOneViewController e passando ela como parâmetro do método pushViewController, eu adiciono minha controller ao topo da pilha de navegação. Em outras palavras, minha nova controller se torna a tela que será mostrada no aparelho, e, se o usuário quiser voltar, essa controller será a primeira a sair da pilha, mostrando a tela que estava embaixo dela. Faz sentido? 🤔

3. Voltando à tela anterior 🔙

Vamos fazer justamente isso então: voltar à tela anterior. Se a tela que eu estou é sempre a do topo da minha pilha, se quero voltar à tela que veio antes dela, basta remover a tela atual da pilha e a anterior ficará à mostra. Isso é o método popViewController, oposto ao push.

Onde posso definir isso? Veja bem, quando criamos a hierarquia, o UIKit automaticamente provê uma navigationBar que mostrará a opção "Back" por default. Esse botão será gerado automaticamente pela UINavigationController, e suas características podem ser alteradas na função viewDidLoad da ViewController da qual queremos sair.

(Obs: você pode mandar outro botão, que não o default dado pela UINavigationController, fazer o pop também! Basta usar a mesma @objc func da etapa 2 e trocar push por pop, e a nextViewController pela controller atual da qual se deseja sair)

Nesse exemplo, há também a opção "Done", que não aparece no nosso caso, e o "Back" foi customizado com o título "Previous VC"
override func viewDidLoad() { 
super.viewDidLoad()
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: “”, style: .plain, target: nil, action: nil)
}

No exemplo de cima, quero que o botão não tenha título e seja apenas o ícone da setinha retornando. Ela será branca, pois definimos na etapa 1 que a cor da navigationBar = .white.

Chamamos o .self primeiro, e então podemos mexer no navigationItem, especificamente no que retorna, que é o backBarButtonItem. Tudo isso foi gerado automaticamente pra gente pela UINavigationController ;)

4. Tá, e se eu quiser voltar 2 telas atrás? ❓

Você pode voltar quantas telas quiser, até chegar na raiz! Não precisa ir só pra anterior. Pra isso, você não pode usar pop, já que o pop sempre remove a tela que está no topo da pilha.

Você vai ter que chamar o método .remove. Veja no exemplo abaixo, no qual quero tenho 3 telas, estou na 3ª e quero pular direto pra 1ª.

(Considere que esse código está na viewDidLoad da minha 3ª tela)

let previousViewControllerIndex = navigationController.viewControllers.count — 2 
navigationController.viewControllers.remove(at: previousViewControllerIndex)

Note que chamar o valor .viewControllers da navigationController retorna um array de todos os controllers que estão na minha pilha, com índices de [0] a [total de telas — 1]. A primeira, de índice 0, é sempre a raiz, e a última é sempre a que estou agora.

Dessa forma, se quero pegar o valor da tela imediatamente anterior à que estou, consigo isso subtraindo 2 do valor total de telas. É isso que faço na primeira linha, guardando esse valor numa variável chamada previousViewControllerIndex.

A partir daí, posso remover usando .remove e passando como parâmetro esse index que eu calculei. Assim, quando eu apertar "Back", ele não vai pra antiga tela anterior, pois ela foi removida da hierarquia! Ele vai pular a tela anterior, a 2ª nesse exemplo, e ir direto pra 1ª.

Pra mais formas de usar o .remove, vá para esse link do Stack Overflow.

E é isso. Espero que essa curta introdução tenha ajudado! Você pode adicionar quantas viewControllers quiser nessa hierarquia, a lógica do push e pop é a mesma. Estudar a estrutura de dados chamada Pilha ajuda a entender mais intuitivamente a ideia, mas é bem simples.

Até a próxima :)

--

--

Bia Férre

cientista computacional escrevendo sobre a área de TI, design, política e literatura. nem sempre nessa ordem.