J'essaie de faire l'architecture d'une application SwiftUI plus grande et prête pour la production. Je rencontre tout le temps le même problème qui pointe vers un défaut majeur de conception dans SwiftUI.
Personne ne pouvait toujours me donner une réponse complète, prête pour la production.
Comment faire des vues réutilisables dans SwiftUI
lesquelles contiennent la navigation?
Comme le SwiftUI
NavigationLink
est fortement lié à la vue, cela n'est tout simplement pas possible de telle sorte qu'il évolue également dans les applications plus grandes. NavigationLink
dans ces petits exemples d'applications, oui - mais pas dès que vous souhaitez réutiliser de nombreuses vues dans une seule application. Et peut-être aussi réutiliser au-delà des limites des modules. (comme: réutiliser View dans iOS, WatchOS, etc ...)
Le problème de conception: les liens de navigation sont codés en dur dans la vue.
NavigationLink(destination: MyCustomView(item: item))
Mais si la vue contenant ceci NavigationLink
doit être réutilisable, je ne peux pas coder en dur la destination. Il doit y avoir un mécanisme qui fournit la destination. J'ai posé cette question ici et j'ai obtenu une assez bonne réponse, mais toujours pas la réponse complète:
SwiftUI MVVM Coordinator / Router / NavigationLink
L'idée était d'injecter les liens de destination dans la vue réutilisable. En général, l'idée fonctionne, mais malheureusement, cela ne s'adapte pas aux vraies applications de production. Dès que j'ai plusieurs écrans réutilisables, je rencontre le problème logique qu'une vue réutilisable ( ViewA
) a besoin d'une vue-destination préconfigurée ( ViewB
). Mais que se passe-t-il si vous ViewB
avez également besoin d'une destination de vue préconfigurée ViewC
? Je besoin de créer ViewB
déjà de telle sorte que l' ViewC
on injecte déjà ViewB
avant que j'injecter ViewB
dans ViewA
. Et ainsi de suite .... mais comme les données qui à ce moment doivent être transmises ne sont pas disponibles, la construction entière échoue.
Une autre idée que j'avais était d'utiliser le Environment
mécanisme d'injection comme dépendance pour injecter des destinations NavigationLink
. Mais je pense que cela devrait être considéré plus ou moins comme un hack et non comme une solution évolutive pour les grandes applications. Nous finirions par utiliser l'environnement essentiellement pour tout. Mais comme l'environnement ne peut également être utilisé qu'à l' intérieur de View (pas dans des coordinateurs ou des ViewModels séparés), cela créerait à nouveau des constructions étranges à mon avis.
Comme la logique métier (par exemple, voir le code du modèle) et la vue doivent être séparées, la navigation et la vue doivent être séparées (par exemple le modèle de coordinateur). C'est UIKit
possible parce que nous accédons à UIViewController
et UINavigationController
derrière la vue. UIKit's
MVC avait déjà le problème de mélanger tellement de concepts qu'il devint le nom amusant "Massive-View-Controller" au lieu de "Model-View-Controller". Maintenant, un problème similaire persiste, SwiftUI
mais encore pire à mon avis. La navigation et les vues sont fortement couplées et ne peuvent pas être découplées. Il n'est donc pas possible de faire des vues réutilisables si elles contiennent de la navigation. Il était possible de résoudre ce problème, UIKit
mais maintenant je ne vois pas de solution sensée dansSwiftUI
. Malheureusement, Apple ne nous a pas expliqué comment résoudre de tels problèmes architecturaux. Nous avons juste quelques petits exemples d'applications.
J'adorerais avoir tort. Veuillez me montrer un modèle de conception d'application propre qui résout ce problème pour les grandes applications prêtes pour la production.
Merci d'avance.
Mise à jour: cette prime se terminera dans quelques minutes et malheureusement, personne n'a encore été en mesure de fournir un exemple de travail. Mais je vais commencer une nouvelle prime pour résoudre ce problème si je ne trouve pas d'autre solution et le lier ici. Merci à tous pour leur grande contribution!
Réponses:
La fermeture est tout ce dont vous avez besoin!
J'ai écrit un article sur le remplacement du modèle de délégué dans SwiftUI par des fermetures. https://swiftwithmajid.com/2019/11/06/the-power-of-closures-in-swiftui/
la source
Mon idée serait à peu près une combinaison de
Coordinator
etDelegate
modèle. Créez d'abord uneCoordinator
classe:Adaptez le
SceneDelegate
pour utiliserCoordinator
:À l'intérieur de
ContentView
, nous avons ceci:Nous pouvons définir le
ContenViewDelegate
protocole comme ceci:Où
Item
est juste une structure qui est identifiable, pourrait être autre chose (par exemple id d'un élément comme dans unTableView
dans UIKit)L'étape suivante consiste à adopter ce protocole
Coordinator
et simplement passer la vue que vous souhaitez présenter:Jusqu'à présent, cela a bien fonctionné dans mes applications. J'espère que ça aide.
la source
Text("Returned Destination1")
pour quelque chose commeMyCustomView(item: ItemType, destinationView: View)
. IlMyCustomView
faut donc également injecter des données et une destination. Comment résoudriez-vous cela?dependencies
etdestination
.Text("Returned Destination1")
. Et si cela devait être unMyCustomView(item: ItemType, destinationView: View)
. Qu'allez-vous y injecter? Je comprends l'injection de dépendances, les protocoles de couplage lâche et les dépendances partagées avec les coordinateurs. Tout cela n'est pas le problème - c'est l'imbrication nécessaire. Merci.Quelque chose qui me vient à l'esprit est que lorsque vous dites:
ce n'est pas tout à fait vrai. Plutôt que de fournir des vues, vous pouvez concevoir vos composants réutilisables de manière à fournir des fermetures qui fournissent des vues à la demande.
De cette façon, la fermeture qui produit ViewB à la demande peut lui fournir une fermeture qui produit ViewC à la demande, mais la construction réelle des vues peut se produire à un moment où les informations contextuelles dont vous avez besoin sont disponibles.
la source
Voici un exemple amusant de recherche infinie et de modification de vos données pour la prochaine vue détaillée par programme
la source
J'écris une série d'articles de blog sur la création d'une approche MVP + Coordinators dans SwiftUI qui peut être utile:
https://lascorbe.com/posts/2020-04-27-MVPCoordinators-SwiftUI-part1/
Le projet complet est disponible sur Github: https://github.com/Lascorbe/SwiftUI-MVP-Coordinator
J'essaie de le faire comme s'il s'agissait d'une grosse application en termes d'évolutivité. Je pense avoir résolu le problème de navigation, mais je dois encore voir comment faire des liens profonds, sur lesquels je travaille actuellement. J'espère que ça aide.
la source
NavigationView
la vue racine est fantastique. C'est de loin la mise en œuvre la plus avancée des coordinateurs SwiftUI que j'ai vue de loin.NavigationLink
mais il le fait en introduisant une nouvelle dépendance couplée. LeMasterView
dans votre exemple ne dépend pasNavigationButton
. Imaginez le placementMasterView
dans un package Swift - il ne compilerait plus car le typeNavigationButton
est inconnu. De plus, je ne vois pas comment le problème des réutilisables imbriquésViews
serait résolu par cela?Il s'agit d'une réponse complètement absurde, donc cela se révélera probablement un non-sens, mais je serais tenté d'utiliser une approche hybride.
Utilisez l'environnement pour traverser un seul objet coordinateur - appelons-le NavigationCoordinator.
Donnez à vos vues réutilisables une sorte d'identifiant qui est défini dynamiquement. Cet identifiant fournit des informations sémantiques correspondant au cas d'utilisation réel et à la hiérarchie de navigation de l'application cliente.
Demandez aux vues réutilisables d'interroger le NavigationCoordinator pour la vue de destination, en passant leur identifiant et l'identifiant du type de vue vers lequel ils naviguent.
Cela laisse le NavigationCoordinator comme un seul point d'injection, et c'est un objet non-vue auquel on peut accéder en dehors de la hiérarchie de vue.
Pendant la configuration, vous pouvez enregistrer les bonnes classes de vue pour qu'elles soient renvoyées, en utilisant une sorte de correspondance avec les identifiants transmis lors de l'exécution. Quelque chose d'aussi simple que la correspondance avec l'identifiant de destination peut fonctionner dans certains cas. Ou une correspondance avec une paire d'identifiants d'hôte et de destination.
Dans les cas plus complexes, vous pouvez écrire un contrôleur personnalisé qui prend en compte d'autres informations spécifiques à l'application.
Puisqu'il est injecté via l'environnement, n'importe quelle vue peut remplacer le NavigationCoordinator par défaut à tout moment et en fournir un autre à ses sous-vues.
la source