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 1parte 2parte 3Com 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.Noentanto, 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.