一个用于编辑字段数据的多平台 SwiftUI 组件。
作为一个开源库,可以集成到 SwiftUI 应用程序中。
SwiftDetailer 是 OpenAlloc 开源 Swift 软件工具家族的一部分。
macOS | iOS |
---|---|
![]() |
![]() |
List
、Table
、LazyVStack
等*.editDetailer
视图修饰符,用于支持(绑定、读/写)视图.viewDetailer
视图修饰符,用于支持(未绑定、只读)视图DetailerMenu
包可用,方便调用AnyView
)* 以及配套的 Tabler 组件(由同一作者编写)
** 其他平台,如 macCatalyst、Mac 上的 iPad、watchOS、tvOS 等,如果支持,则支持性较差。 请贡献代码以提高支持!
一个示例,展示了 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,您需要
SwiftDetailer
和 SwiftDetailerMenu
包。editDetailer
的调用,它可用作修饰符。Form
,并且...Fruit
元素。这些都显示(并带有注释)在下面的修改后的代码中
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 项目(链接如下)。 它扩展了示例,增加了添加新记录、删除记录和验证输入的操作。
它显示了与 LazyVGrid
和 Table
容器一起使用的 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
处理程序定义后,将启用关联的操作。
minWidth: CGFloat
- 最小工作表宽度;默认值因平台而异canEdit: (Element) -> Bool
- 如果定义了 onSave
,则启用每个元素的修改;默认为 { _ in true }
canDelete: (Element) -> Bool
- 如果定义了 onDelete
,则启用每个元素的删除;默认为 { _ in true }
onDelete: ((Element) -> Void)?
- 删除处理程序;默认为 nil
onValidate: (Context, Element) -> [String]
- 重量级验证的处理程序;默认为 { _, _ in [] }
onSave: ((Context, Element) -> Void)?
- 用户保存的处理程序;默认为 nil
onCancel: (Context, Element) -> Void
- 用户取消的处理程序;默认为 { _, _ in }
titler: ((Element) -> String)?
- 标题生成的处理程序;默认为 nil
validateIndicator: (Bool) -> AnyView
- 默认为“exclamationmark.triangle”图像,带有附加属性演示 Detailer 的应用程序
该库是 OpenAlloc Project 的成员。
版权所有 2021, 2022 OpenAlloc LLC
根据 Apache License, Version 2.0(“许可证”)获得许可;除非符合许可证的规定,否则您不得使用此文件。 您可以在以下位置获得许可证的副本:
https://apache.ac.cn/licenses/LICENSE-2.0
除非适用法律要求或以书面形式达成协议,否则按“原样”分发的软件将不提供任何形式的担保或条件,无论是明示的还是暗示的。 请参阅许可证,了解有关权限和限制的特定语言。
欢迎贡献。 鼓励您提交拉取请求以修复错误、改进文档或提供新功能。
拉取请求不必是生产就绪的功能或修复。 它可以是建议更改的草案,或者只是一个测试,以表明预期的行为存在错误。 关于拉取请求的讨论可以从那里开始。