优化 LazyVGrid 组件的性能

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

优化 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() 部分。

0:00
/0:02

其他人也有遇到该问题:

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