Destinations 是一个用于 UIKit 和 SwiftUI 的 Swift 库,旨在从你的界面中移除应用和业务逻辑,管理导航流程,并抽象数据源交互,以便你的用户界面可以再次专注于用户。它基于一种强调清晰关注点分离的理念,即应用中每个重要的 View
或 UIViewController
都不应该彼此了解,并且 UI 和功能应该能够在你需求变化时轻松替换。
另请查看 示例项目,以查看 Destinations 在 UIKit 和 SwiftUI 中的实际应用,或深入研究源代码文档。
将应用逻辑、与数据源和系统 API 的紧密耦合以及对其他视图的了解排除在你的视图和视图控制器之外可能是一场持续的战斗,并且常常因交付功能的紧张时间表而加剧。Destinations 是一个从头开始设计的库,旨在解决这个问题。
Destinations 旨在将每个重要的界面元素(通常是将在屏幕上作为活动视图呈现的元素)表示为一个抽象对象,该对象符合 Destinationable
协议,但为了简写,我将其称为 Destination。Destination 在生态系统中的作用是表示一个离散的用户界面屏幕,并代表该界面管理用户交互事件和 Interactor 请求。Interactor 是什么?可以将其视为一个黑盒子,你可以将其附加到 Destination,用于处理逻辑、系统 API 或数据源请求。它可以简单到计数器,也可以复杂到云同步服务。但是,Destination 不直接处理任何 UI;这仍然是界面的责任。Destination 允许其界面专注于该任务。然后,在 Destination 之上是 Flow 对象,正如你可能期望的那样,它管理用户在应用中导航时从一个 Destination 到另一个 Destination 的流程,呈现使用 Provider 构建的每个 Destination,并使用你定义的配置模型。
这些配置模型,称为 DestinationPresentation
和 InteractorConfiguration
,是事情变得更有趣的地方。假设你正在制作一个 UIKit 应用,并创建一个 DestinationPresentation
,它将在当前活动的导航控制器中呈现一个详细信息屏幕。你可以这样做
let detailPresentation = DestinationPresentation<DestinationType, ContentType, TabType>(destinationType: .detail, presentationType: .navigationController(type: .present))
现在我们需要将其馈送到 Provider 中。每个 Provider 构建一个特定的 Destination,并且 Flow 在每次需要创建新 Destination 时都会使用这些 Provider。因此,我们将要使用的 Provider 构建 Destination,其界面管理我们要在其中呈现详细信息屏幕的导航控制器。下面示例中的 presentationsData
字典将该 Destination 的用户交互类型与演示配置模型配对。因此,在本示例中,我们告诉 Destination,当用户点击 .detailButton
类型时,应运行 detailPresentation
操作,这将创建详细信息 Destination 并通过将其推送到导航控制器上来在屏幕上呈现它。
let listProvider = ListProvider(presentationsData: [.detailButton: detailPresentation])
为了在你的界面中启用它,你所需要做的就是将该用户交互类型传递给其 Destination
destination().handleThrowable { [weak self] in
try self?.destination().performInterfaceAction(interactionType: .detailButton)
}
因此,我们创建了一个交互,其中容纳此按钮的 UIViewController
对详细信息屏幕或应在其导航控制器中呈现它一无所知。所有这些演示代码和相关逻辑都由 Destinations 在内部处理。我们既加强了此控制器的关注点分离,又 DRY 了代码。创建 Interactor 操作并将其分配给用户交互同样容易。
由于用户交互类型和演示操作之间的配对,我们还开启了快速重新配置此按钮功能的能力。假设产品团队希望按钮在模态表单中呈现不同的视图。修改按钮的行为就像创建一个新的 DestinationPresentation
并将其分配给 .detailButton
类型一样容易。这种灵活性使得快速测试新行为、进行 A/B 测试、为测试提供模拟数据源等等成为可能。
这是一个简单的 Destinations 初始配置示例,它为 SwiftUI 应用设置初始界面,该应用应显示一个带有两个选项卡及其内容 View
的 TabView
。为了简单起见,我们仅定义一个用户操作,即“colorDetail”交互,当点击颜色单元格时,它会显示详细信息 View
。在此代码中,我们创建了起始 Destination,创建一个演示模型来处理用户选择颜色,设置 Destinations 及其关联用户界面的提供程序,最后构建一个 ViewFlow
,它将管理应用的流程。
func buildFlow() -> ViewFlow<DestinationType, TabType, ContentType> {
// set up starting Destination
let startingTabs: [TabType] = [.colors, .home]
let startingType: DestinationType = .tabBar(tabs: startingTabs)
let startingDestination = DestinationPresentation<DestinationType, ContentType, TabType>(destinationType: startingType, presentationType: .replaceCurrent)
// set up a presentation for user selecting a color
let colorSelection = DestinationPresentation<DestinationType, ContentType, TabType>(destinationType: .colorDetail, presentationType: .navigationController(type: .present))
// create the Destination providers
let colorsListProvider = ColorsListProvider(presentationsData: [.color(model: nil): colorSelection])
let homeProvider = HomeProvider()
let tabBarProvider = TabBarProvider()
let providers: [DestinationType: any ViewDestinationProviding] = [
.colorsList: colorsListProvider,
.home: homeProvider,
.tabBar(tabs: startingTabs): tabBarProvider
]
return ViewFlow<DestinationType, TabType, ContentType>(destinationProviders: providers, startingDestination: startingDestination)
}
在这里,我们使用该 ViewFlow
在 SwiftUI App 文件中显示其初始 UI。
@State var appFlow: ViewFlow<DestinationType, TabType, ContentType>?
var body: some Scene {
WindowGroup {
ZStack {
if hasStartedAppFlow {
appFlow?.startingDestinationView()
}
}
.onAppear(perform: {
if (hasStartedAppFlow == false) {
self.appFlow = buildAppFlow()
self.appFlow?.start()
hasStartedAppFlow = true
}
})
}
}
你可以通过将 Destinations 添加为 Swift 包依赖项来将其添加到 Xcode 项目中。
.product(name: "Destinations", package: "Destinations")
此库在 MIT 许可证下发布。有关详细信息,请参阅 LICENSE。
我很想知道你是否在你的项目中使用 Destinations!