SwiftDetailer

一个用于编辑字段数据的多平台 SwiftUI 组件。

作为一个开源库,可以集成到 SwiftUI 应用程序中。

SwiftDetailerOpenAlloc 开源 Swift 软件工具家族的一部分。

macOS iOS

特性

* 以及配套的 Tabler 组件(由同一作者编写)

** 其他平台,如 macCatalyst、Mac 上的 iPad、watchOS、tvOS 等,如果支持,则支持性较差。 请贡献代码以提高支持!

Detailer 示例

一个示例,展示了 Detailer 的基本用法。 首先,从在 List 中显示数据行开始

import SwiftUI

struct Fruit: Identifiable {
    var id: String
    var name: String
    var weight: Double
    var color: Color
}

struct ContentView: View {

    @State private var fruits: [Fruit] = [
        Fruit(id: "🍌", name: "Banana", weight: 118, color: .brown),
        Fruit(id: "🍓", name: "Strawberry", weight: 12, color: .red),
        Fruit(id: "🍊", name: "Orange", weight: 190, color: .orange),
        Fruit(id: "🥝", name: "Kiwi", weight: 75, color: .green),
        Fruit(id: "🍇", name: "Grape", weight: 7, color: .purple),
        Fruit(id: "🫐", name: "Blueberry", weight: 2, color: .blue),
    ]

    var body: some View {
        List(fruits) { fruit in
            HStack {
                Text(fruit.id)
                Text(fruit.name).foregroundColor(fruit.color)
                Spacer()
                Text(String(format: "%.0f g", fruit.weight))
            }
        }
    }
}

然后,要为详细信息页面添加基本支持,同时面向 macOS 和 iOS,您需要

这些都显示(并带有注释)在下面的修改后的代码中

import SwiftUI
import Detailer         // A
import DetailerMenu

struct Fruit: Identifiable {
    var id: String
    var name: String
    var weight: Double
    var color: Color
}

struct ContentView: View {

    @State private var fruits: [Fruit] = [
        Fruit(id: "🍌", name: "Banana", weight: 118, color: .brown),
        Fruit(id: "🍓", name: "Strawberry", weight: 12, color: .red),
        Fruit(id: "🍊", name: "Orange", weight: 190, color: .orange),
        Fruit(id: "🥝", name: "Kiwi", weight: 75, color: .green),
        Fruit(id: "🍇", name: "Grape", weight: 7, color: .purple),
        Fruit(id: "🫐", name: "Blueberry", weight: 2, color: .blue),
    ]
    
    @State private var toEdit: Fruit? = nil // B

    typealias Context = DetailerContext<Fruit>

    var body: some View {
        List(fruits) { fruit in
            HStack {
                Text(fruit.id)
                Text(fruit.name).foregroundColor(fruit.color)
                Spacer()
                Text(String(format: "%.0f g", fruit.weight))
            }
            .modifier(menu(fruit)) // C
        }
        .editDetailer(.init(onSave: saveAction),
                      toEdit: $toEdit,
                      originalID: toEdit?.id,
                      detailContent: editDetail) // D
    }
    
    // E
    private func editDetail(ctx: Context, fruit: Binding<Fruit>) -> some View {
        Form {
            TextField("ID", text: fruit.id)
            TextField("Name", text: fruit.name)
            TextField("Weight", value: fruit.weight, formatter: NumberFormatter())
            ColorPicker("Color", selection: fruit.color)
        }
    }
    
    // F
    private func saveAction(ctx: Context, fruit: Fruit) {
        if let n = fruits.firstIndex(where: { $0.id == fruit.id }) {
            fruits[n] = fruit
        }
    }
    
    // C
#if os(macOS)
    private func menu(_ fruit: Fruit) -> EditDetailerContextMenu<Fruit> {
        EditDetailerContextMenu(fruit) { toEdit = $0 }
    }
#elseif os(iOS)
    private func menu(_ fruit: Fruit) -> EditDetailerSwipeMenu<Fruit> {
        EditDetailerSwipeMenu(fruit) { toEdit = $0 }
    }
#endif
}

在 macOS 上,按住 Ctrl 键并单击(或右键单击)一行以调用上下文菜单。 在 iOS 上,滑动该行以调用菜单。

有关包含添加新记录功能的完整实现,请参见 DetailerDemo 项目(链接如下)。 它扩展了示例,增加了添加新记录、删除记录和验证输入的操作。

它显示了与 LazyVGridTable 容器一起使用的 Detailer

菜单

您可以通过各种方法调用 Detailer。 一种方法是通过上下文菜单或滑动菜单。 有关可选菜单支持,请参见 SwiftDetailerMenu

上下文菜单在 macOS 和 iOS 上的使用

macOS iOS

iOS 的滑动菜单

iOS

验证

您可以选择使用 Detailer 验证数据。 有两种方法可用:字段级别和记录级别。

字段级别和记录级别验证可以单独使用或协同使用。

字段级别验证

这是一种轻量级验证形式,其中单个字段获得一个闭包来测试其有效性。 由于每次更改时都会执行它们,因此它们不应运行昂贵的操作,例如访问远程服务器。

字段级别验证作为详细信息表单中的修饰符实现,如演示应用程序中使用的三个(3)验证器的示例所示

private func editDetail(ctx: DetailerContext<Fruit>, fruit: Binding<Fruit>) -> some View {
    Form {
        TextField("ID", text: fruit.id)
            .validate(ctx, fruit, \.id) { $0.count > 0 }
        TextField("Name", text: fruit.name)
            .validate(ctx, fruit, \.name) { $0.count > 0 }
        TextField("Weight", value: fruit.weight, formatter: NumberFormatter())
            .validate(ctx, fruit, \.weight) { $0 > 0 }
        ColorPicker("Color", selection: fruit.color)
    }
}

前两个是测试字符串长度。 第三个是测试数值。

默认情况下,无效字段将附加一个警告图标,当前是“exclamationmark.triangle”,如上图所示。 此图像是可配置的。

所有字段级别验证都必须返回 true 才能启用 Save 按钮。

提示:为了在布局中保持一致的边距间距,您可以创建一个始终成功的验证:.validate(...) { _ in true }

记录级别验证

这可能是用户按下 Save 按钮时执行的重量级验证形式。

它是 DetailerConfig 初始化的一个参数,特别是 onValidate: (Context, Element) -> [String]

在您的操作处理程序中,测试记录,如果没问题,则返回 [],一个空字符串数组。 如果无效,请使用消息填充该数组。 它们将通过警报呈现给用户。

如果使用此验证,则用户将无法保存更改,直到它返回 []

配置

默认值可能因平台而异。 有关详细信息,请参见 DetailerConfigDefaults 代码。

can 处理程序通常用于启用或禁用控件,例如菜单项。 它们受到其 on 对应项的定义的约束。

on 处理程序定义后,将启用关联的操作。

另请参见

演示 Detailer 的应用程序

该库是 OpenAlloc Project 的成员。

许可证

版权所有 2021, 2022 OpenAlloc LLC

根据 Apache License, Version 2.0(“许可证”)获得许可;除非符合许可证的规定,否则您不得使用此文件。 您可以在以下位置获得许可证的副本:

https://apache.ac.cn/licenses/LICENSE-2.0

除非适用法律要求或以书面形式达成协议,否则按“原样”分发的软件将不提供任何形式的担保或条件,无论是明示的还是暗示的。 请参阅许可证,了解有关权限和限制的特定语言。

贡献

欢迎贡献。 鼓励您提交拉取请求以修复错误、改进文档或提供新功能。

拉取请求不必是生产就绪的功能或修复。 它可以是建议更改的草案,或者只是一个测试,以表明预期的行为存在错误。 关于拉取请求的讨论可以从那里开始。