Intento hacer la arquitectura para una aplicación SwiftUI más grande y lista para producción. Me encuentro todo el tiempo con el mismo problema que apunta a una falla de diseño importante en SwiftUI.
Todavía nadie podía darme una respuesta completa, lista para la producción.
¿Cómo hacer vistas reutilizables SwiftUI
que contengan navegación?
Como SwiftUI
NavigationLink
está fuertemente vinculado a la vista, esto simplemente no es posible de tal manera que se amplíe también en aplicaciones más grandes. NavigationLink
en esas pequeñas aplicaciones de muestra funciona, sí, pero no tan pronto como quiera reutilizar muchas vistas en una aplicación. Y tal vez también reutilizar sobre los límites del módulo. (como: reutilizar View en iOS, WatchOS, etc.)
El problema de diseño: los enlaces de navegación están codificados en la vista.
NavigationLink(destination: MyCustomView(item: item))
Pero si la vista que contiene esto NavigationLink
es reutilizable, no puedo codificar el destino. Tiene que haber un mecanismo que proporcione el destino. Pregunté esto aquí y obtuve una respuesta bastante buena, pero aún no es la respuesta completa:
Coordinador / Enrutador / NavigationLink de SwiftUI MVVM
La idea era inyectar los enlaces de destino en la vista reutilizable. En general, la idea funciona, pero desafortunadamente no se adapta a las aplicaciones de producción reales. Tan pronto como tengo varias pantallas reutilizables, me encuentro con el problema lógico de que una vista reutilizable ( ViewA
) necesita un destino de vista preconfigurado ( ViewB
). Pero, ¿y si ViewB
también necesita un destino de vista preconfigurado ViewC
? Yo tendría que crear ViewB
ya de tal manera que ViewC
ya se inyecta en ViewB
antes de inyectar ViewB
en ViewA
. Y así sucesivamente ... pero como los datos que en ese momento deben pasarse no están disponibles, la construcción completa falla.
Otra idea que tuve fue utilizar el Environment
mecanismo de inyección como dependencia para inyectar destinos NavigationLink
. Pero creo que esto debería considerarse más o menos como un truco y no como una solución escalable para aplicaciones grandes. Terminaríamos usando el entorno básicamente para todo. Pero debido a que el entorno también se puede usar solo dentro de las vistas (no en coordinadores o modelos de vista separados), esto crearía construcciones extrañas en mi opinión.
Al igual que la lógica de negocios (por ejemplo, ver el código del modelo) y la vista deben estar separadas, también la navegación y la vista deben estar separadas (por ejemplo, el patrón Coordinador). UIKit
Es posible porque accedemos a la vista UIViewController
y UINavigationController
detrás de ella. UIKit's
MVC ya tenía el problema de que combinaba tantos conceptos que se convirtió en el nombre divertido "Massive-View-Controller" en lugar de "Model-View-Controller". Ahora continúa un problema similar SwiftUI
pero peor en mi opinión. La navegación y las vistas están fuertemente acopladas y no se pueden desacoplar. Por lo tanto, no es posible hacer vistas reutilizables si contienen navegación. Fue posible resolver esto en UIKit
pero ahora no puedo ver una solución sensata enSwiftUI
. Desafortunadamente, Apple no nos dio una explicación sobre cómo resolver problemas de arquitectura como ese. Tenemos solo algunas pequeñas aplicaciones de muestra.
Me encantaría que me demuestren lo contrario. Muéstrame un patrón de diseño de aplicaciones limpio que resuelva esto para aplicaciones grandes listas para producción
Gracias por adelantado.
Actualización: esta recompensa terminará en unos minutos y desafortunadamente aún nadie pudo proporcionar un ejemplo de trabajo. Pero comenzaré una nueva recompensa para resolver este problema si no puedo encontrar otra solución y vincularla aquí. ¡Gracias a todos por su gran contribución!
Respuestas:
¡El cierre es todo lo que necesitas!
Escribí una publicación sobre cómo reemplazar el patrón de delegado en SwiftUI con cierres. https://swiftwithmajid.com/2019/11/06/the-power-of-closures-in-swiftui/
fuente
Mi idea sería más o menos ser una combinación de
Coordinator
yDelegate
patrón. Primero, crea unaCoordinator
clase:Adapte el
SceneDelegate
para usar elCoordinator
:Dentro de
ContentView
, tenemos esto:Podemos definir el
ContenViewDelegate
protocolo de esta manera:Donde
Item
es solo una estructura que es identificable, podría ser cualquier otra cosa (por ejemplo, la identificación de algún elemento como en unTableView
en UIKit)El siguiente paso es adoptar este protocolo
Coordinator
y simplemente pasar la vista que desea presentar:Hasta ahora, esto ha funcionado bien en mis aplicaciones. Espero que ayude.
fuente
Text("Returned Destination1")
a algo asíMyCustomView(item: ItemType, destinationView: View)
. Entonces esoMyCustomView
también necesita algunos datos y destino inyectado. ¿Cómo resolverías eso?dependencies
ydestination
.Text("Returned Destination1")
. ¿Qué pasa si esto necesita ser aMyCustomView(item: ItemType, destinationView: View)
. ¿Qué vas a inyectar allí? Entiendo la inyección de dependencia, el acoplamiento flojo a través de protocolos y las dependencias compartidas con los coordinadores. Todo eso no es el problema, es el anidamiento necesario. Gracias.Algo que se me ocurre es que cuando dices:
No es del todo cierto. En lugar de proporcionar vistas, puede diseñar sus componentes reutilizables para que proporcione cierres que ofrezcan vistas a pedido.
De esa manera, el cierre que produce ViewB a pedido puede proporcionarle un cierre que produce ViewC a pedido, pero la construcción real de las vistas puede ocurrir en un momento en que la información contextual que necesita está disponible.
fuente
Aquí hay un ejemplo divertido de profundizar infinitamente y cambiar sus datos para la siguiente vista detallada mediante programación
fuente
Estoy escribiendo una serie de publicaciones de blog sobre la creación de un enfoque de Coordinadores MVP + en SwiftUI que puede ser útil:
https://lascorbe.com/posts/2020-04-27-MVPCoordinators-SwiftUI-part1/
El proyecto completo está disponible en Github: https://github.com/Lascorbe/SwiftUI-MVP-Coordinator
Estoy tratando de hacerlo como si fuera una gran aplicación en términos de escalabilidad. Creo que he resuelto el problema de navegación, pero todavía tengo que ver cómo hacer enlaces profundos, que es en lo que estoy trabajando actualmente. Espero que ayude.
fuente
NavigationView
la vista raíz es fantástica. Esta es, con mucho, la implementación más avanzada de Coordinadores SwiftUI que vi con diferencia.NavigationLink
pero lo hace introduciendo una nueva dependencia acoplada. ElMasterView
en su ejemplo no depende deNavigationButton
. Imagine colocarloMasterView
en un paquete Swift: ya no se compilaría porque el tipoNavigationButton
es desconocido. Además, no veo cómo se resolvería el problema de la reutilización anidadaViews
.Esta es una respuesta completamente descabellada, por lo que probablemente resulte absurdo, pero me vería tentado a utilizar un enfoque híbrido.
Use el entorno para pasar a través de un único objeto coordinador, llamémoslo NavigationCoordinator.
Proporcione a sus vistas reutilizables algún tipo de identificador que se configure dinámicamente. Este identificador proporciona información semántica correspondiente al caso de uso real de la aplicación del cliente y la jerarquía de navegación.
Haga que las vistas reutilizables consulten al Coordinador de Navegación para la vista de destino, pasando su identificador y el identificador del tipo de vista al que están navegando.
Esto deja a NavigationCoordinator como un único punto de inyección, y es un objeto sin vista al que se puede acceder fuera de la jerarquía de vista.
Durante la configuración, puede registrar las clases de vista correctas para que regrese, utilizando algún tipo de coincidencia con los identificadores que se pasan en tiempo de ejecución. Algo tan simple como coincidir con el identificador de destino podría funcionar en algunos casos. O coincidir con un par de identificadores de host y destino.
En casos más complejos, puede escribir un controlador personalizado que tenga en cuenta otra información específica de la aplicación.
Dado que se inyecta a través del entorno, cualquier vista puede anular el Coordinador de navegación predeterminado en cualquier punto y proporcionar uno diferente a sus subvistas.
fuente