UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: How to update a view with the number of filtered items using Core Data

Forums > SwiftUI

Hi HWS Community,

I am working on an App that presents some animal Cards in the Content View showing an image of the animal (for instance a dog) and I am trying to show the number of dogs currently in the Core Data.

Here is the AnimalCard code:

import SwiftUI

struct AnimalCard: View {
    var animal: String
    var number: Int

    var body: some View {
        ZStack(alignment: .topTrailing) {
            ZStack {
                Image(animal)
                    .resizable()
                    .scaledToFit()
                    .frame(width: 140, height: 140)
                    .cornerRadius(20)
                    .shadow(color: .secondary, radius: 5, x: 0, y: 1)
            }
            if number != 0 {
                Text(String(number))
                    .font(.title2)
                    .frame(width: 20, height: 20)
                    .padding(5)
                    .foregroundColor(.white)
                    .background(.red)
                    .cornerRadius(50)
                    .offset(x: 10, y: -10)
            }
        }
    }

}

Here is the ContentView code, it has 3 AnimalCards (Dog, Cat and Bird), the number is being hard coded. When you click the Card it takes you to the list of animals available in that category (dog, cat or bird). I also added a button to load some sample data.

struct ContentView: View {

    @Environment(\.managedObjectContext) var moc
    @FetchRequest(sortDescriptors: []) var animals: FetchedResults<Animals>

    var body: some View {
        NavigationStack {
            VStack {
                    VStack(spacing: 40) {
                        Text("Animal Shelter")
                            .font(.largeTitle.bold())
                        NavigationLink(destination: {
                            AnimalListView(animal: "Dog", filter: "dog")
                        }, label: {
                            AnimalCard(animal: "Dog", number: 2)
                        })
                        NavigationLink(destination: {
                            AnimalListView(animal: "Cat", filter: "cat")
                        }, label: {
                            AnimalCard(animal: "Cat", number: 4)
                        })
                        NavigationLink(destination: {
                            AnimalListView(animal: "Bird", filter: "bird")
                        }, label: {
                            AnimalCard(animal: "Bird", number: 1)
                        })
                    }
                    Spacer()
                // Loading Sample Data
                    Button(action: {
                        let coco = Animals(context: moc)
                        coco.name = "Coco"
                        coco.animal = "dog"
                        coco.age = Int16(11)

                        let brani = Animals(context: moc)
                        brani.name = "Brani"
                        brani.animal = "dog"
                        brani.age = Int16(7)

                        let hercules = Animals(context: moc)
                        hercules.name = "Hercules"
                        hercules.animal = "dog"
                        hercules.age = Int16(4)

                        let garfield = Animals(context: moc)
                        garfield.name = "Garfield"
                        garfield.animal = "cat"
                        garfield.age = Int16(2)

                        let gina = Animals(context: moc)
                        gina.name = "Gina"
                        gina.animal = "cat"
                        gina.age = Int16(3)

                        try? moc.save()

                    }, label: {
                        Text("Load Data")
                    })
                    Spacer()
                }
                .padding()
        }
    }
}

Here is the AnimalListView file that will be shown with animal name and age.

import SwiftUI

struct AnimalListView: View {

    @Environment(\.managedObjectContext) var moc
    @FetchRequest(sortDescriptors: []) var animals: FetchedResults<Animals>

    var animal: String
    var filter: String

    var body: some View {
        VStack {
            Text(animal)
                .font(.largeTitle)
                .padding()
            FilteredList(filter: filter)
        }

    }
}

struct AnimalListView_Previews: PreviewProvider {
    static var previews: some View {
        AnimalListView(animal: "Dog", filter: "dog")
    }
}

Here is the FilteredList file. As you can see, I did add the fetchRequest.count to be shown in the AnimalListView.

import CoreData
import SwiftUI

struct FilteredList: View {
    @FetchRequest var fetchRequest: FetchedResults<Animals>

    var body: some View {
        Text("Number of Animals: \(fetchRequest.count)")
        List {
            ForEach(fetchRequest, id: \.self) { animals in
                VStack {
                    Text("\(animals.name ?? "Unknown")")
                        .font(.title)
                        .frame(maxWidth: .infinity, alignment: .topLeading)
                        .lineLimit(1)
                    Text("Age: \(animals.age)")
                        .font(.headline)
                        .foregroundColor(.gray)
                        .frame(maxWidth: .infinity, alignment: .bottomLeading)
                }

            }
        }
    }

    init(filter: String) {
        _fetchRequest = FetchRequest<Animals>(sortDescriptors: [SortDescriptor(\.name)], predicate: NSPredicate(format: "animal == %@", filter))
    }
}

Again, the question is how to take the count from the FilteredList and update the AnimalCard in the ContentView?

Thanks for your help. Renato.

2      

Renato having fun with filters:

how to take the count from the FilteredList and update the AnimalCard in the ContentView?

Seems you have all the data you need in the ContentView! You have the entire array in your array animals.

Consider building a computed property that filters the animals array for one category of animal, and performs a count on that subarray. No need to dive into a subview to make that calculation, then try and pass the count back to the parent view.

Paste into Playgrounds:

struct Animal {
    let category: String // dog
    let age:      Int    // 4
}

var filter = "dogcow"  // Moof!

// Computed Property
var animalCount: Int {
    // --the collection--/--animal filter--/--how many found--
    menagerie.filter { $0.category == filter}.count
}

var menagerie = [
    //----- dog
    Animal(category: "dog", age: 8), Animal(category: "dog", age: 7),
    Animal(category: "dog", age: 6), Animal(category: "dog", age: 5),
    Animal(category: "dog", age: 4),
    //----- cat
    Animal(category: "cat", age: 8), Animal(category: "cat", age: 7),
    Animal(category: "cat", age: 6), Animal(category: "cat", age: 5),
    Animal(category: "cat", age: 4), Animal(category: "cat", age: 3),
    Animal(category: "cat", age: 2), Animal(category: "cat", age: 1),
    //----- dogcow
    Animal(category: "dogcow", age: 18), Animal(category: "dogcow", age: 17),
    Animal(category: "dogcow", age: 16)
]

menagerie.count        // 16

filter = "dog"
animalCount            // 5
filter = "dogcow"      // Moof! Shoutout to Clarus!
animalCount            // 3
filter = "cat"
animalCount            // 8

Careful! Your filter may return no objects!

Keep Coding!

2      

Oooof. ContentView.

-20 points from Hufflepuff!

+20 points for explanation (below).

See -> FooBarView

Please read and leave your thoughts ....

2      

Thanks @Obelix! It worked perfectly!

2      

@Obelix, regarding ContentView, I always change it to MainView, the example above was made only to show the issue I was having, since the main app is very big, it would be hard to explain. But I do agree with you, ContentView is not a good name for a production app.

3      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

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.