使用 Text 加载长文本的卡顿问题

了解如何避免在使用 Text 组件时,因为加载长文本导致的卡顿问题。

使用 Text 加载长文本的卡顿问题

问题背景

在一个 ScrollView(.horizontal) 滑动组件中,使用 LazyHStack + ForEach 加载视图。

当横向滑动页面时,能感受到轻微但明显的掉帧。当使用 .scrollTargetBehavior() 添加滑动对齐行为后,卡顿会更加明显。

父组件

原因分析

通过控制变量实验,发现是因为在 BookmarkDetailView() 组件中,使用 Text 组件加载了一个长文本:

 Text(bookmark.bookDetails.intro)

某些对象 bookmark.bookDetails.intro 的值可能较长,超过 1000 个字,此时就会感觉到明显的滑动卡顿掉帧。

通过懒加载显示长文本

创建一个 lazyText 组件,通过该组件显示长文本。

  • 默认显示 8 行文字
  • 用户可以点击更多,在一个 sheet 窗口中显示完整内容。
import SwiftUI

struct LazyText: View {
    let text: String
    let title: String
    @State private var isShowingFullText = false
    let maxChars: Int

    init(_ text: String, title: String = "简介", maxChars: Int = 200) {
        self.text = text
        self.title = title
        self.maxChars = maxChars
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            if text.count > maxChars {
                Text(text.prefix(maxChars) + "...")
                    .overlay(
                        Text("更多")
                            .foregroundColor(.blue)
                            .padding(.leading, 4)
                            .background(Color(UIColor.systemBackground))
                            .alignmentGuide(.trailing) { d in d[.trailing] }
                            .frame(maxWidth: .infinity, alignment: .trailing),
                        alignment: .bottomTrailing
                    )
                    .contentShape(Rectangle())
                    .onTapGesture {
                        isShowingFullText = true
                    }
            } else {
                Text(text)
            }
        }
        .sheet(isPresented: $isShowingFullText) {
            NavigationStack {
                ScrollView {
                    Text(text)
                        .padding()
                }
                .navigationTitle(title)
                .navigationBarTitleDisplayMode(.inline)
                .toolbar {
                    ToolbarItem(placement: .topBarTrailing) {
                        Button("完成") {
                            isShowingFullText = false
                        }
                    }
                }
            }
        }
    }
}