SeveralSettings 1.0.0

SeveralSettings 1.0.0

Krzysztof Zabłocki 维护。



数据驱动设置界面

在 YouTube 上观看概述演示

几乎每个应用程序都包含某种设置界面,大多数应用程序通常包含面向用户和调试设置,这些界面的实现可能会非常复杂。随着 SwiftUI 的出现,这一切都得到了简化,但我觉得通过利用数据驱动方法和 Sourcery,这些可以进一步简化。

这就是 SeveralSettings 的用武之地。

设置为

  • 可编码
  • 基于纯 Swift 值类型
  • UI 由 Sourcery 生成,但开发者仍可对其进行自定义
  • 更改将被跟踪,并在应用前进行审查(就像事务一样)
  • 设置可以标记为需要重启或触发副作用

它是如何工作的?

您有一个设置的主结构,例如:

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:不为指定的变量生成 UI
  • requiresRestart:如果变量发生变化,会在审查屏幕中强制应用程序重启
  • sideEffect:允许您在变量变化时添加副作用(例如调用外部控制器或一些函数)
  • 范围:如果一个变量是Float/Double类型,则可以指定允许的值范围,例如:范围 = 0.0...1.0
支持类型

我们自动支持以下类型UI

  • 字符串
  • 布尔值
  • 整型
  • 浮点型
  • 枚举
  • 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的扩展或节子视图中添加页眉/页脚。

当前模板支持最多两层嵌套,这意味着你可以有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
    }
}