创建你的 Swift 结构体,让 UI 自动为你生成
struct Calculation: AutomaticSettings {
var text = ""
var mode = Mode.linearRegression
}
// sourcery: injectFooter
struct Smoothing: AutomaticSettings {
var dayPeriod = 7
var algorithm = Algorithm.movingAverage
struct Grouped: AutomaticSettings {
// sourcery: range = 1...4
var level: Float = 1
var prettyCool = true
}
var grouped: Grouped = .init()
}
几乎每个应用程序都包含某种设置界面,大多数应用程序通常包含调试设置以及面向用户的设置。在 UIKit
中实现这些界面可能会变得非常复杂。SwiftUI
的出现极大地简化了这一点,但我相信通过利用数据驱动的方法和 Sourcery,可以进一步简化它。
这就是 AutomaticSettings
的用武之地。
设置是
可编码的 (Codable)
你有一个用于设置的主结构体,例如:
struct BetaSettings: AutomaticSettings {
struct Calculation: AutomaticSettings {
var mode = .linearRegression
var averageMethod = .weightedAverage
var timePeriod = 7
}
var calculation: Calculation = .init()
}
Sourcery 分析你的结构体并为每个设置和部分生成函数。你只需将一个内容函数提供给你的主视图
extension BetaSettingsView {
var content: some View {
calculationLink()
otherSectionLink()
Button("Some action that doesn't have data backing") {
// ...
}
}
}
如果你修改了你的数据结构,Sourcery 将更新相应的函数,并且你的 UI 将反映数据的当前状态。
可以使用 // sourcery: annotation
对变量进行注释,选项如下:
skipSetting
:不会为给定的变量生成 UIrequiresRestart
:如果变量发生变化,它将强制在审查屏幕中重启应用程序sideEffect
:允许你添加变量更改时应该发生的副作用(例如,调用外部控制器或某个函数)range
:如果变量是 Float/Double 类型,它允许你指定允许的值的范围,例如 range = 0.0...1.0
我们自动支持以下类型的 UI:
String(字符串)
Bool(布尔值)
Int(整数)
Float(浮点数)
SettingsDisplayable
为了支持自定义枚举,你可以利用实现 CaseIterable, Hashable, RawRepresentable, RawValue == String
的枚举,或者你可以为符合 SettingsDisplayable
的自定义类型添加支持。对于复杂类型,你还可以为你的类型实现自定义的 setting
DSL 函数。
当你添加一个新部分并用 AutoSettings
标记它时,你需要通过调用以下任一项将其包含在 BetaSettingsView+Content.swift
内容函数中:
fooLink()
- 将该部分作为导航链接注入(仅适用于顶级部分)FooView()
- 将创建一个包含给定部分的所有设置的视图你可以使用 footerView
/ headerView
修改它们。请记住,如果你使用 injectFooter
/ injectHeader
,那么你不能为链接提供页脚或标题,因为它们会自动调用这些函数,所以你只能使用一种方法。
advertisingLink(footerView: {
Button("Report Ad") {
//...
}
})
可以使用 // sourcery: annotation
对部分进行注释,选项如下:
injectFooter
:通过你定义的名为 sectionNameFooter
的函数自动注入子部分的页脚injectHeader
:通过你定义的名为 sectionNameHeader
的函数自动注入子部分的标题因此,允许你通过简单地在 BetaSetingsView
的扩展或其中一个部分子视图中添加正确命名的函数来添加标题/页脚(不用担心,如果你的命名错误,编译器会告诉你)。
该模板目前支持最多 2 级的嵌套,这意味着你可以拥有 BetaSettings.Section.SubSection
,但如果需要,它可以很容易地扩展到更深的嵌套级别。
需要几个步骤:
如果任何事情不清楚,请参考示例应用程序。该存储库还包含突出显示功能集特定部分的提交。
该模板需要在你的 sourcery.yml
文件中添加几个参数,像这样在 args:
部分添加它们
args:
settingsView: BetaSettingsView
settingsStructure: BetaSettings
settingsExternalData: BetaSettingsExternalData
settingsImports:
- FrameworkMySettingsNeed
- MyCustomFrameworkName
- OtherInternalFramework
struct BetaSettingsView: View, AutomaticSettingsViewDSL {
private enum Subscreen: Swift.Identifiable {
case review
var id: String {
switch self {
case .review:
return "review"
}
}
}
@ObservedObject
var viewModel: AutomaticSettingsViewModel<BetaSettings, BetaSettingsExternalData>
@State(initialValue: nil)
private var subscreen: Subscreen?
var body: some View {
NavigationView {
content
.sheet(item: $subscreen, content: { subscreen in
Group {
switch subscreen {
case .review:
reviewScreen
}
}
})
.navigationBarTitle("Beta Settings")
.navigationBarItems(
leading: Button("Cancel") {
viewModel.cancel()
},
trailing: Group {
if viewModel.applicableChanges.isEmpty {
EmptyView()
} else {
Button("Review") {
subscreen = .review
}
}
}
)
}
}
var reviewScreen: some View {
NavigationView {
Form {
if let changes = viewModel.applicableChanges, !changes.isEmpty {
ForEach(changes) { change in
VStack {
HStack {
Text(change.name)
Spacer()
if change.requiresRestart {
Image(systemName: "goforward")
.renderingMode(.template)
.foregroundColor(.red)
}
}
HStack {
Spacer()
Text(change.change)
}
}
}
}
}
.navigationBarTitle("Review changes")
.navigationBarItems(
leading: Button("Cancel") {
subscreen = nil
},
trailing: Button("Save\(viewModel.needsRestart ? " & Restart" : "")") {
subscreen = nil
viewModel.saveChanges()
}
)
}
}
}
extension BetaSettingsView {
var content: some View {
// put your actual settings content here
}
}