Eu tento fazer a arquitetura para obter um SwiftUI App maior e pronto para produção. Estou correndo o tempo todo com o mesmo problema que aponta para uma falha de design importante no SwiftUI.
Ainda assim, ninguém poderia me dar uma resposta completa, pronta para produção.
Como fazer vistas reutilizáveis nas SwiftUI
quais contêm navegação?
Como o SwiftUI
NavigationLink
aplicativo está fortemente vinculado à visualização, isso simplesmente não é possível, de forma que ele também é dimensionado em aplicativos maiores. NavigationLink
nessas pequenas amostras de aplicativos, sim - mas não assim que você deseja reutilizar muitas visualizações em um aplicativo. E talvez também reutilize além dos limites do módulo. (como: reutilizar a exibição no iOS, WatchOS etc.)
O problema do design: os NavigationLinks são codificados na Visualização.
NavigationLink(destination: MyCustomView(item: item))
Mas se a exibição que contém isso puderNavigationLink
ser reutilizada, não será possível codificar o destino. Tem que haver um mecanismo que forneça o destino. Perguntei isso aqui e obtive uma resposta muito boa, mas ainda não a resposta completa:
Coordenador SwiftUI MVVM / Roteador / NavigationLink
A idéia era injetar os Links de destino na exibição reutilizável. Geralmente, a ideia funciona, mas infelizmente isso não é dimensionado para aplicativos de produção reais. Assim que tenho várias telas reutilizáveis, encontro o problema lógico de que uma view reutilizável ( ViewA
) precisa de um destino de visualização pré-configurado ( ViewB
). Mas e se ViewB
também precisar de um destino de exibição pré-configurado ViewC
? Eu preciso criar ViewB
já de tal forma que ViewC
já é injetado em ViewB
antes de eu injetar ViewB
em ViewA
. E assim por diante ... mas como os dados que naquele momento precisam ser transmitidos não estão disponíveis, toda a construção falha.
Outra idéia que tive foi usar o Environment
mecanismo de injeção como dependência para injetar destinos NavigationLink
. Mas acho que isso deve ser considerado mais ou menos como um hack e não uma solução escalável para aplicativos grandes. Nós acabaríamos usando o Ambiente basicamente para tudo. Mas como o Ambiente também pode ser usado apenas dentro do View (não em coordenadores ou ViewModels separados), isso criaria novamente construções estranhas na minha opinião.
Assim como a lógica de negócios (por exemplo, ver código do modelo) e a visualização, também é necessário separar a navegação e a visualização (por exemplo, o padrão do coordenador). UIKit
É possível porque acessamos UIViewController
e UINavigationController
por trás da visualização. UIKit's
O MVC já teve o problema de juntar tantos conceitos que se tornou o nome divertido "Massive-View-Controller" em vez de "Model-View-Controller". Agora, um problema semelhante continua, SwiftUI
mas ainda pior na minha opinião. A navegação e as visualizações são fortemente acopladas e não podem ser dissociadas. Portanto, não é possível fazer visualizações reutilizáveis se elas contiverem navegação. Foi possível resolver isso, UIKit
mas agora não consigo ver uma solução sã noSwiftUI
. Infelizmente, a Apple não nos deu uma explicação sobre como resolver problemas de arquitetura como esse. Temos apenas alguns aplicativos de amostra pequenos.
Eu adoraria provar que estou errado. Mostre-me um padrão de design de aplicativo limpo que resolve isso para aplicativos prontos para grandes produções.
Desde já, obrigado.
Atualização: essa recompensa terminará em alguns minutos e, infelizmente, ainda ninguém foi capaz de fornecer um exemplo de trabalho. Mas vou iniciar uma nova recompensa para resolver esse problema se não encontrar outra solução e vinculá-la aqui. Obrigado a todos pela excelente contribuição!
Respostas:
O fechamento é tudo que você precisa!
Eu escrevi um post sobre a substituição do padrão delegado no SwiftUI por fechamentos. https://swiftwithmajid.com/2019/11/06/the-power-of-closures-in-swiftui/
fonte
Minha idéia seria praticamente uma combinação de
Coordinator
eDelegate
padrão. Primeiro, crie umaCoordinator
classe:Adapte o
SceneDelegate
para usar oCoordinator
:Dentro de
ContentView
, temos o seguinte:Podemos definir o
ContenViewDelegate
protocolo assim:Onde
Item
é apenas uma estrutura que é identificável, poderia ser qualquer outra coisa (por exemplo, id de algum elemento como em umTableView
no UIKit)O próximo passo é adotar esse protocolo
Coordinator
e simplesmente passar a visualização que você deseja apresentar:Até agora, isso funcionou bem nos meus aplicativos. Espero que ajude.
fonte
Text("Returned Destination1")
para algo comoMyCustomView(item: ItemType, destinationView: View)
. Portanto, issoMyCustomView
também precisa de alguns dados e destino injetados. Como você resolveria isso?dependencies
edestination
.Text("Returned Destination1")
. E se isso precisar ser umMyCustomView(item: ItemType, destinationView: View)
. O que você vai injetar lá? Entendo a injeção de dependência, o acoplamento flexível através de protocolos e as dependências compartilhadas com os coordenadores. Tudo isso não é o problema - é o aninhamento necessário. Obrigado.Algo que me ocorre é que quando você diz:
não é bem verdade. Em vez de fornecer visualizações, você pode projetar seus componentes reutilizáveis para fornecer tampas que fornecem visualizações sob demanda.
Dessa forma, o fechamento que produz o ViewB on demand pode fornecê-lo com um fechamento que produz o ViewC sob demanda, mas a construção real das visualizações pode ocorrer no momento em que as informações contextuais necessárias estão disponíveis.
fonte
Aqui está um exemplo divertido de detalhar infinitamente e alterar seus dados para a próxima exibição de detalhes programaticamente
fonte
Estou escrevendo uma série de posts sobre como criar uma abordagem MVP + Coordinators no SwiftUI, o que pode ser útil:
https://lascorbe.com/posts/2020-04-27-MVPCoordinators-SwiftUI-part1/
O projeto completo está disponível no Github: https://github.com/Lascorbe/SwiftUI-MVP-Coordinator
Estou tentando fazer isso como se fosse um grande aplicativo em termos de escalabilidade. Acho que resolvi o problema de navegação, mas ainda preciso ver como fazer links diretos, e é nisso que estou trabalhando atualmente. Espero que ajude.
fonte
NavigationView
a visualização raiz é fantástica. Esta é de longe a implementação mais avançada de coordenadores SwiftUI que eu vi de longe.NavigationLink
mas o faz introduzindo uma nova dependência acoplada. OMasterView
exemplo não dependeNavigationButton
. Imagine colocarMasterView
em um pacote Swift - ele não seria mais compilado porque o tipoNavigationButton
é desconhecido. Também não vejo como o problema de reutilizável aninhadoViews
seria resolvido por ele?Esta é uma resposta completamente absurda, então provavelmente será um absurdo, mas eu ficaria tentado a usar uma abordagem híbrida.
Use o ambiente para passar por um único objeto coordenador - vamos chamá-lo de NavigationCoordinator.
Dê às suas visualizações reutilizáveis algum tipo de identificador que é definido dinamicamente. Esse identificador fornece informações semânticas correspondentes ao caso de uso real do aplicativo cliente e à hierarquia de navegação.
Faça com que as visualizações reutilizáveis consultem o NavigationCoordinator quanto à visualização de destino, passando seu identificador e o identificador do tipo de visualização para o qual estão navegando.
Isso deixa o NavigationCoordinator como um único ponto de injeção e é um objeto sem visualização que pode ser acessado fora da hierarquia da visualização.
Durante a instalação, você pode registrar as classes de exibição corretas para retornar, usando algum tipo de correspondência com os identificadores que são transmitidos no tempo de execução. Algo tão simples quanto combinar com o identificador de destino pode funcionar em alguns casos. Ou combinando com um par de identificadores de host e destino.
Em casos mais complexos, você pode escrever um controlador personalizado que leve em consideração outras informações específicas do aplicativo.
Como é injetado pelo ambiente, qualquer visualização pode substituir o NavigationCoordinator padrão a qualquer momento e fornecer um diferente para suas subvisões.
fonte