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

SwiftUI List未实现视图实例化懒加载?如何实现真正懒加载?

SwiftUI List 实例化懒加载问题解析与解决方案

核心问题说明

SwiftUI的List官方宣称的“懒加载”仅针对渲染阶段(即仅绘制可见区域的内容),但和UIKit中UITableView/UICollectionView的“实例化懒加载”(仅创建可见cell实例)不是同一概念。你的测试代码中所有Row结构体被提前初始化、body被计算,正是因为SwiftUI的视图树构建逻辑:当你在List的闭包里直接返回Row(value: el)时,SwiftUI会遍历整个数据源数组,提前创建所有对应的View结构体实例并计算body,这和List的渲染懒加载机制无关。

而LazyVStack的设计则完全不同——它会延迟创建视图实例,只有当视图即将进入可见区域时,才会初始化对应的View结构体并计算body,这才是你期望的“实例化层面的懒加载”。

解决方案

方案1:用LazyVStack + ScrollView替代List

如果不需要List的原生交互(如滑动删除、下拉刷新),直接用LazyVStack包裹在ScrollView中即可实现实例化懒加载:

struct ContentView: View {
  var elements: [String] = (0..<100000).map(\.description)
  
  var body: some View {
    ScrollView {
      LazyVStack {
        ForEach(elements, id: \.self) { el in
          Row(value: el)
            .id(el)
        }
      }
    }
  }
}

struct Row: View, Equatable {
  var value: String
  
  init(value: String) {
    print("Row.init \(value)")
    self.value = value
  }
  
  var body: some View {
    let _ = print("Row.body \(value)")
    Text(value)
  }
}

运行后会发现,只有可见区域的Row会被初始化和计算body,滚动到新区域时才会创建新的实例。

方案2:封装UIKit列表组件(保留原生交互)

如果需要List的原生交互特性,可通过UIViewRepresentable封装UIKit的UITableView,完全控制cell的实例化逻辑:

struct UIKitListView: UIViewRepresentable {
  var elements: [String]
  
  func makeUIView(context: Context) -> UITableView {
    let tableView = UITableView()
    tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
    tableView.dataSource = context.coordinator
    return tableView
  }
  
  func updateUIView(_ uiView: UITableView, context: Context) {
    uiView.reloadData()
  }
  
  func makeCoordinator() -> Coordinator {
    Coordinator(self)
  }
  
  class Coordinator: NSObject, UITableViewDataSource {
    var parent: UIKitListView
    
    init(_ parent: UIKitListView) {
      self.parent = parent
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
      parent.elements.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
      let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
      cell.textLabel?.text = parent.elements[indexPath.row]
      print("Cell created for \(parent.elements[indexPath.row])")
      return cell
    }
  }
}

// 使用示例
struct ContentView: View {
  var elements: [String] = (0..<100000).map(\.description)
  
  var body: some View {
    UIKitListView(elements: elements)
  }
}

这种方式完全遵循UIKit的cell复用机制,仅创建可见区域的cell实例,实现真正的实例化懒加载。

补充说明

iOS上的List底层虽基于UITableView,但SwiftUI的视图构建逻辑与UIKit的cell复用是分离的:SwiftUI会提前构建所有View结构体,再将可见的View同步到UIKit的cell中,因此你会看到所有Row被初始化,但只有可见的才会被渲染到屏幕上。这就是为什么iOS(包括iOS 18及以下版本)也存在该问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 11:43:28