
A maioria dos aplicativos móveis contém mais de uma dúzia de telas, transições complexas e partes do aplicativo, separadas por significado e finalidade. Portanto, é necessário organizar a estrutura de navegação correta para o aplicativo, que será flexível, conveniente, expansível, proporcionará acesso confortável a várias partes do aplicativo e também será cuidadoso com os recursos do sistema.
Neste artigo, projetaremos a navegação no aplicativo de forma a evitar os erros mais comuns que causam vazamentos de memória, arruinam a arquitetura e quebram a estrutura de navegação.
A maioria dos aplicativos possui pelo menos duas partes: autenticação (pré-login) e parte privada (pós-login). Alguns aplicativos podem ter uma estrutura mais complexa, vários perfis com um único login, transições condicionais após o início do aplicativo (deeplinks) etc.
Na prática, duas abordagens são usadas principalmente para navegar no aplicativo:
- Uma pilha de navegação para os controladores de apresentação (presente) e controladores de navegação (push), sem a capacidade de voltar. Essa abordagem faz com que todos os ViewControllers anteriores permaneçam na memória.
- Usa alternar window.rootViewController. Com essa abordagem, todos os ViewControllers anteriores são destruídos na memória, mas isso não parece o melhor do ponto de vista da interface do usuário. Além disso, ele não permite que você se mova para frente e para trás, se necessário.
Agora vamos ver como você pode criar uma estrutura de fácil manutenção que permite alternar facilmente entre diferentes partes do aplicativo, sem código de espaguete e fácil navegação.
Vamos imaginar que estamos escrevendo um aplicativo que consiste em:
- A tela principal ( tela inicial ): esta é a primeira tela exibida, assim que o aplicativo é iniciado, você pode adicionar, por exemplo, animação ou fazer solicitações de API primárias.
- Parte de autenticação : login, registro, redefinição de senha, confirmação por email, etc. A sessão de trabalho do usuário geralmente é salva, portanto, não é necessário inserir um logon sempre que o aplicativo for iniciado.
- Parte principal : a lógica de negócios do aplicativo principal
Todas essas partes do aplicativo são isoladas uma da outra e cada uma existe em sua própria pilha de navegação. Portanto, podemos precisar das seguintes transições:
- Tela inicial -> Tela de autenticação , se a sessão atual do usuário ativo estiver ausente.
- Tela inicial -> Tela principal , se o usuário já tiver efetuado login no aplicativo anteriormente e houver uma sessão ativa.
- Tela principal -> Tela de autenticação , caso o usuário efetue logout
Configuração básicaQuando o aplicativo é iniciado, precisamos inicializar o
RootViewController , que será carregado primeiro. Isso pode ser feito com o código e através do Interface Builder. Crie um novo projeto no xCode e tudo isso já será feito por padrão:
main.storyboard já
está vinculado a
window.rootViewController .
Mas, para focar no tópico principal do artigo, não usaremos storyboards em nosso projeto. Portanto, exclua
main.storyboard e também limpe o campo "Interface principal" em Destinos -> Geral -> Informações de implantação:

Agora vamos alterar o método
didFinishLaunchingWithOptions no
AppDelegate para que fique assim:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) window?.rootViewController = RootViewController() window?.makeKeyAndVisible() return true }
Agora, o aplicativo iniciará primeiro o
RootViewController . Renomeie o
ViewController base para o
RootViewController :
class RootViewController: UIViewController { }
Este será o principal controlador responsável por todas as transições entre diferentes seções do aplicativo. Portanto, precisaremos de um link para ele toda vez que quisermos fazer a transição. Para fazer isso, adicione a extensão ao
AppDelegate :
extension AppDelegate { static var shared: AppDelegate { return UIApplication.shared.delegate as! AppDelegate } var rootViewController: RootViewController { return window!.rootViewController as! RootViewController } }
A recuperação forçada de uma opção nesse caso é justificada, porque o RootViewController não muda e, se isso acontecer repentinamente por acidente, a falha do aplicativo é uma situação normal.
Portanto, agora temos um link para o
RootViewController de qualquer lugar do aplicativo:
let rootViewController = AppDelegate.shared.rootViewController
Agora vamos criar mais alguns controladores que precisamos:
SplashViewController, LoginViewController e
MainViewController .
Tela inicial é a primeira tela que um usuário verá após iniciar o aplicativo. No momento, todas as solicitações de API necessárias geralmente são feitas, a atividade da sessão do usuário é verificada etc. Para exibir as ações em segundo plano em andamento, use o
UIActivityIndicatorView :
class SplashViewController: UIViewController { private let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge) override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = UIColor.white view.addSubview(activityIndicator) activityIndicator.frame = view.bounds activityIndicator.backgroundColor = UIColor(white: 0, alpha: 0.4) makeServiceCall() } private func makeServiceCall() { } }
Para simular solicitações de API, adicione o método
DispatchQueue.main.asyncAfter com um atraso de 3 segundos:
private func makeServiceCall() { activityIndicator.startAnimating() DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(3)) { self.activityIndicator.stopAnimating() } }
Acreditamos que a sessão do usuário também esteja definida nessas solicitações. Em nossa aplicação, usamos
UserDefaults para isso:
private func makeServiceCall() { activityIndicator.startAnimating() DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(3)) { self.activityIndicator.stopAnimating() if UserDefaults.standard.bool(forKey: “LOGGED_IN”) {
Você definitivamente não usará UserDefaults para salvar o estado de uma sessão de usuário na versão do programa. Usamos configurações locais em nosso projeto para simplificar a compreensão e não ir muito além do tópico principal do artigo.Crie um
LoginViewController . Será usado para autenticar o usuário se a sessão atual do usuário estiver inativa. Você pode adicionar sua interface de usuário personalizada ao controlador, mas adicionarei aqui apenas o título da tela e o botão de login na barra de navegação.
class LoginViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = UIColor.white title = "Login Screen" let loginButton = UIBarButtonItem(title: "Log In", style: .plain, target: self, action: #selector(login)) navigationItem.setLeftBarButton(loginButton, animated: true) } @objc private func login() {
E, finalmente, crie o controlador principal do aplicativo
MainViewController :
class MainViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = UIColor.lightGray // to visually distinguish the protected part title = “Main Screen” let logoutButton = UIBarButtonItem(title: “Log Out”, style: .plain, target: self, action: #selector(logout)) navigationItem.setLeftBarButton(logoutButton, animated: true) } @objc private func logout() { // clear the user session (example only, not for the production) UserDefaults.standard.set(false, forKey: “LOGGED_IN”) // navigate to the Main Screen } }
Navegação raizAgora, de volta ao
RootViewController .
Como dissemos anteriormente, o
RootViewController é o único objeto responsável pelas transições entre diferentes pilhas de controladores independentes. Para estar ciente do estado atual do aplicativo, criaremos uma variável na qual armazenaremos o
ViewController atual:
class RootViewController: UIViewController { private var current: UIViewController }
Adicione o inicializador de classe e crie o primeiro
ViewController que queremos carregar quando o aplicativo for iniciado. No nosso caso, será o
SplashViewController :
class RootViewController: UIViewController { private var current: UIViewController init() { self.current = SplashViewController() super.init(nibName: nil, bundle: nil) } }
Em
viewDidLoad, adicione o
viewController atual ao
RootViewController :
class RootViewController: UIViewController { ... override func viewDidLoad() { super.viewDidLoad() addChildViewController(current)
Depois de adicionar
childViewController (1), ajustamos seu tamanho configurando
current.view.frame para
view.bounds (2).
Se pularmos essa linha, o
viewController ainda será colocado corretamente na maioria dos casos, mas poderão ocorrer problemas se o tamanho do
quadro for alterado.
Adicione uma nova subview (3) e chame o método didMove (toParentViewController :). Isso concluirá a operação de adição do controlador. Assim que o
RootViewController inicializar , imediatamente depois disso, o
SplashViewController será exibido.
Agora você pode adicionar vários métodos de navegação no aplicativo.
Exibiremos o
LoginViewController sem nenhuma animação, o
MainViewController usará a animação com escurecimento suave, e a transição das telas ao desconectar o usuário terá um efeito de slide.
class RootViewController: UIViewController { ... func showLoginScreen() { let new = UINavigationController(rootViewController: LoginViewController()) // 1 addChildViewController(new) // 2 new.view.frame = view.bounds // 3 view.addSubview(new.view) // 4 new.didMove(toParentViewController: self) // 5 current.willMove(toParentViewController: nil) // 6 current.view.removeFromSuperview()] // 7 current.removeFromParentViewController() // 8 current = new // 9 }
Crie o
LoginViewController (1), adicione como um controlador filho (2), defina o quadro (3). Adicione a visualização do
LoginController como subview (4) e chame o método didMove (5). Em seguida, prepare o controlador atual para remoção usando o método willMove (6). Por fim, exclua a visualização atual da superview (7) e exclua o controlador atual do
RootViewController (8). Lembre-se de atualizar o valor do controlador atual (9).
Agora vamos criar o método
switchToMainScreen :
func switchToMainScreen() { let mainViewController = MainViewController() let mainScreen = UINavigationController(rootViewController: mainViewController) ... }
Animar a transição requer um método diferente:
private func animateFadeTransition(to new: UIViewController, completion: (() -> Void)? = nil) { current.willMove(toParentViewController: nil) addChildViewController(new) transition(from: current, to: new, duration: 0.3, options: [.transitionCrossDissolve, .curveEaseOut], animations: { }) { completed in self.current.removeFromParentViewController() new.didMove(toParentViewController: self) self.current = new completion?()
Esse método é muito semelhante ao
showLoginScreen , mas todas as últimas etapas são executadas após a conclusão da animação. Para notificar o método de chamada do final da transição, no final chamamos de encerramento (1).
Agora a versão final do método
switchToMainScreen será parecida com esta:
func switchToMainScreen() { let mainViewController = MainViewController() let mainScreen = UINavigationController(rootViewController: mainViewController) animateFadeTransition(to: mainScreen) }
E, finalmente, vamos criar o último método que será responsável pela transição do
MainViewController para o
LoginViewController :
func switchToLogout() { let loginViewController = LoginViewController() let logoutScreen = UINavigationController(rootViewController: loginViewController) animateDismissTransition(to: logoutScreen) }
O método
AnimateDismissTransition fornece animação de slide:
private func animateDismissTransition(to new: UIViewController, completion: (() -> Void)? = nil) { new.view.frame = CGRect(x: -view.bounds.width, y: 0, width: view.bounds.width, height: view.bounds.height) current.willMove(toParentViewController: nil) addChildViewController(new) transition(from: current, to: new, duration: 0.3, options: [], animations: { new.view.frame = self.view.bounds }) { completed in self.current.removeFromParentViewController() new.didMove(toParentViewController: self) self.current = new completion?() } }
Estes são apenas dois exemplos de animação. Usando a mesma abordagem, você pode criar animações complexas necessárias.
Para concluir a configuração, adicione chamadas de método com animações do
SplashViewController, LoginViewController e
MainViewController :
class SplashViewController: UIViewController { ... private func makeServiceCall() { if UserDefaults.standard.bool(forKey: “LOGGED_IN”) { // navigate to protected page AppDelegate.shared.rootViewController.switchToMainScreen() } else { // navigate to login screen AppDelegate.shared.rootViewController.switchToLogout() } } } class LoginViewController: UIViewController { ... @objc private func login() { ... AppDelegate.shared.rootViewController.switchToMainScreen() } } class MainViewController: UIViewController { ... @objc private func logout() { ... AppDelegate.shared.rootViewController.switchToLogout() } }
Compile, execute o aplicativo e verifique sua operação de duas maneiras:
- quando o usuário já tiver uma sessão atual ativa (logado)
- quando não houver sessão ativa e a autenticação for necessária
Nos dois casos, você verá uma transição para a tela desejada, imediatamente após carregar o
SplashScreen .

Como resultado, criamos um pequeno modelo de teste do aplicativo, com navegação por seus principais módulos. Se você precisar expandir os recursos do aplicativo, adicionar módulos e transições adicionais entre eles, sempre poderá expandir e escalar com rapidez e conveniência esse sistema de navegação.