SwiftUI 学习笔记(五):常见的 View 和 Modifier 解析(四)

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")
        }
    }
}

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注