WWDC24 SALE: Save 50% on all my Swift books and bundles! >>

SOLVED: Project 7 challenge: deleting from multiple list sections

Forums > 100 Days of SwiftUI

Hello there! I have just completed the challenges for project 7, but I'd like to get a few comments. I'm most definitely sure that they could be solved more easily, but I quite can't put my finger on it.

Specifically, I had some problems with the deletion from multiple List sections: I created two sections for the personal and business expenses, filtering the expenses.items.

When deleting, it appears to me that SwiftUI calls my removeItems functions providing the index within the list section, so the problems arise when, in the array, I have a business expense, then a personal one, as they get reordered by filtering.

What I thought of is "offsetting the offsets", so to speak: I order the array of expenses by type (personal first, business second), so that I know how many personal expenses I have to skip to delete a business one.

I admit I didn't test it extensively, it seems to work, but I'm quite sure there would be a better way to solve this. I don't particularly like reassigning the whole array every time I remove something, or having two different functions that perform basically the same task.

I attach the code, thanks in advance for the suggestions!

import SwiftUI

struct ContentView: View {
    @StateObject var expenses = Expenses()
    @State private var showingAddExpense = false

    var body: some View {
        NavigationStack {
            List {
                Section("Personal expenses") {
                    ForEach(expenses.items.filter { $0.type == "Personal" }) { item in
                        HStack {
                            VStack(alignment: .leading) {
                                Text(item.name)
                                    .font(.headline)

                                Text(item.type)
                                    .font(.subheadline)
                            }

                            Spacer()

                            Text(item.amount, format: .currency(code: Locale.current.currency?.identifier ?? "EUR"))
                        }
                    }
                    .onDelete(perform: removeItemsFromPersonal)
                }

                Section("Business expenses") {
                    ForEach(expenses.items.filter { $0.type == "Business" }) { item in
                        HStack {
                            VStack(alignment: .leading) {
                                Text(item.name)
                                    .font(.headline)

                                Text(item.type)
                                    .font(.subheadline)
                            }

                            Spacer()

                            Text(item.amount, format: .currency(code: Locale.current.currency?.identifier ?? "EUR"))
                        }
                    }
                    .onDelete(perform: removeItemsFromBusiness)
                }
            }
            .navigationTitle("iExpense")
            .toolbar {
                Button {
                    showingAddExpense = true
                } label: {
                    Image(systemName: "plus")
                }

                EditButton()
            }
            .sheet(isPresented: $showingAddExpense) {
                AddView(expenses: expenses)
            }
        }
    }

    func removeItemsFromPersonal(at offsets: IndexSet) {
        //expenses.items.remove(atOffsets: offsets)

        var expensesItems = expenses.items.sorted { a, b in a.type > b.type }
        expensesItems.remove(atOffsets: offsets)
        expenses.items = expensesItems
    }

    func removeItemsFromBusiness(at offsets: IndexSet) {
        // expenses.items.remove(atOffsets: newOffsets)

        let nPersonal = expenses.items.filter { $0.type == "Personal" }.count
        var newOffsets = IndexSet()

        for o in offsets {
            newOffsets.insert(o + nPersonal)
        }

        var orderedExpensesItems = expenses.items.sorted { a, b in a.type > b.type }
        orderedExpensesItems.remove(atOffsets: newOffsets)
        expenses.items = orderedExpensesItems
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

2      

Hi, I think the problem is when you filter it returns a new array of items, so you're basically dealing with 3 different arrays; What worked for me was using a conditional after the ForEach, wrapping the HStack and it's content inside it; One for Personal and one for Business.

if item.type == "Personal" {
  HStack {
    VStack(alignment: .leading) {
        Text(item.name)
            .font(.headline)

        Text(item.type)
            .font(.subheadline)
    }

    Spacer()

    Text(item.amount, format: .currency(code: Locale.current.currency?.identifier ?? "EUR"))
  }
}

That way you could use just 1 removeItems

3      

hi,

the problem is basically that your function removeItemsFromPersonal(at offsets: IndexSet) is being handed offsets with respect to the list

expenses.items.filter { $0.type == "Personal" }

so you need to identify the expenses to be deleted with respect to their indices in this filtered list, and remove these from the base expense.items array.

something like this should work

func removeItemsFromPersonal(at offsets: IndexSet) {
    // identify expenses you want to delete
  let personalExpenses  = expenses.items.filter { $0.type == "Personal" }
  let expensesToDelete = offsets.map { personalExpenses[$0] }

    // delete these from expenses.items
  for expense in expensesToDelete {
    if let index = expenses.items.firstIndex(of: expense) {
      expenses.items.delete(at: index)
    }
  }
}

the same goes for removeItemsFromBusiness.

hope that helps,

DMG

4      

Thanks for the answers!

@Hectorcrdna, your solution made very sense to me, but apparently it does not compile. My guess is that it is not able to understand when the iteration of the ForEach will return something (ie, to tell in advance when an expense is personal or business).

@delawaremathguy, thanks very much, it was evident yet I couldn't see it, of course the problem was operating on three different arrays. Thanks again!

2      

Save 50% in my WWDC sale.

SAVE 50% To celebrate WWDC24, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.