我有一个元素数组,其中包含其他元素的嵌套数组。 删除数组的一行时,有时会发生崩溃,并显示消息“Swift/ContigeousArrayBuffer.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)
}
}
尝试将
@Binding
中的@State
更改为RowView
,可以防止崩溃,但是isSelected
无法正常工作,因为它没有与viewModel的值“绑定”。
我猜这是一个内部 SwiftUI 错误。 (Xcode 15.4、iOS 17+)
尝试使用
ForEach($viewModel.assetsRows)
而不使用 id: \.self
的方法
以及用于绑定的 $assetsRow
。 withAnimation
中也没有ViewModel
,
withAnimation
仅在用于 View
时才有意义。
struct ContentView: View {
@StateObject var viewModel = ViewModel()
var body: some View {
ScrollView {
LazyVStack {
ForEach($viewModel.assetsRows) { $assetsRow in // <--- here
VStack {
Button(action: {
viewModel.deleteSelected(assetsIn: assetsRow)
}, label: {
HStack {
Image(systemName: "trash")
Text("Delete row")
}
})
RowView(assetsRow: $assetsRow) // <--- here
}
}
}
}
}
}
struct RowView: View {
@Binding var assetsRow: AssetsRowModel
var body: some View {
ScrollView(.horizontal) {
LazyHStack {
ForEach($assetsRow.items) { item in // <--- here
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(AssetsRowModel(items: [AssetItem(), AssetItem(), AssetItem()]))
}
return array
}()
// removing items causes crash (not 100% times)
func deleteSelected(assetsIn row: AssetsRowModel) {
// <-- no 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)
}
}