![f7abcc779c217fb39bff687beaefd543.png](https://img-blog.csdnimg.cn/img_convert/f7abcc779c217fb39bff687beaefd543.png)
SwiftUI List (1) —— 基本用法
SwiftUI List (2) —— 定制化显示
三、List、Form、VStack+ScrollView 之间的比较
苹果对List的基本定义是在单列中排列显示数据行的容器。能够符合这个定义的其实还有Form,和 VStack+ScrollView的组合。在不少场景下,这三者其实都是能够符合使用要求的,不过他们也有各自的特点,及更加适合的用途。
(1)List VS Form
List 和 Form 其实都是SwiftUI对UITableView的一个封装,他们在内部实现的机理基本上是一样的。尤其在List使用 GroupListStyle的情况下,其视觉呈现也基本一致。不过苹果还是给了他们不同的定位,因此无论是在功能还是视觉呈现上的细节仍有值得研究的必要。
功能:
由于构造的不同,List上可以直接使用快捷数据源方式,不过Form在配合ForEach的情况下,该区别基本可以忽略。
基于ForEach的DynamicViewContent因素,Form和List都可以实现对列表中的数据进行删除、移动,在PadOS下也同样都支持onInsert。不过由于Form不支持绑定Selection,因此Form无法实现选择功能,这也是两者功能上最大的一点区别。
![a2dbd0f3b4674de806d947b0529cc6af.png](https://img-blog.csdnimg.cn/img_convert/a2dbd0f3b4674de806d947b0529cc6af.png)
视觉:
List有多种风格,Form只有一种。Form和List一样,同样可以使用上节介绍的定制属性来进行定制化显示。不过可能苹果考虑到两者的定位,在其中一个关键的地方作出了明显的区别。
在Form中内嵌List,Form会将List中的内容直接展开。这种设计应该是充分考虑了Form的适用场景而做出的特别设定。
struct ListForm: View {
var body: some View {
VStack{
Form{
Text("选项一")
Section(header:Text("自动展开的List")){
List{
Text("List中的选项一")
Text("List中的选项二")
}
}
}
}
}
}
![de96c11d8269271346595fd698a75449.png](https://img-blog.csdnimg.cn/img_convert/de96c11d8269271346595fd698a75449.png)
除了此种情况外,如果在List中嵌入Form或List,再或者在Form中嵌入Form,嵌入的全部内容都会被当做一个Row来看待。
将上述代码中Form替换成List后效果如下:
![3d51b9f7ac1fa65a96eabfea9a94b7a7.png](https://img-blog.csdnimg.cn/img_convert/3d51b9f7ac1fa65a96eabfea9a94b7a7.png)
这两种显示情况无所谓好坏,可以根据实际的需要灵活选择。
除了上述的几点外,List和Form不愧为同胞兄弟。
(2)List VS VStack + ScrollView
本文写作时,SwiftUI2.0尚未发布。在SwiftUI2.0中,苹果通过LazyVStack、LazyHStack等Lazy特性,解决了下面的问题。
在数据量不太大,并且没有太多其他功能要求的情况下,VStack+ScrollView同样可以实现List的功能,而且由于不受UITableView的一些限制,在布局及视觉呈现上有更高的灵活性。
VStack+ScrollView无法享受List中ForEach提供的onMove,onDelete等功能,只能自己手动编程来提供上述功能。
不过相较于功能上的不同,VStack 同 List(或Form)最大的区别还是在效率方面。
由于List和Form是通过UITableView来完成Render的,因而很好的支持了Lazy特性,而VStack需要将所有的数据都Render后才能进行显示,这使得List在大量的数据呈现上有着绝对的优势。
import SwiftUI
struct VStackVSLIstVSForm: View {
var number = 3000
var body: some View {
NavigationView{
List{
NavigationLink("List",destination: ListSpeedTest(number: number))
NavigationLink("Form",destination: FormSpeedTest(number: number))
NavigationLink("VStack + ScrollView",destination: VStackSpeedTest(number: number))
}
.navigationBarTitle("数据集大小:(number)",displayMode: .inline)
}
}
}
fileprivate struct ListSpeedTest:View{
var number:Int
var body:some View{
List{
ForEach(0..<number){ i in
Text("ID:(i)").padding().background(LinearGradient(gradient: Gradient(colors: [.red,.yellow,.purple,.blue]), startPoint: .zero, endPoint: .bottomTrailing))
}
}
}
}
fileprivate struct VStackSpeedTest:View{
var number:Int
var body:some View{
ScrollView{
VStack{
ForEach(0..<number){ i in
Text("ID:(i)").padding().background(LinearGradient(gradient: Gradient(colors: [.red,.yellow,.purple,.blue]), startPoint: .zero, endPoint: .bottomTrailing))
}
}
}
}
}
fileprivate struct FormSpeedTest:View{
var number:Int
var body:some View{
Form{
ForEach(0..<number){ i in
Text("ID:(i)").padding().background(LinearGradient(gradient: Gradient(colors: [.red,.yellow,.purple,.blue]), startPoint: .zero, endPoint: .bottomTrailing))
}
}
}
}
上述代码都是显示3000条数据,通过视频可以看出两者间的巨大差距。
![4dd00e1ed63ca88c57a90bac78a1fbea.png](https://img-blog.csdnimg.cn/img_convert/4dd00e1ed63ca88c57a90bac78a1fbea.png)
如果你的数据量较大,即使牺牲一定的显示控制力也应该首选List或Form。
(3)List动态显示上的性能上的一点小问题
List对数据的显示效率极高,而且还支持数据变化的动态显示效果,在程序对数据进行删减、增加、排序等情况下都会提供美观的动态显示。不过恰恰是由于这种动态的显示效果,在某些特殊的情况下,会产生非常严重的效率问题。
struct ListDynamic: View {
@State var list = (0...500).map{$0}
@State var id = UUID()
var body: some View {
VStack{
Text("数据量:500")
List(list,id:.self){ i in
Text("(i)")
}.id(self.id)
HStack{
Button("shuffle"){
self.list.shuffle()
}.frame(maxWidth:.infinity)
Button("Shuffle with new ID"){
self.list.shuffle()
self.id = UUID()
}.frame(maxWidth:.infinity)
}.padding(.bottom,20)
}
}
}
![946a6206144fca1f553a24e052d9915b.png](https://img-blog.csdnimg.cn/img_convert/946a6206144fca1f553a24e052d9915b.png)
可以看出来,在了数据量较小的情况下,list可以展示出很好的数据动态转换效果,但当数据量增大后,可能由于内部算法的效率问题,动态转换的计算反倒阻滞了整个app。由于目前没有方法可能禁止List的animation,所以暂时只能通过使用.id(hash:Value)的方式强制重绘来解决以上的问题。希望将来内部算法上能够有所改善。
在基本掌握了List的用法后,在下一篇我们来完成最初设定的目标,仿制一个Mail程序的首页。
更新:
对于该如何在List中使用ForEach的问题,我的另一篇文章有提及
东坡肘子:聊一下SwiftUI中的List和ForEachzhuanlan.zhihu.com![525c23d6e0afea2d3b983301b40ccc52.png](https://img-blog.csdnimg.cn/img_convert/525c23d6e0afea2d3b983301b40ccc52.png)
看下篇:
东坡肘子:SwiftUI List (4) —— 仿制Mail首页zhuanlan.zhihu.com![bb99adfec0aa46f00e4c55d49fb4644a.png](https://img-blog.csdnimg.cn/img_convert/bb99adfec0aa46f00e4c55d49fb4644a.png)