Alterando o Controlador Raiz de uma UIWindow no iOS em Tempo de Execução

Em aplicações iOS, o UIWindow principal possui um rootViewController, que é o controlador de visualização base a partir do qual toda a hierarquia de visualizações é gerenciada. Existem cenários onde pode ser necessário alterar este controlador raiz dinamicamente em tempo de execução, como, por exemplo, após um usuário fazer login ou logout, para exibir um fluxo de onboarding, ou para alternar entre diferentes modos da aplicação.

A forma mais direta e recomendada para trocar o controlador raiz da sua aplicação envolve a manipulação da propriedade rootViewController da sua UIWindow principal, geralmente acessada através do AppDelegate.

Definição Inicial do Controlador Raiz

Normalmente, o controlador raiz é configurado no método application(_:didFinishLaunchingWithOptions:) do seu AppDelegate.


import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
        window = UIWindow(frame: UIScreen.main.bounds)
        let controladorInicial = PrimeiroControladorDeVisualizacao()
        window?.rootViewController = controladorInicial
        window?.makeKeyAndVisible()
        
        return true
    }
    // ... outros métodos do AppDelegate
}

class PrimeiroControladorDeVisualizacao: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemRed
        
        let botaoAlternar = UIButton(type: .system)
        botaoAlternar.setTitle("Ir para Segundo Controlador", for: .normal)
        botaoAlternar.titleLabel?.font = UIFont.systemFont(ofSize: 20, weight: .semibold)
        botaoAlternar.addTarget(self, action: #selector(alternarControladorRaiz), for: .touchUpInside)
        
        view.addSubview(botaoAlternar)
        botaoAlternar.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            botaoAlternar.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            botaoAlternar.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
    
    @objc func alternarControladorRaiz() {
        if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
            appDelegate.mudarControladorRaiz(para: SegundoControladorDeVisualizacao(), comAnimacao: true)
        }
    }
}

Implementando a Troca do Controlador Raiz

Para trocar o controlador raiz, você precisará de uma referência à UIWindow principal e, em seguida, definir uma nova instância de UIViewController para sua propriedade rootViewController. É uma boa prática encapsular essa lógica em um método no AppDelegate para facilitar o reuso e manter a responsabilidade clara.

Além disso, para proporcionar uma experiência de usuário mais agradável, é recomendável animar a transição entre os controladores. O método UIView.transition(with:duration:options:animations:completion:) é ideal para isso.


// No AppDelegate.swift

extension AppDelegate {
    func mudarControladorRaiz(para novoControlador: UIViewController, comAnimacao animar: Bool) {
        guard let window = self.window else { return }

        // Opcional: Preparar o novo controlador (ex: cor de fundo)
        novoControlador.view.backgroundColor = .systemBlue
        
        if animar {
            UIView.transition(with: window, duration: 0.5, options: .transitionCrossDissolve, animations: {
                // Remover o controlador antigo da hierarquia de visualizações (opcional, ARC geralmente lida com isso)
                let controladorAntigo = window.rootViewController
                controladorAntigo?.view.removeFromSuperview()
                
                window.rootViewController = novoControlador
            }) { completed in
                // A transição foi concluída
                print("Controlador raiz alterado para: \(type(of: novoControlador))")
            }
        } else {
            window.rootViewController = novoControlador
        }
    }
}

class SegundoControladorDeVisualizacao: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBlue
        
        let label = UILabel()
        label.text = "Bem-vindo ao Segundo Controlador!"
        label.textAlignment = .center
        label.font = UIFont.systemFont(ofSize: 22, weight: .bold)
        label.textColor = .white
        
        view.addSubview(label)
        label.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
}

Neste exemplo, o método mudarControladorRaiz é chamado a partir do PrimeiroControladorDeVisualizacao. Ele cria uma nova instância de SegundoControladorDeVisualizacao e o define como o novo rootViewController da janela, com uma animação de fade (.transitionCrossDissolve).

Considerações Importantes

  • Memória do Controlador Antigo: Quando você define um novo rootViewController, o controlador antigo é automaticamente liberado da memória pelo ARC (Automatic Reference Coutning) do Swift, desde que não existam outras referências fortes a ele. Não é necessário chamar métodos como removeFromParentViewController ou manipular a propriedade view do controlador antigo explicitamente para desallocá-lo neste contexto.
  • Ciclo de Vida: Certifique-se de que o novo controlador raiz está pronto para ser exibido. Métodos como viewDidLoad() e viewWillAppear() serão chamados no novo controlador conforme ele é inserido na hierarquia de visualizações.
  • Evitando Problemas com presentViewController: A abordagem de usar presentViewController para exibir um controlador e depois tentar torná-lo o controlador raiz pode levar a problemas, como o erro "Attempt to present ... whose view is not in the window hierarchy!". Isso ocorre porque presentViewController espera que o controlador apresentador já faça parte de uma hierarquia de visualizações ativa. A maneira correta de alterar o controlador raiz é através da propriedade window.rootViewController, como demonstrado acima.

Tags: ios swift UIViewController UIWindow AppDelegate

Publicado em 7-29 03:00