在本章中,你将学会如何使用循环遍历的方式创建一个列表,并可自定义参数动态生成列表。
如果你接触过UIKit的话,应该会用过tableView组件创建列表,我们生活中用的很多App基本都是列表的形式。
例如掘金的信息列表、手机系统设置、音乐列表等等……
基本上大多数App,特别是资讯类App都少不了列表的存在。
而在SwiftUI中,我们用List代替了原有tableView,使得代码更加简洁易懂。
由于List组件在不同场景下的应用不同,本章节分成4个部分讲解。
1、简单文字列表;
2、图片+文字列表;
3、列表数据整合;
4、Identifiable协议的使用;
第一部分:简单文字列表
首先,我们先创建一个新项目,命名为SwiftUIList。
我们在ContentView.swift文件中,创建一个简单的列表。
List的构造方式和之前学习的VStack很类似,将内容包裹在里面形成列表。
struct ContentView: View { var body: some View { //简单的列表 List { Text(“第1页") Text(“第2页") Text(“第3页") Text(“第4页") } } }
我们看到List里面都是Text文本,而且只是内容不同。
这时候,我们可以使用ForEach的方式把代码抽离出来,也就不需要写那么多相似的代码。
struct ContentView: View { var body: some View { // 简单的列表 List { ForEach(1 ... 4, id: \.self) { index in Text("第 \(index)页") } } } }
在使用ForEach遍历创建视图时,需要用id来标识内容,当里面的内容发现变化时,ForEach就可以自动更新UI。
简单来读一下代码内容:
我们传递给ForEach一个范围的值,用来循环遍历生成列表。
而它的id(标识符)被设置为值本身self,也就是前面设置的1、2、3、4。
然后用index参数存储循环的值。
我们在这里遍历了4次,每一次展示一个Text,Text里面的文字是“第”+{index}+“页”,index的参数值从1~4;
这样,我们就得到了一个列表。
当然,还有更简单的遍历方法。
struct ContentView: View { var body: some View { // 简单的列表 List { ForEach(1 ... 4, id: \.self) { Text("第 \($0)页") } } } }
在这里,我们省略索引参数index,而使用简化的$0,它引用闭包的第一个参数,直接将数据集合传递给List。
这样,也可以达到列表的效果,而且使得代码更加简单。
第二部分:图片+文字列表
好,下面进阶一下,我们尝试完成下面的UI设计稿。
首先分析下它的结构。
一个列表里,有Image、Text,他们是横向HStack排布。
我们先在Assets.xcassets导入我们所需的图片。
并且我们已经提前给图片命好名了,方便我们接下来使用它们。
我们回到ContentView.swift文件中,创建2个数组,存放我们的图片和文字。
//定义数组,存放数据 var myImages = ["weixin","weibo","qq","phone","mail"] var myNames = ["这是微信","这是微博","这是QQ","这是电话","这是邮箱"]
我们看到报错了,但又没有完全报错。
这是因为我们定义了一个动态数组,但在代码中没有用到,所以系统告诉我们定义的数组名称没有被使用罢了。
没事的,我们继续。
我们在body里面创建我们需要的代码。
struct ContentView: View { //定义数组,存放数据 var myImages = ["weixin","weibo","qq","phone","mail"] var myNames = ["这是微信","这是微博","这是QQ","这是电话","这是邮箱"] var body: some View { // 列表 List(myImages.indices, id: \.self) { index in HStack { Image(self.myImages[index]) .resizable() .frame(width: 40, height: 40) .cornerRadius(5) Text(self.myNames[index]) } } } }
我们还是构建了一个列表,不过使用myImages作为目录,也就是myImages.indices。
然后图片遍历用myImages数组,文字遍历用myNames数组。
是不是很简单,比起以前用UIKit的时候,要给cell协议和声明,SwiftUI几行代码就搞定了。
第三部分:列表数据整合
下面,我们再进阶一下。
上面的代码中,我们发现如果是图片+文字,那么我们创建了2个数组,如果是更加复杂的场景,我们岂不是要建立一堆的数组数据?
不不不,这肯定不够优雅。
最好的方式应该是,无论我们多少数组数据,我们都用一个数组包裹住。
我们回到UI稿中。
有没有办法,把Image和Text定义出来,然后Image和Text是一个数组?
有的!这时候,我们需要创建一个结构体,叫做Message,并定义好里面的变量。
代码如下:
struct Message { var name: String var image: String }
使用这个Message结构体,我们将原来的myImages、myNames数组组合成一个数组。
我们定义了一个数组Messages,它里面的内容是Message结构体。
代码如下:
// 定义数组,存放数据 var Messages = [ Message(name: "这是微信", image: "weixin"), Message(name: "这是微博", image: "weibo"), Message(name: "这是QQ", image: "qq"), Message(name: "这是电话", image: "phone"), Message(name: "这是邮箱", image: "mail") ]
构建好了以后,我们回到body里面,把里面的引用的参数值换成数组Messages,使用结构体Message遍历数据,List中使用image属性作为唯一的标识符。
同时,要把里面Image的参数引用结构体Message的image参数,Text引用结构体Message的name参数。
代码如下:
// 列表 List(Messages, id: \.image) { Message in HStack { Image(Message.image) .resizable() .frame(width: 40, height: 40) .cornerRadius(5) Text(Message.name) } }
那么,最终的结果和我们之前做的效果是一样的。
唯一不一样的是,我们的代码看起来优雅多了。
小结一下:
我们创建了一个结构体Message,它定义了2个变量,1个是image图片,是Strring类型,另一个是name名称,也是Strring类型。
然后我们定义个一个数组Messages(注意加了S),这个数组里面是结构体Message,并赋予了结构体里面2个变量的值;
然后在在body主代码块List里面,用Messages数据作为引用值数据,然后用结构体Message遍历数据。
最后把里面的Image和Text中的值换成结构体中变量的值。
第四部分:Identifiable协议的使用
完成第三部分以后,我们的代码List就完美了么?
并不!
因为这个:
List(Messages, id: \.image)
我们使用了image为Messages数组的唯一标识符,也就是List是通过image的唯一性找到对应数组的数据。
这样,我们会面临一个问题,如果我有2个图片是一样的,但是它的name不一样。
我们尝试把Messages数组的数据换成下面这样:
// 定义数组,存放数据 var Messages = [ Message(name: "这是微信", image: "weixin"), Message(name: "这是第二个微信号", image: "weixin") ]
我们发现,如果我们使用image作为id,也就是唯一标识符的话。
如果我的数组里有2个image,如果它们的值相同,那么SwiftUI会认为这两个是同一个东西。
也就是,我都是图片都是“微信”,但名称不一样,但计算机认为两个都是微信,而且值一样,这是因为image作为id是唯一的。
这时候,我们该怎么办?
我们期望的结果是,Messages数组里,每一个结构体的数据都是唯一的。
那么我们就不能在body构建唯一的标识符,应该在传值之前就在Message结构体里构建唯一的id。
struct Message { var id = UUID() var image: String var name: String }
在代码中,我们添加了id属性,并用唯一标识符初始化它。
UUID()函数用于生成一个全局惟一的随机标识符。
UUID由128位数字组成,因此从理论上讲,拥有两个相同标识符的可能性几乎为零。
然后,我们把body代码中的id,引用Message结构体的id。
// 列表 List(Messages, id: \.id) { Message in HStack { Image(Message.image) .resizable() .frame(width: 40, height: 40) .cornerRadius(5) Text(Message.name) } }
这样,我们就完成了List数据源的唯一。
再科普一个知识点。
我们还可以设置结构体Message遵循Identifiable协议。
这样,遵循Identifiable的结构体就可以自动跟踪它的id作为唯一标识符,我们也就不需要在body中指定id了。
完整代码如下:
import SwiftUI struct ContentView: View { // 定义数组,存放数据 var Messages = [ Message(image: "weixin", name: "这是微信"), Message(image: "weixin", name: "我的第二个微信号") ] var body: some View { // 列表 List(Messages) { Message in HStack { Image(Message.image) .resizable() .frame(width: 40, height: 40) .cornerRadius(5) Text(Message.name) } } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } struct Message: Identifiable { var id = UUID() var image: String var name: String }
恭喜你!完成了List的学习!