I've built a horizontal scrolling ForEach UI within my ContentView component that displays an array of custom objects (struct form). When I try to delete an item, I get an "Fatal Error: Index out of range" error.
The issue is when I delete an item, the actual array itself updates, but specific AssetView (component below) component is not updating and so it ultimately iterates to an index that no longer exists. Any idea what the issue can be? Below is my code:
ContentView
struct ContentView: View {
@ObservedObject var assetStore: AssetStore
var body: some View {
ScrollView (.horizontal) {
ForEach(assetStore.assets.indices, id: \.self) { index in
AssetView(
asset: $assetStore.assets[index],
assetStore: assetStore,
smallSize: geo.size.height <= 667
)
.padding(.bottom)
}
}
}
}
AssetView
struct AssetView: View {
@Binding var asset: Asset
@ObservedObject var assetStore: AssetStore
var smallSize: Bool
@State var editAsset: Bool = false
var body: some View {
VStack(alignment: .center, spacing: smallSize ? -10 : 0) {
HStack {
TagText(tagName: asset.name)
.onTapGesture {
editAsset.toggle()
}
Spacer()
DisplayCurrentValues(
forCurrentValueText: asset.getCurrentValueString,
forCurrentValueLabel: "Current Value"
)
.onTapGesture {
editAsset.toggle()
}
}
DisplayStepper(asset: $asset, title: "YoY Growth", type: .growth)
DisplayStepper(asset: $asset, title: "Recurring Deposit", type: .recurring)
}
.sheet(isPresented: $editAsset, content: {
EditAsset(assetStore: assetStore, currentValue: String(asset.currentValue), name: asset.name, asset: $asset)
})
}
}
AssetStore
This is where I read/write all of the asset objects to my App's Documents folder.
class AssetStore: ObservableObject {
let assetsJSONURL = URL(fileURLWithPath: "Assets",
relativeTo: FileManager.documentsDirectoryURL).appendingPathExtension("json")
@Published var assets: [Asset] = [] {
didSet {
saveJSONAssets()
}
}
init() {
print(assetsJSONURL)
loadJSONAssets()
}
private func loadJSONAssets() {
guard FileManager.default.fileExists(atPath: assetsJSONURL.path) else {
return
}
let decoder = JSONDecoder()
do {
let assetsData = try Data(contentsOf: assetsJSONURL)
assets = try decoder.decode([Asset].self, from: assetsData)
} catch let error {
print(error)
}
}
private func saveJSONAssets() {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
do {
let assetsData = try encoder.encode(assets)
try assetsData.write(to: assetsJSONURL, options: .atomicWrite)
} catch let error {
print(error)
}
}
public func deleteAsset(atIndex id: Asset) {
let index = assets.firstIndex(where: { $0.id == id.id})
assets.remove(at: index!)
}
}
Asset Object
struct Asset: Identifiable, Codable, Hashable {
// currently set for 10 years
let id = UUID()
enum Frequency: String, Codable, CaseIterable {
case month = "Month"
case year = "Year"
case none = "None"
}
let years: Int
var name: String
var currentValue: Int
var growth: Int
var recurringDeposit: Int
var recurringFrequency: Frequency
enum CodingKeys: CodingKey {
case id
case years
case name
case currentValue
case growth
case recurringDeposit
case recurringFrequency
}
init(
name: String = "AssetName",
currentValue: Int = 1000,
growth: Int = 10,
years: Int = 10,
recurringDeposit: Int = 100,
recurringFrequency: Frequency = .month
) {
self.name = name
self.currentValue = currentValue
self.growth = growth
self.recurringDeposit = recurringDeposit
self.recurringFrequency = recurringFrequency
self.years = years
}
}