BLACK FRIDAY: Save 50% on all my Swift books and bundles! >>

Day 38 - project 7 :( I don't get it

Forums > 100 Days of SwiftUI

I spend way too much hours on trying to get the last part of the challange to work :

"For a bigger challenge, try splitting the expenses list into two sections: one for personal expenses, and one for business expenses. This is tricky for a few reasons, not least because it means being careful about how items are deleted!"

I also searched a lot on the forum and think I have found an answer/ more help. There was an hint and the OP from that post said that helped him a lot. But I don't want to just copy and paste and then still don't understand.
While I for starters don't understand how to make the two list, I also don't understand what can go wrong with the deleting of items. In my mind the app user is seeing their expenses, If I figure out how they see it sorted between the Personal and Business, but how can their become an problem when deleting items? I mean, they swipe over the item they want to delete, what can go wrong? Since the items are storted with an UUID, I would think that nothing can go wrong?

2      

Hi,

try to filter the array using the type property in the structure, then create an expense section (I suggest you to create a reusable view).

Last think how to implement a function that deletes the correct expense. The function should take as argument the expenses array of the desired category.

2      

@suzanne is learning via frustration. This is a valid use of her time! But it's time for a hint.

While I for starters don't understand how to make the two list,
I also don't understand what can go wrong with the deleting items.

Source of Truth

To answer the first part, consider the true list of expenses in your application. What is the absolute source of truth? You'll need to identify the source and make sure any changes you make (adding, deleting) are made to the source of truth. Do not try to update sublists, or temporary holding variables. Update your source of truth!

Playground

Run this code in playgrounds. Notice how you have one and only one source of truth for students. But you can use array functions to extract portions of your data for other purposes. For example, maybe you just want to know who's in Hufflepuff.

enum House {
    case gryffendor, hufflepuff, ravenclaw, slytherin
}
// Define a student.
struct Student {
    let name:  String
    let house: House  // must belong to one of the established houses.
}

// Students from some school.
let students = [
    Student(name: "Harry",        house: .gryffendor),
    Student(name: "Hermione",     house: .gryffendor),
    Student(name: "Malfoy",       house: .slytherin),
    Student(name: "Luna",         house: .ravenclaw),
    Student(name: "Myrtle",       house: .hufflepuff),
    Student(name: "Padma",        house: .ravenclaw),
    Student(name: "Lockhart",     house: .ravenclaw),
    Student(name: "Slughorn",     house: .slytherin),
    Student(name: "Riddle",       house: .slytherin),
    Student(name: "Longbottom",   house: .gryffendor),
    Student(name: "Cho",          house: .ravenclaw),
    Student(name: "Ginny Weasly", house: .gryffendor)
]

// You have a long list of students from the school.
// How can you divide this into more specific lists?
//
// Maybe you can derive new (focused) variables from the
// complete list of students?
//
// Computed variables to the rescue!

// The list of students is the Authoritative Data Source.
// These variables are computed from this authoritative source.
var gryffendor: [Student] { students.filter{$0.house == .gryffendor} }
var ravenclaw:  [Student] { students.filter{$0.house == .ravenclaw } }

print("Gryffendor Students")
gryffendor.forEach { student in print(student.name) }
print(" ")
print("Ravenclaw Students")
ravenclaw.forEach { print($0.name) } // Same effect. Shorthand syntax.

2      

Save 50% in my WWDC sale.

SAVE 50% All our books and bundles are half price for Black Friday, 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!

@Obelix and @andreasara-dev

Thankyou for your responses! I orderd myself to really stop for now since I have spend most of today on this topic and really need to drink my birthday beer for the day is over ;). But I will read your responses again tomorrow and with new energy and hope I will see if it will help me further. Otherwise I will ask some new questions

2      

Hi @suzanne

If I figure out how they see it sorted between the Personal and Business, but how can their become an problem when deleting items?

Taken from Solutions

The final challenge was to split the expenses list into two sections: one for personal expenses, and one for business expenses. I said this one was a little harder because you need to give some thought to how you divide up the two categories, but in my view the best place for this is computed properties in the Expenses class.

That would mean starting by adding these two to the class:

var personalItems: [ExpenseItem] {
    items.filter { $0.type == "Personal" }
}

var businessItems: [ExpenseItem] {
    items.filter { $0.type == "Business" }
}

That was the easy part. The trickier part is in ContentView, because we can no longer just remove items from the main items array. In fact, we have no idea where individually might appear in the items array, so we need to find them individually – it’s more work, but it’s doable.

Being able to find items by hand means telling Swift how it can check whether one item is equal to another, which means making ExpenseItem conform to the Equatable protocol. In its simplest form that means just adding Equatable to its declaration, like this:

struct ExpenseItem: Identifiable, Codable, Equatable {

When we do that we’re asking Swift to generate the Equatable conformance for us, and it does that by comparing every property against every other property. In practice, this will return false as soon as one property – our id – is not the same, so it will be very efficient.

Next we need to update the removeItems() method so that it knows which input array it’s reading from – personal items or business items. This will then create a new IndexSet by locating each item in the full array, and delete them in bulk using remove(atOffsets:) like before:

func removeItems(at offsets: IndexSet, in inputArray: [ExpenseItem]) {
    var objectsToDelete = IndexSet()

    for offset in offsets {
        let item = inputArray[offset]

        if let index = expenses.items.firstIndex(of: item) {
            objectsToDelete.insert(index)
        }
    }

    expenses.items.remove(atOffsets: objectsToDelete)
}

We need to wrap that in two simpler methods that SwiftUI can call directly from onDelete():

func removePersonalItems(at offsets: IndexSet) {
    removeItems(at: offsets, in: expenses.personalItems)
}

func removeBusinessItems(at offsets: IndexSet) {
    removeItems(at: offsets, in: expenses.businessItems)
}

Let you work out how to use it

3      

@Obelix
I think I understand the delete better. Please correct me if I am wrong but I think mij delete functions deletes sometimes and some line I tell it to do. While I might think it will be for example delete the first student Padma from ravenclaw, the delete function is working on 'Students' So it will delete Harry.

edit: with the delete fuction I was thinking about the deletefunction in the current project iExpense.

2      

The lightbulb moment for @suzanne!

While I might think it will be for example delete the first student Padma from ravenclaw,
the delete function is working on 'Students' So it will delete Harry.

Exactly! I love it when programmers work through the logic with gentle hints from this forum. Wonderful!

@twoStraw's hint:
This is tricky for a few reasons, not least because it means being careful about how items are deleted!

This is a great lesson for you. Try to design your screens and data models to support one Source of Truth™. Even though you may pass subsets of this data to other views, or to rows in a scroll view, or to a popup view, you need to update the Source of Truth, otherwise you'll have unintended side effects.

Keep coding!

2      

@Obelix
Thankyou. Still working on this, also ussing the help from @NigelGee, or answers tbh. But doing it bit by bit and trying myself first. I did have two nice lists now. So going to look into the delete after dinner. Want that goes very wrong indeed.

I'm also making this more complicated on my way (of course haha). Yesterday I already spotted https://www.hackingwithswift.com/forums/100-days-of-swiftui/day-38-challenge-3-how-to-do-it/13348. and also https://www.hackingwithswift.com/forums/swiftui/delete-rows-in-sorted-list/20315. With person also busy with day 38, I see thet they have two sections and ForEach loops in the contentView. And I was wondering if that couldn't be done differently. I was thinking that is we get another group with costs for some reason, there wille be another section in the code with 99% the same lines of code. Since we have an constant with types, I was wondering if we can not use that to make one ForEach loop. Not completly sure how that should be working with secion headers. But somehow I am convinced that we shouldn't need to write the same section twice (or even more if there are more types added)

2      

Today spend anonther day on this project and lot of searches online. But it works! So happy me :).

To be honest, I used the answers @NigelGee gave and did the last action myself to get things really working. I could have done that the first moment I looked at it today but still wanted to solve it myself and more important understand it. So that is why I still did many searches online and spend a lot of time.
@Obelix already helped me a lot with understanding it more, so again thankyou! I used the answers from @NigelGee because in the end examples help me a lot to understand. I played arround with the answers, adjusted things to see what happend, and I think I (mostly) understand now what is done, why, and how.

The thing I mentioned myself about having two sections, is something I will look into in a other moment.

2      

@suzanne finds the right helper:

To be honest, I used the answers @NigelGee gave
I used the answers from @NigelGee because in the end examples help me a lot to understand.

Yes, @nigel is well known in this forum for providing great solutions. Well done on finishing this task.

2      

@Obelix you were also the right helper because you also helped me understand things a lot better with a good example

2      

Save 50% in my WWDC sale.

SAVE 50% All our books and bundles are half price for Black Friday, 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.