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

SwiftUI中List复选框滚动后状态重置的异常问题及原因解析

Great question! Let's break down why this happens and why wrapping ForEach inside List fixes the issue (plus, what the proper long-term fix should be).

Why the original List setup loses checkbox state

  • First, look at your CheckView: it uses @State var isChecked: Bool to track the checkbox state. This is view-local state — the value lives only inside that specific CheckView instance, not in your checkListData source.
  • When you use List(checkListData) { item in ... }, SwiftUI's List uses a cell reuse mechanism to optimize performance. When a cell scrolls off-screen, SwiftUI might destroy the corresponding CheckView to free up memory. When you scroll back up, it creates a brand new CheckView for that item, initializing isChecked with the original item.isChecked value (which defaults to false in your model). That's why your checked state disappears.
  • On top of that, checkListData is a let constant and CheckListItem is a value type. Even if you wanted to update the source data from CheckView, you can't — the item passed to the List closure is a copy, not a reference to the original data.

Why wrapping ForEach inside List fixes the issue (sort of)

When you nest ForEach inside List, you change how SwiftUI tracks views for each item:

  • ForEach explicitly uses each CheckListItem's id to create a stable identity for its child views. SwiftUI uses this identity to map views to items consistently, even during scrolling.
  • Instead of destroying and recreating CheckView instances when cells scroll off-screen, SwiftUI is more likely to preserve the view's local @State because it can reliably link the view back to its original item. This makes the state persist through scrolling.
  • Important note: This is a temporary band-aid, not a real fix. If you navigate away from the screen and come back, or if the view refreshes for any other reason, you'll still lose the state — it's only stored in the view, not your source data.

The proper solution: Bind to the source data

To make state persist reliably, sync the checkbox state with your checkListData instead of relying on view-local @State:

  1. Make checkListData a mutable state variable in your parent view:
struct CheckListView: View {
    @State private var checkListData = [
        CheckListItem(id:0,title: "Neopolitan"),
        CheckListItem(id:1,title: "New York"),
        CheckListItem(id:2,title: "Hawaiian")
    ]
    
    var body: some View {
        List {
            ForEach($checkListData) { $item in
                CheckView(isChecked: $item.isChecked, title: item.title)
            }
        }
        .font(.title)
    }
}
  1. Update CheckView to use @Binding instead of @State — this creates a two-way link between the view and your source data:
struct CheckView: View {
    @Binding var isChecked: Bool
    var title: String
    
    var body: some View {
        HStack{
            Button(action: { isChecked.toggle() }) {
                Image(systemName: isChecked ? "checkmark.square" : "square")
            }
            Text(title)
        }
    }
}

Now, when you toggle a checkbox, the change saves directly to checkListData. No matter how you scroll, refresh, or navigate, the state will always reflect the source data.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 19:02:51