SwiftUI复用列表行视图留存旧数据问题排查与修复
问题:SwiftUI可复用列表行视图数据不更新的原因与修复方案
我在应用中使用可复用列表行视图ReusableListRowView,通过视图内部的@State属性传递数据。首次传递数据时视图显示正常,但更新数据后,该视图仍显示旧数据,未同步更新。以下是示例代码与复现情况:
示例代码
import SwiftUI import Foundation import Combine class DisplayItem: Hashable, Identifiable { let id = UUID().uuidString let title: String let subtitle: String init(title: String, subtitle: String) { self.title = title self.subtitle = subtitle } public func hash(into hasher: inout Hasher) { hasher.combine(id) } public static func == (lhs: DisplayItem, rhs: DisplayItem) -> Bool { lhs.id == rhs.id } } class ViewModel: ObservableObject { @Published var showDisplayData: Bool = false @Published var displayData: DisplayItem? /// 仅用于视图出现时修改数据,实际应用中由查询逻辑更新数据 var value = 0 } struct ContentView: View { @StateObject var viewModel: ViewModel = ViewModel() @State private var navigateToNextView: Bool = false var body: some View { NavigationStack { ZStack { Color.gray.opacity(0.5).edgesIgnoringSafeArea(.all) VStack { List { Section { if viewModel.showDisplayData, let displayData = viewModel.displayData { let _ = print("Latest display title in ContentView - \(displayData.title)") ReusableListRowView(displayData: displayData) } VStack { Text("Click to go to next view").font(.body).foregroundColor(Color.black) }.onTapGesture { navigateToNextView = true } } header: { Text("Data update issue") } } } .onAppear { viewModel.displayData = getDisplayItem() viewModel.showDisplayData = true } } .navigationDestination(isPresented: $navigateToNextView) { NextView().environmentObject(viewModel) } } } private func getDisplayItem() -> DisplayItem { if viewModel.value == 0 { return DisplayItem(title: "Update Me", subtitle: "Both title and subtitle should get updated.") } else { return DisplayItem(title: "I have been updated", subtitle: "No need to worry.") } } } struct ReusableListRowView: View { @State var displayData: DisplayItem var body: some View { VStack(alignment: .leading, spacing: 2.0) { let _ = print("Display title in ReusableListRowView - \(displayData.title)") Text(displayData.title).font(.body.bold()).foregroundStyle(Color.primary) Text(displayData.subtitle).font(.caption).foregroundStyle(Color.secondary) } } } struct NextView: View { @EnvironmentObject var viewModel: ViewModel var body: some View { List { Section { HStack(spacing: 2) { Text("Use back button to go back.").font(.body).foregroundColor(Color.black) Spacer() } .contentShape(Rectangle()) } header: { Text("Hello") } } .onAppear { viewModel.value = 1 } .onDisappear { viewModel.value = 1 } } }
复现情况
首次运行控制台输出:
Latest display title in ContentView - Update Me
Display title in ReusableListRowView - Update Me
点击跳转至NextView,返回ContentView后,控制台输出:
Latest display title in ContentView - I have been updated
Display title in ReusableListRowView - Update Me
此时ContentView已获取新数据,但ReusableListRowView仍显示旧数据。
问题原因
@State的误用:@State用于管理视图内部的私有状态,初始化值仅在视图首次创建时读取。父视图传递新数据时,@State修饰的变量不会自动更新——它的设计意图是让视图自身控制状态,而非接收外部动态数据。- List视图复用:SwiftUI的List会复用行视图实例,如果视图的标识未变化,List可能保留旧的视图实例,导致旧数据无法被替换。
修复方案
方案1:移除@State修饰符(最优解)
将ReusableListRowView中的displayData改为普通属性,因为它仅用于展示父视图传入的数据,不需要内部状态管理:
struct ReusableListRowView: View { let displayData: DisplayItem // 移除@State修饰符 var body: some View { VStack(alignment: .leading, spacing: 2.0) { let _ = print("Display title in ReusableListRowView - \(displayData.title)") Text(displayData.title).font(.body.bold()).foregroundStyle(Color.primary) Text(displayData.subtitle).font(.caption).foregroundStyle(Color.secondary) } } }
方案2:添加视图标识(可选优化)
为确保List能准确识别行视图的变化,可以给ReusableListRowView绑定唯一标识,强制List在数据更新时创建新的视图实例:
// 在ContentView中修改ReusableListRowView的调用 ReusableListRowView(displayData: displayData) .id(displayData.id)
内容的提问来源于stack exchange,提问作者tech_human
相关产品推荐
相关产品推荐

