数据驱动的设置界面

创建你的 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()
    }

在 YouTube 上观看概览演示

几乎每个应用程序都包含某种设置界面,大多数应用程序通常包含调试设置以及面向用户的设置。在 UIKit 中实现这些界面可能会变得非常复杂。SwiftUI 的出现极大地简化了这一点,但我相信通过利用数据驱动的方法和 Sourcery,可以进一步简化它。

这就是 AutomaticSettings 的用武之地。

设置是

它是如何工作的?

你有一个用于设置的主结构体,例如:

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 对变量进行注释,选项如下:

支持的类型

我们自动支持以下类型的 UI:

为了支持自定义枚举,你可以利用实现 CaseIterable, Hashable, RawRepresentable, RawValue == String 的枚举,或者你可以为符合 SettingsDisplayable 的自定义类型添加支持。对于复杂类型,你还可以为你的类型实现自定义的 setting DSL 函数。

添加新部分

当你添加一个新部分并用 AutoSettings 标记它时,你需要通过调用以下任一项将其包含在 BetaSettingsView+Content.swift 内容函数中:

你可以使用 footerView / headerView 修改它们。请记住,如果你使用 injectFooter / injectHeader,那么你不能为链接提供页脚或标题,因为它们会自动调用这些函数,所以你只能使用一种方法。

advertisingLink(footerView: {
                    Button("Report Ad") {
                        //...
                    }
})

调整部分行为

可以使用 // sourcery: annotation 对部分进行注释,选项如下:

因此,允许你通过简单地在 BetaSetingsView 的扩展或其中一个部分子视图中添加正确命名的函数来添加标题/页脚(不用担心,如果你的命名错误,编译器会告诉你)。

该模板目前支持最多 2 级的嵌套,这意味着你可以拥有 BetaSettings.Section.SubSection,但如果需要,它可以很容易地扩展到更深的嵌套级别。

安装

需要几个步骤:

  1. 你的项目需要 Sourcery
  2. 将 Sourcery 模板 复制到你项目的模板中并进行配置。
  3. 将此库代码添加到你的项目中,你可以使用 Swift Package Manager / CocoaPods 或者只复制此项目包含的几个文件。
  4. 为你的设置界面添加视图实现(这是为了允许你进一步自定义它)

如果任何事情不清楚,请参考示例应用程序。该存储库还包含突出显示功能集特定部分的提交。

配置模板

该模板需要在你的 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
    }
}