1.4 创建高性能的可滚动栈

原文链接: https://developer.apple.com/documentation/swiftui/creating-performant-scrollable-stacks

1.4 创建高性能的可滚动栈

借助滚动视图、栈视图和惰性栈,高效地显示大量重复视图。

概述

你的应用经常需要在容器视图中显示比设备屏幕可用空间更多的数据。水平和垂直栈是重复视图或视图组的好方案,但它们没有内置的滚动机制。你可以把栈包进 ScrollView 来加入滚动,并在出现性能问题时改用惰性栈。

在可滚动容器中显示视图组

实现重复视图或视图组可以很简单,只需把它们包在 ScrollView 内的 HStack 或 VStack 里。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
ScrollView(.horizontal) {
    HStack {
        ProfileView()
        ProfileView()
        ProfileView()
        ProfileView()
        ProfileView()
    }
}
.frame(maxWidth: 500)

如果上面示例代码中的 ProfileView 的固有内容尺寸是 200 x 200 点,那么 frame(minWidth:idealWidth:maxWidth:minHeight:idealHeight:maxHeight:alignment:) 视图修饰符施加在 ScrollView 上的 500 点最大宽度会让这个栈在其中滚动。

五个资料视图在一行中显示于栈视图内。滚动视图的最大宽度裁剪了其内容,使得栈中最后两个半资料视图落在视口之外。

关于使用栈对视图分组的入门介绍,请参阅 用栈视图构建布局。

为数据重复视图

使用 ForEach 为应用中的数据重复视图。对于 profiles 数组中的一份资料数据列表,使用 ForEach 在 HStack 中为数组的每个元素创建一个 ProfileView。

1
2
3
4
5
6
7
8
ScrollView(.horizontal) {
    HStack {
        ForEach(profiles) { profile in
            ProfileView(profile: profile)
        }
    }
}
.frame(maxWidth: 500)

注意:当你使用 ForEach 时,遍历的每个元素都必须具有唯一标识。要么让元素遵循 Identifiable 协议,要么把一个指向唯一标识符的键路径作为 init(_:id:content:) 的 id 参数传入。

视图数量很大时考虑惰性栈

三种标准栈视图 HStack、VStack 和 ZStack 在显示时都会加载其包含的整个视图层级,一次性加载大量视图会导致运行时性能变慢。

在上面的示例中,ProfileView 是一个由嵌套的栈视图、文本标签和一个图像视图组成的复合视图。一次性加载大量资料会造成明显的性能下降。

随着栈中视图数量的增加,可以考虑使用 LazyHStack 和 LazyVStack 代替 HStack 和 VStack。惰性栈按需加载并渲染其子视图,在加载大量子视图时能带来显著的性能提升。

示意图,展示滚动视图容器中的惰性栈视图。已在中间视口中加载的视图是可见的,尚未加载的视图则待命在右侧。

栈视图和惰性栈功能相似,看起来可以互换使用,但它们在不同场景下各有所长。栈视图会一次性加载其子视图,这使得布局既快速又可靠,因为系统在加载每个子视图时就知道它的尺寸和形状。惰性栈则以一定程度的布局正确性换取性能,因为系统只在子视图即将可见时才计算它们的几何信息。

在选择使用哪种栈视图时,始终先用标准栈视图;只有在剖析代码后发现改用惰性栈能带来值得的性能提升时,才换成惰性栈。

通过性能剖析找出问题

在考虑使用哪种栈时,请使用 Instruments 工具剖析你的应用,找出用户界面代码中在栈内加载大量视图的位置。

要剖析 SwiftUI 的视图加载,请从 Xcode 的 Product 菜单中选择 Profile 打开 Instruments 工具,然后选取 SwiftUI 剖析模板。这个模板会载入四个工具:View Body、View Properties、Core Animation Commits 和 Time Profiler。把这些工具结合使用,是寻找加速应用机会的良好起点。

注意:不要使用 iOS 模拟器剖析代码。性能测试务必使用真机。

Instruments 工具的截图,显示大量视图在同一时间加载。

在剖析上面的代码时,View Body 工具会显示有 1,000 个 ProfileView 实例与 HStack 同时载入内存。你还可以看到系统在加载每份资料时同样数量的 Image 视图被载入。

在这种情况下,解决办法就是把 HStack 替换为 LazyHStack,如下面代码所示:

1
2
3
4
5
6
7
8
ScrollView(.horizontal) {
    LazyHStack {
        ForEach(profiles) { profile in
            ProfileView(profile: profile)
        }
    }
}
.frame(maxWidth: 500)

再运行一次跟踪可以看出,初始加载的视图数量大幅减少,因为一开始只有四个 ProfileView 实例是可见的。你还能看到 Total Duration 一列相应地下降。

Instruments 工具的截图,显示只有少量视图在加载。

关于使用 Instruments 工具的更多信息,请参阅 提升应用的性能。