关于列表中的具体每一行的设置,请查看:ListRow
List
是滚动视图,它是显示垂直滚动数据的最有效方式List
列表中同样可以用 section
进行分区块展示Form
实际上是 List
的一种特殊类型;List
与 Form
几乎相同,只是它仅用于呈现数据而不是辅助用户输入ScrollView
中也可以显示数据,但在内存或性能方面不会像 List
列表视图那样高效// List 列表可以用来放置静态数据
List {
Text("Line One")
Text("Line Two")
Button("Click Here", action: {})
.foregroundColor(.green)
HStack {
Text("Centered Text")
}
}
// List 列表可以添加 Section 区分不同的部分,并且可添加页眉、页脚视图
List {
Section(header: Text("Other tasks"), footer: Text("End")) {
Text("Row 1")
Text("Row 2")
Text("Row 3")
}
Section {
// ...
} header: {
ListSectionTitle2View(text: "Appearance")
}
}
List
和 Form
使用方法非常相似,但 List
可以做而 Form
不能做的一件事是:完全脱离 ForEach
直接生成动态内容行List
和 ForEach
的创建都需要两个参数,第一个参数是:提供一组资料集合,或一个范围.self
,代表用具体的值本身(即1、2、3),作为每行的唯一标识// 结合 ForEach 创建动态数据内容
List {
// 第1个参数: 传给ForEach一个范围值,参数index也是从1到4
// 第2个参数: ID识别码设定为前面的值本身
ForEach(1...4, id: \\.self){ index in
Text("Item \\(index)")
}
}
// 脱离 ForEach 直接创建动态数据内容
// 1.去掉了ForEach,直接把参数写到List上
// 2.省略index参数名称,使用缩写$0来代替($0表示闭包第1个参数)
List(1...4, id: \\.self){
Text("Item \\($0)")
}
}
由于 List
能够同时包含静态和动态内容,就可以利用它创建类似 Wi-Fi 设置界面的内容:一个用于启用 Wi-Fi 的开关、一个展示附近网络的动态列表、再加上一些自动加入热点选项等更静态的内容等。
// List有趣的是可以将静态数据和动态相结合
List {
Text("Static row 1")
Text("Static row 2")
ForEach(0..<5) {
Text("Dynamic row \\($0)")
}
Text("Static row 3")
Text("Static row 4")
}
// 结合上 Section 使用让代码可读性更强
List {
Section("Section 1") {
Text("Static row 1")
Text("Static row 2")
}
Section("Section 2") {
ForEach(0..<5) {
Text("Dynamic row \\($0)")
}
}
Section("Section 3") {
Text("Static row 3")
Text("Static row 4")
}
}
id
参数的作用,它告诉 SwiftUI 数组中每个项目的唯一标识是什么。该参数在 List
和 ForEach
中的使用方式是一样的,具体参见:参数:ID[2, 4, 6, 8, 10]
,那么这些数字本身就是唯一标识符。毕竟,我们没有其他元素可以用。当处理这种列表数据时,就使用 id: \\.self
。// 例如:有两个数组
var resNames = []
var resImages = []
// 首先:传入给List的不是固定范围,而是一个数组,但是加了.indices;indices是一个属性,它用于获取数组索引范围(Range)的一种便捷方式。具体来说,它返回一个表示数组索引范围的,可以用于遍历数组的索引。 返回的是从0到数组的元素个数减1的一个范围,通常用于循环遍历数组的索引。所以后面的 index 就是从 indices 里循环取值,自然取的也是1、2、3...这样的整数
// 然后:.self 代表用resName中各个元素的字符串本身,作为唯一标识
List(resNames.indices, id: \\.self){ index in
Image(self.resImages[index])
Text(self.resNames[index])
}
当遇到上面同一个事物的两个属性,分成两个数组存储的情况,更好的方式是建立数据模型进行存储。因此可以建立一个struct。有个问题是如果用 name 属性作为唯一标识,如果发生两个餐厅重名的情况,它们的图片也会一样,这是不希望发生的。因此应该给每个餐厅一个唯一的标识,而不是用名字。关于使用 UUID 作为唯一标识的说明,具体参见:参数:ID
// 1: 让餐厅结构遵循 Identifiable 协议,该协议只有一个要求,即该类型必须具备某种ID作为唯一识别码
// 2: 因此给每个餐厅生成一个唯一的ID,用UUID函数产生通用的唯一标识。
struct Restaurant: Identifiable {
var id = UUID()
var name: String
var image: String
}
// 构建数组
var restaurants = [
Restaurant(name:"xxxx", image:"xxxxxx")
Restaurant(name:"xxxx", image:"xxxxxx")
Restaurant(name:"xxxx", image:"xxxxxx")
......
]
// 读取。这时传入 List 的不是一个范围,而是一个数组,并且用它内部的 id 属性作为唯一标识
List(restaurants, id: \\.id){ restaurant in
// 传入的是数组,所以循环的是单个数组内的元素
// 所以取值是也是单个元素的某属性,不需要索引值
Image(restaurant.image)
Text(restaurant.name)
}
}
可展开的 List
视图指的是,列表显示一级视图,并且在一级视图旁边有一个可点击的箭头,点击后列表会展开以显示其里面的子元素。要使用这种形式的 List
,需要具有精确形式的数据:数据模型应该有一个可选的相同类型的子项数组,以便可以创建一棵树。
一旦数据就位,您可以通过传入【数据数组】以及【子级所在位置的键路径】(例子中为 \\.items
)将其加载到列表中。然后,您将获得一个常规闭包,您可以在其中提供行数据,就像平常一样。
// 例如有以下数据
struct Bookmark: Identifiable {
let id = UUID()
let name: String
let icon: String
var items: [Bookmark]?
// some example websites
static let apple = Bookmark(name: "Apple", icon: "1.circle")
static let bbc = Bookmark(name: "BBC", icon: "square.and.pencil")
static let swift = Bookmark(name: "Swift", icon: "bolt.fill")
static let twitter = Bookmark(name: "Twitter", icon: "mic")
// some example groups
static let example1 = Bookmark(name: "Favorites", icon: "star", items: [Bookmark.apple, Bookmark.bbc, Bookmark.swift, Bookmark.twitter])
static let example2 = Bookmark(name: "Recent", icon: "timer", items: [Bookmark.apple, Bookmark.bbc, Bookmark.swift, Bookmark.twitter])
static let example3 = Bookmark(name: "Recommended", icon: "hand.thumbsup", items: [Bookmark.apple, Bookmark.bbc, Bookmark.swift, Bookmark.twitter])
}
struct ContentView: View {
let items: [Bookmark] = [.example1, .example2, .example3]
var body: some View {
List(items, children: \\.items) { row in
HStack {
Image(systemName: row.icon)
Text(row.name)
}
}
}
}
SwiftUI 允许我们直接从绑定创建 List
或 ForEach
,然后可以实现后续的内容闭包,与显示的数据集合中的每个单独元素进行绑定。当每个项目视图需要与某些数据绑定时(例如具有用于编辑用户名的文本字段的列表行),这非常有用。
要使用它,请将绑定直接传递到列表中,例如 $users
,然后接受内容闭包中的绑定,例如 $user
。例如,在此代码中,我们显示用户列表,并向每行添加 Toggle
以确定是否已联系他们:
struct User: Identifiable {
let id = UUID()
var name: String
var isContacted = false
}
struct ContentView: View {
@State private var users = [
User(name: "Taylor"),
User(name: "Justin"),
User(name: "Adele")
]
var body: some View {
List($users) { $user in
Text(user.name)
Spacer()
Toggle("User has been contacted", isOn: $user.isContacted)
}
}
}
// 以这种方式使用绑定是修改列表的最有效方法,因为当只有单个项目更改时,它不会导致整个视图重新加载。
如果想获得混合效果的列表(例如列表的第一项用了特殊样式),可以采用加额外判断的方式实现。
List(restaurants.indices) { index in
if (0...1).contains(index) {
FullImageRow(restaurant: self.restaurants[index])
} else {
BasicImageRow(restaurant: self.restaurants[index])
}
}
如果想以编程方式使 SwiftUI 的 List
滚动到特定行,应该将其嵌入到 ScrollViewReader
中。这在其代理上提供了一个 scrollTo()
方法,只需提供其 ID 和可选的锚点即可移动到列表内的任何行。如果在 withAnimation()
内调用 scrollTo()
,滑动还会有动画。
具体参照:ScrollViewReader
refreshable()
修饰符可将此功能附加到 List
,以便在用户向下拖动足够远时触发。只要代码完成运行,iOS 就会自动显示活动指示器。
struct ContentView: View {
var body: some View {
NavigationStack {
List(1..<100) { row in
Text("Row \\(row)")
}
.refreshable {
print("Do your refresh work here")
}
}
}
}
放置在 refreshable()
中的代码,会在异步任务中运行,因此这里是放置加载网络内容的完美位置。例如下面的示例,它使用 pull 刷新将一些新闻报道下载到 List
中:
struct NewsItem: Decodable, Identifiable {
let id: Int
let title: String
let strap: String
}
struct ContentView: View {
@State private var news = [
NewsItem(id: 0, title: "Want the latest news?", strap: "Pull to refresh!")
]
var body: some View {
NavigationStack {
List(news) { item in
VStack(alignment: .leading) {
Text(item.title)
.font(.headline)
Text(item.strap)
.foregroundStyle(.secondary)
}
}
.refreshable {
do {
// Fetch and decode JSON into news items
let url = URL(string: "<https://www.hackingwithswift.com/samples/news-1.json>")!
let (data, _) = try await URLSession.shared.data(from: url)
news = try JSONDecoder().decode([NewsItem].self, from: data)
} catch {
// Something went wrong; clear the news
news = []
}
}
}
}
}
以下修饰符是附加到 List
视图后面的:
修饰符代码示例 | 说明 | |
---|---|---|
设置列表样式 | .listStyle(.plain) | 支持样式有:.sidebar .grouped .plain .insetGrouped .inset |
设置列表 每行的间距 | .listRowSpacing(20) | |
设置列表 section 的间距 | .listSectionSpacing(30) | |
设置下拉刷新闭包 | .refreshable { … } | |