You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI List使用自定义Cell时过度渲染问题排查与解决

SwiftUI List使用自定义视图触发全量渲染的原因及解决方法

问题重现

以下是简化后的测试代码:

import SwiftUI

struct ContentView: View {
    var body: some View {
        NavigationView {
            List(0..<1000, id: \.self) { index in
                let _ = print(index)
                Text("\(index)")        // 初始加载时约创建18个单元格
//                Cell(index: index)    // 初始加载时创建1000个单元格
            }
        }
    }
}

struct Cell: View {
    let index: Int
    var body: some View {
        Text("\(index)")
    }
}

#Preview {
    ContentView()
}

观察现象:

  • 直接使用Text("\(index)")时,符合List的懒加载特性,初始仅创建可见范围内的约18个单元格;
  • 切换为自定义视图Cell(index: index)时,会一次性创建全部1000个单元格,严重影响性能。

原因分析

SwiftUI的List懒加载生效的核心前提是:闭包返回的视图能被系统识别为可延迟初始化的轻量单元。

  • 直接返回Text时,SwiftUI内置了对这类基础视图的懒加载优化,能精准控制只创建可见范围内的实例;
  • 自定义Cell视图作为自定义结构体,默认情况下SwiftUI无法确定它是否存在依赖外部状态的副作用,或是需要提前初始化所有实例来计算布局。加上在List的遍历闭包中直接初始化Cell,会被系统判定为需要立即求值的表达式,最终触发全量创建。

解决方案(保留自定义视图)

方法1:结合Identifiable数据源

让数据源遵循Identifiable协议,直接将数组传入List,让系统明确每个单元格的唯一标识,从而触发懒加载:

import SwiftUI

struct ContentView: View {
    // 构建遵循Identifiable的数据源
    private let items = (0..<1000).map { Item(id: $0, index: $0) }
    
    var body: some View {
        NavigationView {
            List(items) { item in
                let _ = print(item.index)
                Cell(index: item.index)
            }
        }
    }
}

// 定义可标识的数据模型
struct Item: Identifiable {
    let id: Int
    let index: Int
}

struct Cell: View {
    let index: Int
    var body: some View {
        Text("\(index)")
    }
}

#Preview {
    ContentView()
}

方法2:List嵌套ForEach

利用ForEach的懒加载特性,将自定义视图放在ForEach闭包中,让ForEach负责控制视图的延迟创建:

import SwiftUI

struct ContentView: View {
    var body: some View {
        NavigationView {
            List {
                ForEach(0..<1000, id: \.self) { index in
                    let _ = print(index)
                    Cell(index: index)
                }
            }
        }
    }
}

struct Cell: View {
    let index: Int
    var body: some View {
        Text("\(index)")
    }
}

#Preview {
    ContentView()
}

效果验证

修改后运行代码,初始加载时控制台只会打印可见范围内的索引(约18个),滚动时才会打印新出现的索引,说明懒加载正常生效,性能问题解决。

内容的提问来源于stack exchange,提问作者de.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 22:33:20