
目录
List
List属于比较复杂的视图,功能比较多,问题也比较多。
基本List
基本List类似VStack,每个子项目就是列表的一个条目,具体不限:
Swift
List{
Text("123")
Image(systemName:"magnifyingglass")
Text("123")
}
效果:

如果是VStack就不会有边框和横线装饰,而且内容是居中的。
基于数据的List
很多情形列表来自数据,还需要根据数据变化自动更新,这就需要使用FofEach来遍历数据生成List的内容。
最简单的情况是用字符串数组:
Swift
var items : [String] = ["123","456","123"]
List{
ForEach(items.indices,id: \.self){
item in
Text(items[item])
}
}
ForEach第一个参数是要迭代的对象集合,第二个参数是用作识别每一条数据的id,如果没有给出,则要迭代的对象必须支持Identifiable。迭代参数是每一个对象。
效果:

注意到我们有两条"123",正确区分了,所以".self"明显不是数据的内容。作为基本数据显示,我们现在并不关心每条数据如何唯一标识,不过既然系统做了如此约束,后面应该是有大用处的。
定义数据模型Model
现代编程语言都喜欢把数据结构称为"Model"并集中存放在一个目录下,但我仍然习惯把结构和功能放在一起(当然不一定是一个文件,但至少放在同一个目录下),在你写的项目足够高大上以前,你的代码也没必要那么高大上。
Identifiable协议
用做基础的List的来源数据(实际上是ForEach的来源数据),唯一的要求是符合Identifiable协议。这个协议唯一的要求要有id属性来做唯一标识。如果不明确定义id属性,Identifiable可以默认生成一个,默认生成的其实就是对象的地址(指针?嗯)。
示例
Swift
struct Data: Identifiable {
var id: UUID = UUID()
var i: Int
var name: String
}
//in view
//注意此时@State是不需要的,因为还不涉及到双向绑定
@State var datas: [Data] = [
Data(i:1,name: "magnifyingglass"), Data(i:2,name: "globe.americas"),
Data(i:3,name: "sun.rain.circle"),
]
//in body
List {
ForEach(datas) {
item in
HStack{
Text("\(item.i)")
Image(systemName: item.name)
}
}
}
效果:

UUID是全球唯一标识符,生成方式可以保证同一个程序同一次运行生成的肯定是唯一的,不刻意捣鬼的话不同计算机生成的发生碰撞的可能性也是可以忽略的。
注意我们在ForEach里根据数据内容生成了Text和Image,用HStack组合起来。如果不组合起来,对List来说,就会生成六条数据,这样,在我们后面操作移动、删除的时候,传递的List的序号和数据如何对应就成了问题。
拖动排序和删除
支持拖动排序和删除的并不是List,而是ForEach!所以我们需要给ForEach加上排序和删除动作,而不是给List加:
Swift
List {
ForEach(datas) {
item in
HStack{
Text("\(item.i)")
Image(systemName: item.name)
}
}
.onMove(perform: { IndexSet, Int in
info = "\(IndexSet.first ?? -1) \(IndexSet.last ?? -1) to:\(Int)"
datas.move(fromOffsets: IndexSet, toOffset: Int)
})
.onDelete(perform: { IndexSet in
datas.remove(atOffsets: IndexSet)
})
}
两个动作也可以写成函数:
Swift
func moveItem(form source: IndexSet, to destination: Int)
{
}
func deleteItem(at offsets:IndexSet)
{
}
里面的代码差不多,注意参数名不一样,直接写的代码用的参数名就是参数类型(写代码的时候双击perform后面的提示自动生成的)。
info是我额外增加的用来显示参数的视图:
Swift
@State var info : String = "info"
//in view
VStack
{
List {
......
}
Text(info)
}
根据显示的内容,可以明白:索引值是基于0的,拖放到第一个时插入位置是0,拖放到最后一个时插入位置时最后一个+1(三条数据,0,1,2,把第一条拖到最后,插入位置是3)。
删除是滑动删除:

注意,预览功能不全
我的电脑上预览界面无法执行拖动,实体机上正常。
修饰
样式.listStyle
注意这是List的修饰,不是ForEach的。
.automatic
默认样式
.grouped

没有圆角外包了,但上下空白还是有的。
.inset

没有上下空白了(红色是我设置的总VStack的背景色)。
.insetGrouped

这......好像又回到默认了啊。
.plain

注意关键区别,不仅没有圆角边界,连下面多余的空白都没有了。
但是,不要以为List变小了!你看到的效果仅仅是因为List没有绘制背景,从而把外部的红色背景透过来了!加上背景色看看(.background(.blue)):

更晕了,原来List的背景色和数据项的背景色是无关的。而且,这样设置背景色的方法仅适用于.plain,对别的样式是无效的。
.sidebar
看起来又回到默认了。
去掉列表项分隔线
分隔线属于ForEach而不是List,所以要给ForEach加:
Swift
ForEach(datas) {
。。。。。。
}
.onMove(perform: { IndexSet, Int in
。。。。。。
})
.onDelete(perform: { IndexSet in
。。。。。。
})
.listRowSeparator(.hidden)//去掉分隔线
设置列表项背景色
列表项背景色也属于ForEach。
我们可以看到默认的列表项背景色是比List的背景色要亮一些的,我们可以设置为与背景色一致:
Swift
//ForEach的修饰
.listRowBackground(Color.clear)

当然也可以指定颜色:
Swift
.listRowBackground(Color.green)

设置列表背景色
前面已经说了,直接设置.background仅对.plain风格有效,其他风格必须配合.scrollContentBackground(.hidden)才行:
Swift
.scrollContentBackground(.hidden)
.background(.blue)
效果:

背景色的特殊之处
列表的背景色会占据相邻的安全区,不影响边框
不需要设置,顶部的那个列表直接占据了顶部安全区:
Swift
var body: some View {
VStack {
List {
Text("123")
Image(systemName: "magnifyingglass")
Text("123")
}
.scrollContentBackground(.hidden) //取消默认背景才能让background生效
.background(.blue) //除了.listStyle(.plain)都必须有上一句配合
List {
ForEach(items.indices, id: \.self) {
item in
Text(items[item])
}
}
List {
ForEach(datas) {
。。。。。。
}
.onMove(perform: { IndexSet, Int in
。。。。。。
})
.onDelete(perform: { IndexSet in
。。。。。。
})
.listRowSeparator(.hidden)
.listRowBackground(Color.green)
}
.scrollContentBackground(.hidden) //取消默认背景才能让background生效
.background(.blue) //除了.listStyle(.plain)都必须有上一句配合
Text(info)
}
.border(.black)
.background(.red)
}
代码中有三个列表,效果:

有点困惑,简单说,VStack和List的背景色都会占据相邻的安全区,Text不会。因此上面的代码中顶部List的背景色铺到了顶部安全区,VStack的背景色则铺满了底部。但它们的边界都不包含安全区。如果在第一个List之前加一个Text,则VStack占据所有的安全区。
所以特殊性仅仅局限于背景色。