Einfache, leistungsstarke und elegante Implementierung des Koordinatormusters in Swiftui. Stinsen wird mit 100% Swiftui geschrieben, wodurch es nahtlos auf iOS, tvOS, watchos und macOS -Geräten funktioniert.
Wir alle wissen, dass Routing in UIKIT schwierig sein kann, wenn sie mit Anwendungen mit größerer Größe oder beim Versuch, ein architektonisches Muster wie MVVM anzuwenden, zu tun sein. Leider leidet Swiftui aus dem Box unter vielen der gleichen Probleme wie UIKIT: Konzepte wie NavigationLink leben in der View-Layer, wir haben immer noch kein klares Konzept von Strömungen und Routen und so weiter. Stinsen wurde geschaffen, um diese Schmerzen zu lindern, und ist eine Implementierung des Koordinatormusters . In Swiftui geschrieben ist es vollständig plattformübergreifend und verwendet native Tools wie @EnvironmentObject . Das Ziel ist es, Stinsen in Swiftui wie ein fehlendes Werkzeug zu fühlen, das sich seinem Codierungsstil und seinen allgemeinen Prinzipien entspricht.
Normalerweise muss in Swiftui die Ansicht mithilfe von NavigationLink andere Ansichten zum Navigationsstapel hinzufügen. Was wir hier haben, ist eine enge Kopplung zwischen den Ansichten, da die Ansicht im Voraus alle anderen Ansichten wissen muss, dass sie zwischen dem Navigieren kann. Außerdem verstößt die Ansicht gegen das Einzelvertretbarkeitsprinzip (SRP). Mit dem Koordinatormuster, das Soroush Khanlou auf der NSSPain -Konferenz im Jahr 2015 der iOS -Community präsentiert hat, können wir diese Verantwortung an eine höhere Klasse delegieren: den Koordinator.
Beispiel mit einem Navigationsstapel:
final class UnauthenticatedCoordinator : NavigationCoordinatable {
let stack = NavigationStack ( initial : UnauthenticatedCoordinator . start )
@ Root var start = makeStart
@ Route ( . modal ) var forgotPassword = makeForgotPassword
@ Route ( . push ) var registration = makeRegistration
func makeRegistration ( ) -> RegistrationCoordinator {
return RegistrationCoordinator ( )
}
@ ViewBuilder func makeForgotPassword ( ) -> some View {
ForgotPasswordScreen ( )
}
@ ViewBuilder func makeStart ( ) -> some View {
LoginScreen ( )
}
} Der @Route S definiert alle möglichen Routen, die vom aktuellen Koordinator und dem Übergang durchgeführt werden können, der durchgeführt wird. Der Wert auf der rechten Seite ist die Fabrikfunktion, die beim Routing ausgeführt wird. Die Funktion kann entweder eine Swiftui -Ansicht oder einen anderen Koordinator zurückgeben. Der @Root Eine andere Art von Route, die keinen Übergang hat und zur Definition der ersten Ansicht des Navigationsstacks des Koordinators verwendet wird, auf die von der NavigationStack -Klasse verwiesen wird.
Stinsen Out of the Box verfügt über zwei verschiedene Arten von Coordinatable Protokollen, die Ihre Koordinatoren implementieren können:
NavigationCoordinatable - für Navigationsströme. Stellen Sie sicher, dass Sie diese in einen NavigationViewCoordinator einwickeln, wenn Sie den Navigationsstapel drücken möchten.TabCoordinatable - für tabViews. Darüber hinaus verfügt Stinsen über zwei Koordinatoren, die Sie verwenden können, ViewWrapperCoordinator und NavigationViewCoordinator . ViewWrapperCoordinator ist ein Koordinator, den Sie entweder unterklassen oder sofort verwenden können, um Ihren Koordinator in eine Ansicht einzuwickeln, und NavigationViewCoordinator ist eine Subly -Sub -Class ViewWrapperCoordinator , die Ihren Koordinator in eine NavigationView umschließt.
Die Ansicht für den Koordinator kann mit .view() erstellt werden. Um dem Benutzer einen Koordinator anzuzeigen, würden Sie einfach so etwas tun wie:
struct StinsenApp : App {
var body : some Scene {
WindowGroup {
MainCoordinator ( ) . view ( )
}
}
} Stinsen kann verwendet werden, um Ihre gesamte App oder nur Teile Ihrer App mit Strom zu versorgen. Sie können die üblichen Swiftui NavigationLink noch verwenden und Modal Sheets Inside Ansichten präsentieren, die von Stinsen verwaltet werden, wenn Sie dies tun möchten.
Mit einem Router, der sowohl auf den Koordinator als auch auf die Ansicht verweist, können wir Übergänge aus einer Ansicht durchführen. In der Ansicht kann der Router mit @EnvironmentObject abgerufen werden. Mit dem Router kann man zu anderen Routen übergehen:
struct TodosScreen : View {
@ EnvironmentObject var todosRouter : TodosCoordinator . Router
var body : some View {
List {
/* ... */
}
. navigationBarItems (
trailing : Button (
action : {
// Transition to the screen to create a todo:
todosRouter . route ( to : . createTodo )
} ,
label : {
Image ( systemName : " doc.badge.plus " )
}
)
)
}
} Sie können auch Router abrufen, die Koordinatoren referenzieren, die früher im Baum erschienen sind. Zum Beispiel möchten Sie die Registerkarte möglicherweise aus einer Ansicht in der TabView wechseln.
Das Routing kann direkt auf dem Koordinator selbst durchgeführt werden, was nützlich sein kann, wenn Ihr Koordinator eine Logik hat oder den Koordinator übergeben wird:
final class MainCoordinator : NavigationCoordinatable {
@ Root var unauthenticated = makeUnauthenticated
@ Root var authenticated = makeAuthenticated
/* ... */
init ( ) {
/* ... */
cancellable = AuthenticationService . shared . status . sink { [ weak self ] status in
switch status {
case . authenticated ( let user ) :
self ? . root ( . authentiated , user )
case . unauthenticated :
self ? . root ( . unauthentiated )
}
}
}
} Welche Aktionen Sie vom Router/Koordinator ausführen können, hängt von der Art des verwendeten Koordinators ab. Zum Beispiel sind einige der Funktionen, die Sie ausführen können, unter Verwendung eines NavigationCoordinatable :
popLast - Entfernt den letzten Artikel aus dem Stapel. Beachten Sie, dass Stinsen egal ist, ob die Ansicht modal oder gedrückt wurde. Für beide wird die gleiche Funktion verwendet.pop - entfernt die Aussicht vom Stapel. Diese Funktion kann nur von einem Router ausgeführt werden, da nur der Router weiß, welche Ansicht Sie zu knallen versuchen.popToRoot - Löscht den Stapel.root - ändert die Wurzel (dh die erste Ansicht des Stapels). Wenn die Wurzel bereits die aktive Wurzel ist, wird nichts tun.route - Navigiert zu einer anderen Route.focusFirst - Findet die angegebene Route, wenn sie im Stapel aus dem ersten Element vorhanden ist. Wenn gefunden, entfernen Sie danach alles.dismissCoordinator - Löscht den gesamten Koordinator und seine assoziierten Kinder aus dem Baum.
Klonen Sie das Repo und führen Sie den StinsenApp in Beispielen/App aus, um ein Gefühl dafür zu bekommen, wie Stinsen verwendet werden kann. Stinsenapp arbeitet auf iOS, tvos, watchos und macos. Es wird versucht, viele der Funktionen zu präsentieren, die Stinsen für Sie zur Verfügung steht. Der größte Teil des Codes aus dieser Readme stammt aus der Beispiel -App. Es gibt auch ein Beispiel, das zeigt, wie Stinsen verwendet werden kann, um eine Testable MVVM-C-Architektur in Swiftui anzuwenden, die in Beispiel/MVVM erhältlich ist.
Da @EnvironmentObject nur innerhalb einer View zugegriffen werden kann, bietet Stinsen einige Möglichkeiten, aus dem ViewModel auszuleiten. Sie können den Koordinator durch den "Nitializer" injizieren oder ihn bei der Erstellung registrieren und im ViewModel über einen Abhängigkeitsinjektionsgerüst auflösen. Dies sind die empfohlenen Möglichkeiten, dies zu tun, da Sie maximale Kontrolle und Funktionalität haben.
Andere Möglichkeiten bestehen darin, den Router mit der onAppear -Funktion zu übergeben:
struct TodosScreen : View {
@ StateObject var viewModel = TodosViewModel ( )
@ EnvironmentObject var projects : TodosCoordinator . Router
var body : some View {
List {
/* ... */
}
. onAppear {
viewModel . router = projects
}
}
} Sie können auch so genannte RouterStore verwenden, um den Router zurückzusetzen. Der RouterStore spart die Instanz des Routers und Sie können ihn über einen benutzerdefinierten PropertyWrapper erhalten.
Um einen Router abzurufen:
class LoginScreenViewModel : ObservableObject {
// directly via the RouterStore
var main : MainCoordinator . Router ? = RouterStore . shared . retrieve ( )
// via the RouterObject property wrapper
@ RouterObject
var unauthenticated : Unauthenticated . Router ?
init ( ) {
}
func loginButtonPressed ( ) {
main ? . root ( . authenticated )
}
func forgotPasswordButtonPressed ( ) {
unauthenticated ? . route ( to : . forgotPassword )
}
}Um dieses Beispiel in Aktion zu sehen, überprüfen Sie bitte die MVVM-App in Beispielen/MVVM .
Manchmal möchten Sie die von Ihrem Koordinator generierte Ansicht anpassen. NavigationCoordinierable und tabCoordinationable haben eine customize Funktion, die Sie dazu implementieren können, um dies zu tun:
final class AuthenticatedCoordinator : TabCoordinatable {
/* ... */
@ ViewBuilder func customize ( _ view : AnyView ) -> some View {
view
. onReceive ( Services . shared . $authentication ) { authentication in
switch authentication {
case . authenticated :
self . root ( . authenticated )
case . unauthenticated :
self . root ( . unauthenticated )
}
}
}
}
} Es gibt auch einen ViewWrapperCoordinator , den Sie auch anpassen können.
Da die meisten Funktionen auf dem Koordinator/Router einen Koordinator zurückgeben, können Sie die Ergebnisse verwenden und bei Bedarf fortgeschrittenere Routing durchführen. Zum Beispiel zum Erstellen einer Swiftui -Schaltflächen, die die Registerkarte ändern und nach dem Anmeldung ein bestimmtes Toto von überall in der App auswählen:
VStack {
ForEach ( todosStore . favorites ) { todo in
Button ( todo . name ) {
authenticatedRouter
. focusFirst ( . todos )
. child
. popToRoot ( )
. route ( to : . todo , todo . id )
}
}
} Der vom authenticatedRouter verwiesene AuthenticatedCoordinator ist ein TabCoordinatable , sodass die Funktion:
focusFirst : Geben Sie die erste Registerkarte zurück, die durch die Route todos dargestellt wird, und machen Sie es zur aktiven Registerkarte, es sei denn, sie ist bereits der aktive.child : Wird das Kind zurückkehren, der Todos -Tab ist ein NavigationViewCoordinator und das Kind ist der NavigationCoordinatable .popToRoot : wird alle Kinder wegspringen, die möglicherweise anwesend waren oder nicht.route : Route zur Route Todo mit der angegebenen ID. Da Stinsen Tastaturen verwendet, um die Routen darzustellen, sind die Funktionen vom Typ Typ und ungültige Ketten können nicht erstellt werden. Das bedeutet: Wenn Sie eine Route in A bis B und in B bis C haben, wird die App nicht kompiliert, wenn Sie versuchen, von A nach C zu wechseln, ohne zuerst nach B zu leiten. Außerdem können Sie keine Aktionen wie popToRoot() auf einem TabCoordinatable und so weiter ausführen.
Mit den zurückgegebenen Werten können Sie leicht in der App tief verlinken:
final class MainCoordinator : NavigationCoordinatable {
@ ViewBuilder func customize ( _ view : AnyView ) -> some View {
view . onOpenURL { url in
if let coordinator = self . hasRoot ( . authenticated ) {
do {
// Create a DeepLink-enum
let deepLink = try DeepLink ( url : url , todosStore : coordinator . todosStore )
switch deepLink {
case . todo ( let id ) :
coordinator
. focusFirst ( . todos )
. child
. route ( to : . todo , id )
}
} catch {
print ( error . localizedDescription )
}
}
}
}
} Stinsen verfügt über ein paar Koordinierbare für Standard -Swiftui -Ansichten. Wenn Sie es zum Beispiel für einen Hamburger-Menu verwenden möchten, müssen Sie Ihre eigenen erstellen. Überprüfen Sie den Quellcode, um Inspiration zu erhalten.
Stinsen unterstützt zwei Installationsmethoden, Cocoapods und SPM.
Öffnen Sie Xcode und Ihr Projekt, klicken Sie auf File / Swift Packages / Add package dependency... Schreiben Sie im Textfeld " Paket -Repository -URL eingeben " https://github.com/rundfunk47/stinsen und drücken Sie zweimal
Erstellen Sie eine Podfile im Root -Verzeichnis Ihrer App. Hinzufügen
# Podfile
use_frameworks!
target 'YOUR_TARGET_NAME' do
pod 'Stinsen'
end
DoubleColumnNavigationViewStyle nicht. Der Grund dafür ist, dass es aufgrund von Problemen mit isActive in Swiftui nicht wie erwartet funktioniert. Problemumgehung: Verwenden Sie UIViewrepräsentable oder erstellen Sie Ihre eigene Implementierung.Bei BYVA bemühen wir uns, eine 100% ige Swiftui -Anwendung zu erstellen. Daher ist es natürlich, dass wir einen Koordinator -Framework erstellen müssen, der dies und andere Bedürfnisse erfüllt, die wir haben. Das Framework wird in der Produktion verwendet und verwaltet ~ 50 Flows und ~ 100 Bildschirme. Das Framework wird von @Rundfunk47 gepflegt.
Stins ist kurz in Schwedisch für "Station Master" und Stinsen ist der definitive Artikel "The Station Master". Umgangssprachlich wurde der Begriff hauptsächlich verwendet, um sich auf den Zugdispatcher zu beziehen, der für das Routing der Züge verantwortlich ist. Das Logo basiert auf einer hölzernen Statue einer Sting , die sich in der Nähe des Bahnhofs in Linköping, Schweden befindet.
Die größte Veränderung in Stinsen V2 besteht darin, dass es mehr Typ-Safe als Stinsen V1 ist, was unter anderem eine leichtere Ketten und Tiefenverbindung ermöglicht.
AnyCoordinatable wurde durch ein Protokoll ersetzt. Es erfüllt nicht die gleichen Aufgaben wie das alte AnyCoordinatable und passt nicht zu der mehr Typ-sichereren Routing von Version 2. Entfernen Sie es also aus Ihrem Projekt.route(to: .a) verwenden wir route(to: .a) ..view() .@Root S und wechseln Sie zwischen ihnen mit .root() um die gleiche Funktionalität zu erhalten.Stinsen wird unter einer MIT -Lizenz freigelassen. Weitere Informationen finden Sie unter Lizenz.