UIKit — Hierarquia de navegação básica com ViewCode
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)
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 :)