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许可发布。有关更多信息,请参见许可证。