Adaptando sua solução comercial existente para o SwiftUI. Parte 4. Navegação e configuração

Bom Dia a todos! Com você, Anna Zharkova, desenvolvedora móvel líder da Usetech,
agora vamos falar sobre outro ponto interessante no SwiftUI, a navegação.

Se você perdeu os artigos anteriores da série, pode lê-los aqui:

parte 1
parte 2
parte 3

Com a alteração na descrição da parte visual e a transição para a sintaxe declarativa, o controle de navegação no aplicativo SwiftUI também foi alterado. O uso do UIViewContoller é negado diretamente; o UINavigationController não é usado diretamente. É substituído pelo NavigationView.

@available(iOS 13.0, OSX 10.15, tvOS 13.0, *)
@available(watchOS, unavailable)
public struct NavigationView<Content> : View where Content : View {

    public init(@ViewBuilder content: () -> Content)

    //....
}

Essencialmente, um invólucro sobre o UINavigationController e sua funcionalidade.


O principal mecanismo de transição é o NavigationLink (um análogo de segue), que é definido imediatamente no código de exibição do corpo.

public struct NavigationLink<Label, Destination> : View where Label : View, 
                                                                                           Destination : View {
.
    public init(destination: Destination, @ViewBuilder label: () -> Label)

    public init(destination: Destination, isActive: Binding<Bool>, 
                   @ViewBuilder label: () -> Label)

    public init<V>(destination: Destination, tag: V, selection: Binding<V?>,
                          @ViewBuilder label: () -> Label) where V : Hashable
//....
}

Ao criar um NavigationLink, ele indica a Visualização na qual a transição é feita, bem como a Visualização que o NavigationLink envolve, ou seja, ao interagir com ele, o NavigationLink é ativado. Mais informações sobre as possíveis maneiras de inicializar o NavigationLink na documentação da Apple.No

entanto, deve-se ter em mente que não há acesso direto à pilha View devido ao encapsulamento, a navegação é programada apenas para a frente, é possível retornar apenas 1 nível de volta e, em seguida, através do código encapsulado para o botão "Voltar"



Também no SwiftUI não há navegação dinâmica por software. Se a transição não estiver vinculada a um evento acionador, por exemplo, pressionar um botão, mas seguir como resultado de algum tipo de lógica, simplesmente não faça isso. A transição para a próxima Visualização é necessariamente vinculada ao mecanismo NavigationLink, que é definido declarativamente imediatamente ao descrever a Visualização que os contém. Todos.

Se nossa tela contiver uma transição para muitas telas diferentes, o código se tornará complicado:

  NavigationView{
            NavigationLink(destination: ProfileView(), isActive: self.$isProfile) {
                Text("Profile")
            }
            NavigationLink(destination: Settings(), isActive: self.$isSettings) {
                           Text("Settings")
                       }
            NavigationLink(destination: Favorite(), isActive: self.$isFavorite) {
                Text("Favorite")
            }
            NavigationLink(destination: Login(), isActive: self.$isLogin) {
                Text("Login")
            }
            NavigationLink(destination: Search(), isActive: self.$isSearch) {
                Text("Search")
            }
}

Podemos gerenciar links de várias maneiras:
- Controle de atividade do NavigationLink através da propriedade @Binding

 NavigationLink(destination: ProfileView(), isActive: self.$isProfile) {
                Text("Profile")
            }

- controle sobre a criação de links por meio de uma condição (variáveis ​​de estado)

       if self.isProfile {
            NavigationLink(destination: ProfileView()) {
                Text("Profile")
            }
}

O primeiro método nos adiciona o trabalho de monitorar o estado das variáveis ​​de controle.
Se planejamos navegar mais de um nível à frente, essa é uma tarefa muito difícil.

No caso da tela da lista de elementos semelhantes, tudo parece compacto:

 NavigationView{
        List(model.data) { item in
            NavigationLink(destination: NewsItemView(item:item)) {
            NewsItemRow(data: item)
            }
        }

O problema mais sério do NavigationLink, na minha opinião, é que todos os links de exibição especificados não são preguiçosos. Eles são criados não no momento em que o link é acionado, mas no momento da criação. Se tivermos uma lista de muitos elementos ou transições para diversos conteúdos pesados ​​da View, isso não afetará da melhor maneira o desempenho de nosso aplicativo. Se ainda tivermos o ViewModel anexado a esses View com lógica, na implementação em que o View do ciclo de vida não é levado em consideração ou não corretamente, a situação se torna muito difícil.

Por exemplo, temos uma lista de notícias com elementos do mesmo tipo. Nunca fomos a uma única tela de uma única notícia e os modelos já estão em nossa memória: o



que podemos fazer neste caso para facilitar nossa vida?

Primeiro, lembre-se de que o View não existe no vácuo, mas é renderizado no UIHostingController.

open class UIHostingController<Content> : UIViewController where Content : View {

    public init(rootView: Content)

    public var rootView: Content
//...
}

E este é o UIViewController. Para que possamos fazer o seguinte. Transferiremos toda a responsabilidade pela transição para a próxima Visualização dentro do novo UIHostingController para o controlador da Visualização atual. Vamos criar módulos de navegação e configuração que chamaremos de nossa Visualização.

O navegador que trabalha com o UIViewController terá a seguinte aparência:

class Navigator {
    private init(){}
    
    static let shared = Navigator()
    
    private weak var view: UIViewController?
    
    internal weak var nc: UINavigationController?
        
   func setup(view: UIViewController) {
        self.view = view
    }
     
  internal func open<Content:View>(screen: Content.Type, _ data: Any? = nil) {
     if let vc = ModuleConfig.shared.config(screen: screen)?
        .createScreen(data) {
        self.nc?.pushViewController(vc, animated: true)
        }
   }

Pelo mesmo princípio, criaremos uma fábrica de configuradores, o que nos dará a implementação do configurador de um módulo específico:

protocol IConfugator: class {
    func createScreen(_ data: Any?)->UIViewController
}

class ModuleConfig{
    private init(){}
    static let shared = ModuleConfig()
    
    func config<Content:View>(screen: Content.Type)->IConfugator? {
        if screen == NewsListView.self {
            return NewsListConfigurator.shared
        }
      // -
        return nil
    }
}

O navegador, por tipo de tela, solicita o configurador de um módulo específico, transfere para ele todas as informações necessárias.

class NewsListConfigurator: IConfugator {
    static let shared = NewsListConfigurator()
    
    func createScreen(_ data: Any?) -> UIViewController {
         var view = NewsListView()
        let presenter = NewsListPresenter()
         let interactor = NewsListInteractor()
        
        interactor.output = presenter
        presenter.output = view
        view.output = interactor
        
        let vc = UIHostingController<ContainerView<NewsListView>>
                    (rootView: ContainerView(content: view))
        return vc
    }
}

O configurador distribui o UIViewController, que é o Navegador e envia o UINavigationController para a pilha compartilhada.



Substitua NavigationLink no código por uma chamada ao Navigator. Como gatilho, teremos um evento de clicar em um item da lista:

  List(model.data) { item in
            NewsItemRow(data: item)
                .onTapGesture {
                Navigator.shared.open(screen: NewsItemView.self, item)
            }
        }

Nada nos impede de invocar o Navigator em qualquer método View da mesma maneira. Não apenas dentro do corpo.

Além do fato de o código ter se tornado visivelmente mais limpo, também descarregamos a memória. Afinal, com essa abordagem, o View será criado apenas quando chamado.



Agora, nosso aplicativo SwiftUI é mais fácil de expandir e modificar. O código é limpo e bonito.
Você pode encontrar o código de exemplo aqui .

Da próxima vez, falaremos sobre a implementação mais profunda do Combine.

All Articles