优化 LazyVGrid 组件的性能
了解如何解决在使用 LazyVGrid 可能遇到的卡顿掉帧问题。

当将 LazyVGrid 组件嵌入一个 ScrollView 中时,在 LazyVGrid 的直接子组件上使用例如 .cornerRadius()
、.clipShape()
、.shadow()
等修饰器,可能会造成明显的滑动时卡顿问题。
截止 2025 年 3 月 21 日,我还不清楚产生卡顿的根本原因,但找到一些可以解决或缓解卡顿问题的方案,于此记录。
理解背景
在父组件中,我们使用一个 LazyVGrid 组件,并调用 MovieCardView()
组件:
let columns = [
GridItem(.adaptive(minimum: 160, maximum: 220), spacing: 12)
]
ScrollView{
LazyVGrid(columns: columns, spacing: 12) {
ForEach(bookmarkIds, id: \.self) { bookmarkId in
NavigationLink(value: bookmark) {
MovieCardView(bookmark: bookmark)
}
}
}
}
使用 .cornerRadius() 导致的卡顿
在 BookCardView 组件中,在最外层 VStack 上使用一个 .cornerRadius(12)
修饰器,这一行代码会导致父组件滑动明显卡顿(注释这行代码问题即消失):
import SwiftUI
struct BookCardView: View {
let bookmark: Bookmark
var body: some View {
VStack(alignment: .leading, spacing: 0) {
BookmarkImageView(
bookmark: bookmark,
imageIndex: 0
)
.aspectRatio(3 / 4, contentMode: .fit)
.frame(height: 240)
.clipped()
VStack(alignment: .leading, spacing: 8) {
Text(bookmark.content.title)
.font(.headline)
.fontWeight(.bold)
.foregroundStyle(.primary)
.lineLimit(2)
Text(bookmark.bookDetails.author.joined(separator: "、"))
.font(.subheadline)
.foregroundStyle(.secondary)
.lineLimit(1)
}
}
.cornerRadius(12)
}
}
解决方案一:使用 .drawingGroup() 修饰器提升渲染性能
通过在最外层添加 .drawingGroup()
修饰器,可以明显减少卡顿。
但.drawingGroup()
组件会导致.navigationTransition()
失效。
import SwiftUI
struct BookCardView: View {
let bookmark: Bookmark
var body: some View {
VStack(alignment: .leading, spacing: 0) {
BookmarkImageView(
bookmark: bookmark,
imageIndex: 0
)
.aspectRatio(3 / 4, contentMode: .fit)
.frame(height: 240)
.clipped()
VStack(alignment: .leading, spacing: 8) {
Text(bookmark.content.title)
.font(.headline)
.fontWeight(.bold)
.foregroundStyle(.primary)
.lineLimit(2)
Text(bookmark.bookDetails.author.joined(separator: "、"))
.font(.subheadline)
.foregroundStyle(.secondary)
.lineLimit(1)
}
}
.cornerRadius(12)
// 这个修饰器能显著提高性能
.drawingGroup()
}
}
解决方案二:将 .cornerRadius() 移动到子组件内部
避免在最外层使用 .cornerRadius() 修饰器,将 .cornerRadius()
移动到 BookCardView 内部,而不是最外层的 VStack 上。
import SwiftUI
struct BookCardView: View {
let bookmark: Bookmark
var body: some View {
VStack(alignment: .leading, spacing: 0) {
BookmarkImageView(
bookmark: bookmark,
imageIndex: 0
)
.aspectRatio(3 / 4, contentMode: .fit)
.frame(height: 240)
.clipped()
.cornerRadius(12)
VStack(alignment: .leading, spacing: 8) {
Text(bookmark.content.title)
.font(.headline)
.fontWeight(.bold)
.foregroundStyle(.primary)
.lineLimit(2)
Text(bookmark.bookDetails.author.joined(separator: "、"))
.font(.subheadline)
.foregroundStyle(.secondary)
.lineLimit(1)
}
.cornerRadius(12)
}
}
}
这样也能解决卡顿问题,但是为什么呢?不清楚!
在内部直接使用 .cornerRadius(),圆角可能会有一些细节问题,需要花点精力解决。
解决方案三:使用 GroupBox 组件来实现圆角
在外层使用 GroupBox 组件,GroupBox 组件自带圆角效果 —— 微信听书中大量使用这种效果,并且可以避免卡顿问题。
使用 .shadow() 导致的卡顿
在 LazyVGrid 的直接子组件中使用 .shadow 修饰器添加阴影,也会造成滑动时页面发生抖动。
解决方案参考上面 .cornerRadius() 部分。
其他人也有遇到该问题:
What is the proper way to add a shadow to LazyVGrid items? For every single item in the grid it has one of these optimization opportunities. This shows up when I open the view hierarchy.
by u/buddybudddy in SwiftUI
SwiftUI经常会有一些非常奇妙且难以解释的问题。
— 迅哥儿 (@bartfastt) May 11, 2024
比如今天我发现,如果你给一个LazyVGrid的每个cell的view直接使用shadow modifier增加一个大半径阴影的话,在列表滚动时屏幕会发出一种噪音,我只能猜测可能是这个modifier会大幅降低perfermance,导致Promotion进入了某种异常状态。