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

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仍显示旧数据。


问题原因

  1. @State的误用:@State用于管理视图内部的私有状态,初始化值仅在视图首次创建时读取。父视图传递新数据时,@State修饰的变量不会自动更新——它的设计意图是让视图自身控制状态,而非接收外部动态数据。
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 02:44:51