Ich versuche, die Architektur für eine größere, produktionsbereite SwiftUI-App zu erstellen. Ich stoße ständig auf das gleiche Problem, das auf einen großen Designfehler in SwiftUI hinweist.
Trotzdem konnte mir niemand eine voll funktionsfähige, produktionsbereite Antwort geben.
Wie mache ich wiederverwendbare Ansichten, in SwiftUI
denen Navigation enthalten ist?
Da das SwiftUI
NavigationLink
stark an die Ansicht gebunden ist, ist dies einfach nicht so möglich, dass es auch in größeren Apps skaliert. NavigationLink
In diesen kleinen Beispielen funktionieren Apps zwar - aber nicht, sobald Sie viele Ansichten in einer App wiederverwenden möchten. Und vielleicht auch über Modulgrenzen hinweg wiederverwenden. (wie: Wiederverwendung von View in iOS, WatchOS usw.)
Das Designproblem: NavigationLinks sind in der Ansicht fest codiert.
NavigationLink(destination: MyCustomView(item: item))
Aber wenn die Ansicht, die dies enthält NavigationLink
, wiederverwendbar sein sollte, kann ich das Ziel nicht fest codieren . Es muss einen Mechanismus geben, der das Ziel bereitstellt. Ich habe das hier gefragt und eine ziemlich gute Antwort bekommen, aber immer noch nicht die vollständige Antwort:
SwiftUI MVVM-Koordinator / Router / NavigationLink
Die Idee war, die Ziel-Links in die wiederverwendbare Ansicht einzufügen. Im Allgemeinen funktioniert die Idee, aber leider lässt sich dies nicht auf echte Produktions-Apps skalieren. Sobald ich mehrere wiederverwendbare Bildschirme habe, stoße ich auf das logische Problem, dass eine wiederverwendbare Ansicht ( ViewA
) ein vorkonfiguriertes Ansichtsziel ( ViewB
) benötigt. Was aber, wenn ViewB
auch ein vorkonfiguriertes Ansichtsziel benötigt wird ViewC
? Ich würde erstellen müssen ViewB
bereits in einer solchen Art und Weise , die ViewC
in bereits eingespritzt wird , ViewB
bevor ich spritze ViewB
in ViewA
. Und so weiter ... aber da die Daten, die zu diesem Zeitpunkt übergeben werden müssen, nicht verfügbar sind, schlägt das gesamte Konstrukt fehl.
Eine andere Idee, die ich hatte, war, den Environment
Mechanismus der Abhängigkeitsinjektion zu verwenden, um Ziele für zu injizieren NavigationLink
. Ich denke jedoch, dass dies mehr oder weniger als Hack und nicht als skalierbare Lösung für große Apps betrachtet werden sollte. Wir würden die Umwelt grundsätzlich für alles nutzen. Da die Umgebung jedoch auch nur innerhalb von Views verwendet werden kann (nicht in separaten Koordinatoren oder ViewModels), würde dies meiner Meinung nach wiederum seltsame Konstrukte erzeugen.
Wie Geschäftslogik (z. B. Ansichtsmodellcode) und Ansicht müssen auch Navigation und Ansicht getrennt werden (z. B. das Koordinatormuster). Dies UIKit
ist möglich, weil wir auf UIViewController
und UINavigationController
hinter der Ansicht zugreifen . UIKit's
MVC hatte bereits das Problem, dass es so viele Konzepte zusammenbrachte, dass es zum lustigen Namen "Massive-View-Controller" anstelle von "Model-View-Controller" wurde. Jetzt geht ein ähnliches Problem weiter, SwiftUI
aber meiner Meinung nach noch schlimmer. Navigation und Ansichten sind stark gekoppelt und können nicht entkoppelt werden. Daher ist es nicht möglich, wiederverwendbare Ansichten zu erstellen, wenn diese eine Navigation enthalten. Es war möglich, dies zu lösen, UIKit
aber jetzt sehe ich keine vernünftige Lösung inSwiftUI
. Leider hat Apple uns keine Erklärung gegeben, wie wir solche Architekturprobleme lösen können. Wir haben nur einige kleine Beispiel-Apps.
Ich würde gerne das Gegenteil beweisen. Bitte zeigen Sie mir ein sauberes App-Design-Muster, das dieses Problem für große produktionsreife Apps löst.
Danke im Voraus.
Update: Diese Prämie endet in wenigen Minuten und leider konnte noch niemand ein funktionierendes Beispiel liefern. Aber ich werde eine neue Prämie starten, um dieses Problem zu lösen, wenn ich keine andere Lösung finde und sie hier verlinke. Vielen Dank an alle für ihren tollen Beitrag!
Antworten:
Der Verschluss ist alles was Sie brauchen!
Ich habe einen Beitrag über das Ersetzen des Delegatenmusters in SwiftUI durch Schließungen geschrieben. https://swiftwithmajid.com/2019/11/06/the-power-of-closures-in-swiftui/
quelle
Meine Idee wäre so ziemlich eine Kombination aus
Coordinator
undDelegate
Muster. Erstellen Sie zunächst eineCoordinator
Klasse:Passen Sie das
SceneDelegate
an, um Folgendes zu verwendenCoordinator
:Im Inneren
ContentView
haben wir Folgendes:Wir können das
ContenViewDelegate
Protokoll folgendermaßen definieren :Wo
Item
nur eine Struktur ist, die identifizierbar ist, könnte alles andere sein (z. B. ID eines Elements wie in aTableView
in UIKit)Der nächste Schritt besteht darin, dieses Protokoll zu übernehmen
Coordinator
und einfach die Ansicht zu übergeben, die Sie präsentieren möchten:Dies hat bisher in meinen Apps gut funktioniert. Ich hoffe, es hilft.
quelle
Text("Returned Destination1")
zu so etwas zu wechselnMyCustomView(item: ItemType, destinationView: View)
. Dazu müssen alsoMyCustomView
auch einige Daten und Ziele eingespeist werden. Wie würden Sie das lösen?dependencies
unddestination
.Text("Returned Destination1")
. Was passiert , wenn diese Bedürfnisse ein seinMyCustomView(item: ItemType, destinationView: View)
. Was wirst du dort spritzen? Ich verstehe Abhängigkeitsinjektion, lose Kopplung durch Protokolle und gemeinsame Abhängigkeiten mit Koordinatoren. All das ist nicht das Problem - es ist die notwendige Verschachtelung. Vielen Dank.Mir fällt ein, dass wenn Sie sagen:
es ist nicht ganz wahr. Anstatt Ansichten bereitzustellen, können Sie Ihre wiederverwendbaren Komponenten so gestalten, dass Sie Verschlüsse bereitstellen, die bei Bedarf Ansichten bereitstellen.
Auf diese Weise kann der Abschluss, der ViewB bei Bedarf erzeugt, einen Abschluss liefern, der ViewC bei Bedarf erzeugt. Die eigentliche Erstellung der Ansichten kann jedoch zu einem Zeitpunkt erfolgen, an dem die von Ihnen benötigten Kontextinformationen verfügbar sind.
quelle
Hier ist ein unterhaltsames Beispiel für einen unendlichen Drilldown und das programmgesteuerte Ändern Ihrer Daten für die nächste Detailansicht
quelle
Ich schreibe eine Blogpost-Reihe zum Erstellen eines MVP + Coordinators-Ansatzes in SwiftUI, der nützlich sein kann:
https://lascorbe.com/posts/2020-04-27-MVPCoordinators-SwiftUI-part1/
Das vollständige Projekt ist auf Github verfügbar: https://github.com/Lascorbe/SwiftUI-MVP-Coordinator
Ich versuche es so zu machen, als wäre es eine große App in Bezug auf Skalierbarkeit. Ich glaube, ich habe das Navigationsproblem gelöst, aber ich muss noch sehen, wie man Deep Linking macht, woran ich gerade arbeite. Ich hoffe, es hilft.
quelle
NavigationView
die Root-Ansicht zu erstellen, ist fantastisch. Dies ist bei weitem die fortschrittlichste Implementierung von SwiftUI Coordinators, die ich bei weitem gesehen habe.NavigationLink
aber es tut dies, indem es eine neue gekoppelte Abhängigkeit einführt. DasMasterView
in deinem Beispiel ist nicht abhängig vonNavigationButton
. Stellen Sie sich vor, Sie platzierenMasterView
ein Swift-Paket - es wird nicht mehr kompiliert, da der TypNavigationButton
unbekannt ist. Auch sehe ich nicht, wie das Problem der verschachtelten WiederverwendbarkeitViews
dadurch gelöst würde?Dies ist eine völlig unkonventionelle Antwort, die sich wahrscheinlich als Unsinn herausstellen wird, aber ich wäre versucht, einen hybriden Ansatz zu verwenden.
Verwenden Sie die Umgebung, um ein einzelnes Koordinatorobjekt zu durchlaufen - nennen wir es NavigationCoordinator.
Geben Sie Ihren wiederverwendbaren Ansichten eine Art Kennung, die dynamisch festgelegt wird. Diese Kennung gibt semantische Informationen an, die dem tatsächlichen Anwendungsfall und der Navigationshierarchie der Clientanwendung entsprechen.
Lassen Sie die wiederverwendbaren Ansichten den Navigationskoordinator nach der Zielansicht abfragen und ihre Kennung und die Kennung des Ansichtstyps übergeben, zu dem sie navigieren.
Dadurch bleibt der NavigationCoordinator als einzelner Injektionspunkt und es handelt sich um ein Nichtansichtsobjekt, auf das außerhalb der Ansichtshierarchie zugegriffen werden kann.
Während des Setups können Sie die richtigen Ansichtsklassen für die Rückgabe registrieren, indem Sie eine Art Übereinstimmung mit den zur Laufzeit übergebenen Bezeichnern verwenden. In einigen Fällen kann etwas so Einfaches wie das Abgleichen mit der Zielkennung funktionieren. Oder Abgleich mit einem Paar von Host- und Zielkennungen.
In komplexeren Fällen können Sie einen benutzerdefinierten Controller schreiben, der andere app-spezifische Informationen berücksichtigt.
Da es über die Umgebung eingefügt wird, kann jede Ansicht den Standard-Navigationskoordinator an jedem Punkt überschreiben und seinen Unteransichten einen anderen zuweisen.
quelle