SwiftPamphletApp
SwiftPamphletApp copied to clipboard
List

List 除了能够展示数据外,还有下拉刷新、过滤搜索和侧滑 Swipe 动作提供更多 Cell 操作的能力。
通过 List 的可选子项参数提供数据模型的关键路径来制定子项路劲,还可以实现大纲视图,使用 DisclosureGroup 和 OutlineGroup 可以进一步定制大纲视图。
下面是 List 使用,包括了 DisclosureGroup 和 OutlineGroup 的演示代码:
struct PlayListView: View {
@StateObject var l: PLVM = PLVM()
@State private var s: String = ""
var outlineModel = [
POutlineModel(title: "文件夹一", iconName: "folder.fill", children: [
POutlineModel(title: "个人", iconName: "person.crop.circle.fill"),
POutlineModel(title: "群组", iconName: "person.2.circle.fill"),
POutlineModel(title: "加好友", iconName: "person.badge.plus")
]),
POutlineModel(title: "文件夹二", iconName: "folder.fill", children: [
POutlineModel(title: "晴天", iconName: "sun.max.fill"),
POutlineModel(title: "夜间", iconName: "moon.fill"),
POutlineModel(title: "雨天", iconName: "cloud.rain.fill", children: [
POutlineModel(title: "雷加雨", iconName: "cloud.bolt.rain.fill"),
POutlineModel(title: "太阳雨", iconName: "cloud.sun.rain.fill")
])
]),
POutlineModel(title: "文件夹三", iconName: "folder.fill", children: [
POutlineModel(title: "电话", iconName: "phone"),
POutlineModel(title: "拍照", iconName: "camera.circle.fill"),
POutlineModel(title: "提醒", iconName: "bell")
])
]
var body: some View {
HStack {
// List 通过$语法可以将集合的元素转换成可绑定的值
List {
ForEach($l.ls) { $d in
PRowView(s: d.s, i: d.i)
.listRowInsets(EdgeInsets(top: 5, leading: 15, bottom: 5, trailing: 15))
.listRowBackground(Color.black.opacity(0.2))
}
}
.refreshable {
// 下拉刷新
}
.searchable(text: $s) // 搜索
.onChange(of: s) { newValue in
print("搜索关键字:\(s)")
}
Divider()
// 自定义 List
VStack {
PCustomListView($l.ls) { $d in
PRowView(s: d.s, i: d.i)
}
// 添加数据
Button {
l.ls.append(PLModel(s: "More", i: 0))
} label: {
Text("添加")
}
}
.padding()
Divider()
// 使用大纲
List(outlineModel, children: \.children) { i in
Label(i.title, systemImage: i.iconName)
}
Divider()
// 自定义大纲视图
VStack {
Text("可点击标题展开")
.font(.headline)
PCOutlineListView(d: outlineModel, c: \.children) { i in
Label(i.title, systemImage: i.iconName)
}
}
.padding()
Divider()
// 使用 OutlineGroup 实现大纲视图
VStack {
Text("OutlineGroup 实现大纲")
OutlineGroup(outlineModel, children: \.children) { i in
Label(i.title, systemImage: i.iconName)
}
// OutlineGroup 和 List 结合
Text("OutlineGroup 和 List 结合")
List {
ForEach(outlineModel) { s in
Section {
OutlineGroup(s.children ?? [], children: \.children) { i in
Label(i.title, systemImage: i.iconName)
}
} header: {
Label(s.title, systemImage: s.iconName)
}
}
}
}
}
}
}
// MARK: - 自定义大纲视图
struct PCOutlineListView<D, Content>: View where D: RandomAccessCollection, D.Element: Identifiable, Content: View {
private let v: PCOutlineView<D, Content>
init(d: D, c: KeyPath<D.Element, D?>, content: @escaping (D.Element) -> Content) {
self.v = PCOutlineView(d: d, c: c, content: content)
}
var body: some View {
List {
v
}
}
}
struct PCOutlineView<D, Content>: View where D: RandomAccessCollection, D.Element: Identifiable, Content: View {
let d: D
let c: KeyPath<D.Element, D?>
let content: (D.Element) -> Content
@State var isExpanded = true // 控制初始是否展开的状态
var body: some View {
ForEach(d) { i in
if let sub = i[keyPath: c] {
PCDisclosureGroup(content: PCOutlineView(d: sub, c: c, content: content), label: content(i))
} else {
content(i)
} // end if
} // end ForEach
} // end body
}
struct PCDisclosureGroup<C, L>: View where C: View, L: View {
@State var isExpanded = false
var content: C
var label: L
var body: some View {
DisclosureGroup(isExpanded: $isExpanded) {
content
} label: {
Button {
isExpanded.toggle()
} label: {
label
}
.buttonStyle(.plain)
}
}
}
// MARK: - 大纲模式数据模型
struct POutlineModel: Hashable, Identifiable {
var id = UUID()
var title: String
var iconName: String
var children: [POutlineModel]?
}
// MARK: - List 的抽象,数据兼容任何集合类型
struct PCustomListView<D: RandomAccessCollection & MutableCollection & RangeReplaceableCollection, Content: View>: View where D.Element: Identifiable {
@Binding var data: D
var content: (Binding<D.Element>) -> Content
init(_ data: Binding<D>, content: @escaping (Binding<D.Element>) -> Content) {
self._data = data
self.content = content
}
var body: some View {
List {
Section {
ForEach($data, content: content)
.onMove { indexSet, offset in
data.move(fromOffsets: indexSet, toOffset: offset)
}
.onDelete { indexSet in
data.remove(atOffsets: indexSet) // macOS 暂不支持
}
} header: {
Text("第一栏,共 \(data.count) 项")
} footer: {
Text("The End")
}
}
.listStyle(.plain) // 有.automatic、.inset、.plain、sidebar,macOS 暂不支持的有.grouped 和 .insetGrouped
}
}
// MARK: - Cell 视图
struct PRowView: View {
var s: String
var i: Int
var body: some View {
HStack {
Text("\(i):")
Text(s)
}
}
}
// MARK: - 数据模型设计
struct PLModel: Hashable, Identifiable {
let id = UUID()
var s: String
var i: Int
}
final class PLVM: ObservableObject {
@Published var ls: [PLModel]
init() {
ls = [PLModel]()
for i in 0...20 {
ls.append(PLModel(s: "\(i)", i: i))
}
}
}