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

SwiftUI双层Lazy Stack用ForLoop时触发「数组越界致命错误」

SwiftUI嵌套数组删除行时偶发索引越界崩溃问题

我维护的SwiftUI界面中,存在嵌套数组结构(AssetsRowModel数组包含AssetItem数组),删除某一行时偶尔会触发崩溃,错误提示:

'Swift/ContiguousArrayBuffer.swift:600: Fatal error: Index out of range'
且无法定位到具体代码行。

最小复现代码

// View components
struct ContentView: View {
    @StateObject var viewModel: ViewModel = .init()

    var body: some View {
         ScrollView {
            LazyVStack {
                ForEach($viewModel.assetsRows, id: \.self) { assetsRow in
                    VStack {
                        Button(action: {
                            viewModel.deleteSelected(assetsIn: assetsRow.wrappedValue)
                        }, label: {
                            HStack {
                                Image(systemName: "trash")
                                Text("Delete row")
                            }
                        })
                        RowView(assetsRow: assetsRow)
                    }
                }
            }
        }
    }
}

struct RowView: View {
    @Binding var assetsRow: AssetsRowModel

    var body: some View {
        ScrollView(.horizontal) {
            LazyHStack {
                ForEach($assetsRow.items, id: \.self) { item in
                    GridItemView(
                        assetItem: item,
                        image: .init(systemName: "photo.fill")
                    )
                }
            }
        }
    }
}

struct GridItemView: View {
    @Binding var assetItem: AssetItem
    @State var image: Image?

    var body: some View {
        Group {
            if let image = image {
                image
            } else {
                ProgressView()
            }
        }
        .frame(width: 200, height: 120)
        .overlay(alignment: .bottomTrailing) {
            Toggle(isOn: $assetItem.isSelected) {
                Text("checkmark")
            }
            .padding(4)
        }
        .onAppear {
            // fetch image logic
        }
    }
}


@MainActor final class ViewModel: ObservableObject {
    @Published var assetsRows: [AssetsRowModel] = {
        var array: [AssetsRowModel] = []
        for i in 0..<30 {
            array.append(.init(items: [.init(), .init(), .init()]))
        }
        return array
    }()

    // removing items causes crash (not 100% times)
    func deleteSelected(assetsIn row: AssetsRowModel) {
        withAnimation {
            assetsRows.removeAll { element in
                element.id == row.id
            }
        }
    }

    // other fetching logic
}

// Models
struct AssetsRowModel: Identifiable, Equatable, Hashable {
    var id = UUID()

    var items: [AssetItem]
}

struct AssetItem: Identifiable, Hashable {
    var id = UUID()
    var isSelected = false
}

extension AssetItem: Equatable {
    static func ==(lhs: AssetItem, rhs: AssetItem) -> Bool {
        (lhs.id == rhs.id)
    }
}

已尝试的无效方案

将RowView中的@Binding var assetsRow: AssetsRowModel改为@State,虽能避免崩溃,但isSelected无法与ViewModel中的值同步,不符合业务需求。

问题分析与修复方案

问题根源

LazyVStack/LazyHStack结合ForEach($array, id: \.self)使用时,SwiftUI的懒加载机制可能在数组元素删除后,仍持有已失效的绑定引用,尤其是在动画过程中,内部视图尝试访问已被移除的数组索引,触发越界错误。

修复步骤

  1. 改用Identifiable原生重载的ForEach
    由于AssetsRowModel已实现Identifiable,直接使用ForEach($viewModel.assetsRows)即可,无需指定id: \.self,让SwiftUI通过唯一id精准跟踪视图身份。

  2. 优化删除方法的参数传递
    直接传递行的id而非整个实例,避免引用失效问题。

修改后的关键代码:

// ContentView中的ForEach部分
ForEach($viewModel.assetsRows) { $assetsRow in
    VStack {
        Button(action: {
            viewModel.deleteSelected(rowId: assetsRow.id)
        }, label: {
            HStack {
                Image(systemName: "trash")
                Text("Delete row")
            }
        })
        RowView(assetsRow: $assetsRow)
    }
}

// ViewModel的删除方法
func deleteSelected(rowId: UUID) {
    withAnimation {
        assetsRows.removeAll { $0.id == rowId }
    }
}
  1. 确保模型协议实现一致性
    当前AssetItem的Equatable和Hashable仅基于id判断,该实现是合理的,但后续若新增属性,需同步更新这两个协议的实现,避免SwiftUI视图身份判断错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 16:20:12