簡體   English   中英

SwiftUI:從 ForEach 中刪除項目導致索引超出范圍

[英]SwiftUI: Deleting an item from a ForEach results in Index Out of Range

我在我的 ContentView 組件中構建了一個水平滾動的 ForEach UI,它顯示了一組自定義對象(結構形式)。 當我嘗試刪除項目時,我收到“致命錯誤:索引超出范圍”錯誤。

問題是當我刪除一個項目時,實際的數組本身會更新,但特定的 AssetView(下面的組件)組件沒有更新,因此它最終會迭代到不再存在的索引。 知道問題可能是什么嗎? 下面是我的代碼:

內容視圖

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)
                       }
              }
    }
}

資產視圖

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)
        })
    }
}

資產商店

這是我將所有資產對象讀/寫到我的 App 的 Documents 文件夾的地方。

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!)
        
    }
  
}

資產 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
    }
}

我認為這是因為您的ForEach依賴於索引,而不是Asset本身。 但是,如果你擺脫了indices ,你將不得不為Asset編寫一個新的綁定。 這是我認為它可能的樣子:

ForEach(assetStore.assets, id: \.id) { asset in
                           AssetView(
                               asset: assetStore.bindingForId(id: asset.id),
                               assetStore: assetStore,
                               smallSize: geo.size.height <= 667
                           )
                            .padding(.bottom)
}

然后在您的AssetStore中:

func bindingForId(id: UUID) -> Binding<Asset> {
        Binding<Asset> { () -> Asset in
            self.assets.first(where: { $0.id == id }) ?? Asset()
        } set: { (newValue) in
            self.assets = self.assets.map { asset in
                if asset.id == id {
                    return newValue
                } else {
                    return asset
                }
            }
        }
    }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM