Swiftui中協調員模式的簡單,強大而優雅的實現。 Stinsen使用100%Swiftui編寫,這使其在iOS,TVOS,WatchOS和MacOS設備之間無縫地工作。
我們都知道,在使用更大尺寸的應用或嘗試應用諸如MVVM之類的建築模式時,Uikit中的路由很難優雅。不幸的是,Swiftui開箱即用的問題與Uikit相同的問題遇到了許多問題:諸如NavigationLink等概念生活在視圖層中,我們仍然沒有清晰的流動和路線概念,等等。 Stinsen的創建是為了減輕這些痛苦,是協調模式的實施。用Swiftui編寫,它是完全跨平台的,並使用了本機工具,例如@EnvironmentObject 。目的是讓Stinsen感覺像Swiftui中缺少的工具,符合其編碼風格和一般原則。
通常在SwiftUI中,該視圖必須使用NavigationLink處理在導航堆棧中添加其他視圖。我們在這裡擁有的是視圖之間緊密的耦合,因為視圖必須事先知道它可以在之間導航的所有其他視圖。此外,觀點違反了單一責任原則(SRP)。使用協調員模式,在2015年的NSSpain會議上由Soroush Khanlou提交給iOS社區,我們可以將這一責任委託給高級階級:協調員。
使用導航堆棧的示例:
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 ( )
}
} @Route s定義了可以從當前協調器執行的所有可能路由以及將要執行的過渡。右側的值是在路由時將執行的出廠功能。該功能可以返回SwiftUI視圖或其他協調員。 @Root另一種沒有過渡的路由,用於定義協調員導航堆棧的第一個視圖,該視NavigationStack -Class引用。
開箱即用的Stinsen有兩種不同類型的Coordinatable協議可以實施的協調協議:
NavigationCoordinatable用於導航流。如果您想推開導航堆棧,請確保將它們包裹在導航瀏覽器中。TabCoordinatable用於TabViews。此外, Stinsen還有兩個可以使用的協調員,即ViewWrapperCoordinator和NavigationViewCoordinator 。 ViewWrapperCoordinator是一個協調員,您可以子類或立即使用將協調器包裹在視圖中,而NavigationViewCoordinator是一個ViewWrapperCoordinator子類,將您的協調器包裝在NavigationView中。
可以使用.view()創建協調器的視圖,因此,為了向用戶展示協調器,您只需執行以下操作:
struct StinsenApp : App {
var body : some Scene {
WindowGroup {
MainCoordinator ( ) . view ( )
}
}
} Stinsen可用於為您的整個應用程序供電,或者僅僅是應用程序的一部分。如果您願意,您仍然可以使用通常的Swiftui NavigationLink s並使用Stinsen管理的內部視圖內部視圖。
使用一個既引用協調器又引用視圖的路由器,我們可以從視圖執行過渡。在視圖內,可以使用@EnvironmentObject獲取路由器。使用路由器可以過渡到其他路線:
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 " )
}
)
)
}
}您還可以獲取引用在樹早期出現的協調器的路由器。例如,您可能需要從TabView中的視圖切換選項卡。
路由可以直接在協調員本身上執行,如果您希望您的協調器具有一些邏輯,或者如果您將協調員傳遞到圍繞:
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 )
}
}
}
}您可以從路由器/協調器中執行哪些操作取決於所使用的協調員。例如,使用NavigationCoordinatable ,您可以執行的某些功能是:
popLast從堆棧中刪除最後一項。請注意, Stinsen不在乎是否呈現視圖或推動,兩者都使用相同的功能。pop從堆棧中刪除視圖。此功能只能由路由器執行,因為只有路由器知道您要彈出哪種視圖。popToRoot清除堆棧。root更改根(即堆棧的第一個視圖)。如果根已經是活躍的根,那將什麼都不做。route - 導航到另一條路線。focusFirst從第一項開始,找到指定的路由是否存在於堆棧中。如果找到,將在此之後刪除所有內容。dismissCoordinator - 刪除整個協調員,是從樹上刪除的孩子。
克隆回購併在示例/應用中運行Stinsenapp ,以感覺如何使用Stinsen 。 Stinsenapp在iOS,TVOS,WatchOS和MacOS上工作。它試圖展示Stinsen可以使用的許多功能。此讀取中的大多數代碼都來自示例應用程序。還有一個示例,說明如何使用Stinsen在SwiftUI中應用可測試的MVVM-C體系結構,該體系結構可在示例/MVVM中使用。
由於僅在View中訪問@EnvironmentObject ,因此Stinsen提供了幾種從ViewModel進行路由的方法。您可以通過nitializer注入協調器,或在Creation中註冊它並通過依賴項注入框架在ViewModel中解決。這些是推薦的方法,因為您將具有最大的控制和功能。
其他方法是使用onAppear函數傳遞路由器:
struct TodosScreen : View {
@ StateObject var viewModel = TodosViewModel ( )
@ EnvironmentObject var projects : TodosCoordinator . Router
var body : some View {
List {
/* ... */
}
. onAppear {
viewModel . router = projects
}
}
}您還可以使用所謂的RouterStore來重新放置路由器。 RouterStore保存了路由器的實例,您可以通過自定義屬性處理器獲得它。
檢索路由器:
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 )
}
}要查看此示例,請在示例/MVVM中檢查MVVM應用程序。
有時,您需要自定義協調員生成的視圖。 navigation coordinable和tabcoordinable具有customize功能,您可以實現以下操作:
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 )
}
}
}
}
}還有一個ViewWrapperCoordinator ,您也可以使用自定義。
由於協調器/路由器上的大多數功能返回協調器,因此您可以使用結果並將它們鏈接在一起以執行更高級的路由(如果需要)。例如,創建一個將更改選項卡的SwiftUI按鈕,並在登錄後從應用中的任何地方選擇一個特定的todo:
VStack {
ForEach ( todosStore . favorites ) { todo in
Button ( todo . name ) {
authenticatedRouter
. focusFirst ( . todos )
. child
. popToRoot ( )
. route ( to : . todo , todo . id )
}
}
} authenticatedRouter引用的AuthenticatedCoordinator是一個TabCoordinatable ,因此該函數將:
focusFirst :返回由路由todos表示的第一個選項卡並將其製作為“活動”選項卡,除非它已經是活動的標籤。child :將返回它的孩子, Todos -tab是NavigationViewCoordinator ,孩子是NavigationCoordinatable 。popToRoot :將彈出任何可能出現或可能不在的孩子。route :將帶有指定ID的路線到Todo路線。由於Stinsen使用鍵盤代表路由,因此這些功能是類型保護的,無法創建無效的鏈。這意味著:如果您在A到B中有一條路由,則在B到C中有一個路由,如果您嘗試從A到C路程而無需先路由到B ,則該應用將不會編譯。另外,您不能在TabCoordinatable上執行諸如popToRoot()之類的操作,依此類推。
使用返回的值,您可以在應用程序中輕鬆鏈接:
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配備了幾個坐標,可欣賞標準的Swiftui瀏覽量。例如,如果您想將其用於漢堡 - 曼努(Hamburger-Menu),則需要創建自己的。檢查源代碼以獲得一些靈感。
Stinsen支持兩種安裝方法,可可錄和SPM。
打開XCode和您的項目,單擊File / Swift Packages / Add package dependency...在TextField“輸入軟件包存儲庫URL ”中,編寫https://github.com/rundfunk47/stinsen ,然後按下一個兩次
在應用程序的根目錄中創建一個Podfile 。添加
# Podfile
use_frameworks!
target 'YOUR_TARGET_NAME' do
pod 'Stinsen'
end
DoubleColumnNavigationViewStyle 。這樣做的原因是,由於Swiftui中的isActive問題,它無法正常工作。解決方法:使用UiviewSrementable或創建自己的實現。在BYVA,我們努力創建100%的SwiftUI應用程序,因此自然需要創建一個滿足我們所擁有的和其他需求的協調框架。該框架用於生產,管理約50個流量和約100個屏幕。該框架由 @rundfunk47維護。
斯坦斯(Stins)在瑞典人的“站大師”(Station Master)中很短,斯坦森(Stinsen)是明確的文章“ The Station Master”。俗稱該術語主要用於指向火車調查員,後者負責路由火車。徽標是基於位於瑞典林克平市火車站附近的木雕像。
Stinsen V2的最大變化是,它比Stinsen V1更具型號,它可以更容易鏈接和深入鏈接。
AnyCoordinatable都已被協議替換。它不執行與舊的AnyCoordinatable相同的職責,並且不適合版本2的更類型安全路由,因此請將其從項目中刪除。route(to: .a)而不是route(to: .a) 。.view() 。@Root s,並使用.root()在它們之間切換以獲得相同的功能。Stinsen根據MIT許可發布。有關更多信息,請參見許可證。