List
List 为 SwiftUI 中的列表,类似于 UIKit 中的 UITableView,List 中的每一项类似于 UITableViewCell。由于 List 比较复杂,故本文我们通篇只讨论 List 这一种控件。
静态 List
用 List 做静态列表非常容易,使用类似 VStack 的写法即可。
struct ContentView: View {
var body: some View {
List {
Text("Hello SwiftUI")
Image(systemName: "clock")
Text("Hello SwiftUI")
}
}
}
分组
类似 UITableView,我们也可以对 List 进行分组显示。
struct ContentView: View {
var body: some View {
List {
Section(header: Text("UIKit"), footer: Text("表格")) {
Text("UITableView")
}
Section(header: Text("SwiftUI"), footer: Text("列表")) {
Text("List")
}
}
}
}
每个分组由 header+body+footer 组成。
在先前写 UITableView 时我们常常会用到 Grouped 样式,这种样式的实现非常简单,在 List 后面加一个 .listStyle(GroupedListStyle()) 修饰符即可。
struct ContentView: View {
var body: some View {
List {
Section(header: Text("UIKit"), footer: Text("表格")) {
Text("UITableView")
}
Section(header: Text("SwiftUI"), footer: Text("列表")) {
Text("List")
}
}.listStyle(GroupedListStyle())
}
}
在 iOS 13.2 之后,新增了一个 InsetGrouped 样式,需要我们在分组后强制使用 .environment(\.horizontalSizeClass, .regular)。
struct ContentView: View {
var body: some View {
List {
Section(header: Text("UIKit"), footer: Text("表格")) {
Text("UITableView")
}
Section(header: Text("SwiftUI"), footer: Text("列表")) {
Text("List")
}
}.listStyle(GroupedListStyle())
.environment(\.horizontalSizeClass, .regular)
}
}
如果需要修改分组的背景色,直接设置 .background 即可。
struct ContentView: View {
var body: some View {
List {
Section(header:
//内容宽度
HStack {
Text("Header")
.font(.headline)
.foregroundColor(.white)
.padding()
Spacer()
}
.background(Color.orange) //修改分组背景色
//边距为0
.listRowInsets(
EdgeInsets(
top: 0,
leading: 0,
bottom: 0,
trailing: 0))
) {
Text("1")
Text("2")
Text("3")
Text("4")
Text("5")
Text("6")
}
}
}
}
背景
修改每一行的背景色也十分容易。
struct ContentView: View {
var body: some View {
List {
ForEach(0..<10) {
Text("Row \($0)")
}
//设置背景,可以是颜色或者形状
.listRowBackground(Color.red)
}
}
}
填充
假如我们要增加行高该怎么做呢?我们可以 listRowInsets 来设置周边填充大小,就能间接增加行高了。
struct ContentView: View {
var body: some View {
List {
ForEach(0..<10) {
Text("Row \($0)")
}
//设置周边填充大小
.listRowInsets(EdgeInsets(top: 30, leading: 60, bottom: 30, trailing: 0))
}
}
}
.listRowInsets应当直接设置在行的内容上而不是设置在 List 上。
分割线
目前来说,每行之间的分割线的操作还是要借助 UITableView。
struct ContentView: View {
var body: some View {
List(0..<10) {
Text("Row \($0)")
}.onAppear { UITableView.appearance().separatorStyle = .none } //去除分割线
}
}
struct ContentView: View {
var body: some View {
List(0..<10) {
Text("Row \($0)")
}.onAppear { UITableView.appearance().separatorColor = .red } //设置分割线颜色
}
}
struct ContentView: View {
var body: some View {
List(0..<10) {
Text("Row \($0)")
}.onAppear { UITableView.appearance().tableFooterView = UIView() } //去除多余行
}
}
动态 List
动态 List 的构建只需要直接遍历数组即可,List 的数据源必须实现 Identifiable 协议,表示数据需要的唯一标识,一般直接设置 id 即可。
struct User: Identifiable {
var id = UUID()
var name: String
}
struct ContentView: View {
//数据源
let users = [User(name:"ZhangSan"), User(name:"LiSi"), User(name:"WangWu")]
var body: some View {
//构造列表
List(users) { user in
Text(user.name)
}
}
}
同样我们可以用 ForEach 方式来构建 List,需要传一个 KeyPath 参数表示唯一标识,一般传递 \.self 即可。
struct ContentView: View {
let names = ["zhangsan", "list", "wangwu"]
var body: some View {
List{
ForEach(names, id: \.self) { name in
Text(name)
}
}
}
}
在之前的 UIKit 中,我们常常会在 UITableView 中自定义 cell。下面我们通过一个复杂一些的案例来演示如何在 List 中自定义 cell。
struct ContentView: View {
//数据源
let titles = ["2020.8.17", "2020.8.18","2020.8.19","2020.8.20","2020.8.21","2020.8.22","2020.8.23"]
let subtitles = ["周一", "周二","周三","周四","周五","周六","周日"]
let details = ["下雨", "晴天","有雾","多云","阴天","下雪","大风"]
var body: some View {
//遍历数据源构造Item
List(0..<self.titles.count) { item in
HStack {
VStack {
Text(self.titles[item])
.foregroundColor(.orange)
.bold()
.font(.title)
Text(self.subtitles[item])
.foregroundColor(.gray)
.font(.subheadline)
}.padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 50))
Text(self.details[item])
.foregroundColor(.blue)
.font(.largeTitle)
.italic()
}.padding()
}
}
}
编辑
与 UIKit 不同,SwiftUI 是严格的数据驱动框架,若我们要编辑 List 的内容只需要修改数据源。
增加
struct ContentView: View {
//数据源
@State var names = ["ZhangSan", "LiSi", "WangWu"]
var body: some View {
VStack{
Button("Add"){
//增加
self.names += ["LouYu", "ZhaoLiu"]
}
List{
ForEach(names, id: \.self) { name in
Text(name)
}
}
}
}
}
删除
struct ContentView: View {
//数据源
@State var names = ["ZhangSan", "LiSi", "WangWu"]
var body: some View {
List{
ForEach(names, id: \.self) { name in
Text(name)
}
.onDelete(perform: delete(at:)) //删除,此时左滑会出现删除按钮
}
}
//删除时执行的函数
func delete(at offsets: IndexSet) {
if let first = offsets.first {
names.remove(at: first)
}
}
}
删除也可以简写成如下形式。
struct ContentView: View {
//数据源
@State var names = ["ZhangSan", "LiSi", "WangWu"]
var body: some View {
List{
ForEach(names, id: \.self) { name in
Text(name)
}
.onDelete{ index in
self.names.remove(at: index.first!)
}
}
}
}
移动
struct ContentView: View {
//数据源
@State var names = ["ZhangSan", "LiSi", "WangWu"]
var body: some View {
NavigationView {
List{
ForEach(names, id: \.self) { name in
Text(name)
}
.onMove(perform: move(from:to:))
}
//EditButton,点击后进入编辑模式
.navigationBarItems(trailing: EditButton()) .navigationBarTitle("移动List")
}
}
//移动时执行的函数
func move(from: IndexSet, to: Int) {
names.move(fromOffsets: from, toOffset: to)
}
}
移动同样可以简写如下。
struct ContentView: View {
//数据源
@State var names = ["ZhangSan", "LiSi", "WangWu"]
var body: some View {
NavigationView {
List{
ForEach(names, id: \.self) { name in
Text(name)
}
.onMove{ (from, to) in
self.names.move(fromOffsets: from, toOffset: to)
}
}
.navigationBarItems(trailing: EditButton())
.navigationBarTitle("移动List")
}
}
}