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

SOLVED: CoreData Relationship [Crash]

Forums > SwiftUI

Here is another CoreData question (sorry), after @Obelix has kindly helped me with the last one.

My model looks like this:

BrandsEntity (One-to-many relationship to DealersEntity)

DealersEntity (One-to-many relationship to: CarsEntitiy / Many-to-One relationship to: BrandEntity)

CarsEntity (Many-to-One relationship to DealersEntity)

Now, cars can either be reserved or not. I can calculate the number of available cars and show the result per Dealer. When I try to show the number of available cars per Brand, though, the app crashes on launch: Thread 1: "-[DealersEntity cars]: unrecognized selector sent to instance 0x600002a1a440"

During my journey with CoreData I've come along a few of those instances where I'd get a crash stating: unrecognized selector. Most times, if not all, it was down to me doing silly things. Here, though, I can't find the missing link.

Here is how I calculate the number of available cars per Dealer

extension DealersEntity {

    public var wrappedDealersName: String {
        dealersName ?? "Unkown name"
    }

    public var carsArray: [CarsEntity] {
        let set = cars as? Set<CarsEntity> ?? []
        return set.sorted {
            $0.wrappedCarsName < $1.wrappedCarsName
        }
    }

    var availableCars: Int {
        let allCars = cars?.allObjects as? [CarsEntity] ?? []
        guard allCars.isEmpty == false else { return 0 }

        let reservedCars = allCars.filter(\.reserved)
        return Int(allCars.count) -  Int(reservedCars.count)
    }
}

and here is how I try the same thing per Brand. The calculation is exactly the same, the issue seems to be in the relationship in that BrandsEntity doesn't have a direct relationship to Cars (only the DealersEntity in the middle has). Either that or this line is total nonsense of mine:

  let allCars = dealers.cars?.allObjects as? [CarsEntity] ?? []
extension BrandsEntity {

    public var wrappedBrandsName: String {
        brandName ?? "Unkown name"
    }

    public var dealersArray: [DealersEntity] {
        let set = dealers as? Set<DealersEntity> ?? []
        return set.sorted {
            $0.wrappedDealersName < $1.wrappedDealersName
        }
    }

    var availableCars: Int {

        let dealers = DealersEntity()

        let allCars = dealers.cars?.allObjects as? [CarsEntity] ?? []
        guard allCars.isEmpty == false else { return 0 }

        let reservedCars = allCars.filter(\.reserved)
        return Int(allCars.count) -  Int(reservedCars.count)
    }
}

Here are my three views in hierarchical order:

import SwiftUI

struct BrandsView: View {

    @EnvironmentObject var dataController: DataController
    @Environment(\.managedObjectContext) var moc

    @FetchRequest(entity: BrandsEntity.entity(),
                  sortDescriptors: [NSSortDescriptor(keyPath: \BrandsEntity.brandName, ascending: true)]) var brands: FetchedResults<BrandsEntity>

    var body: some View {
        NavigationView {
            List {
                ForEach(brands) { brand in
                    NavigationLink(destination: DealersView(brand: brand, dealer: DealersEntity())) {
                        HStack {
                            Text(brand.wrappedBrandsName)
                            Text("(\(brand.availableCars))")
                        }
                    }
                }.onDelete(perform: deleteBrands)
            }
            .padding()
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button {
                        try? dataController.createSampleData()
                    } label: {
                         Image(systemName: "plus")
                    }
                }
            }
        }
    }

    func deleteBrands(at offsets: IndexSet) {
        for offset in offsets {
            let brand = brands[offset]
            moc.delete(brand)
        }
        try? moc.save()
    }
}
import SwiftUI
import CoreData

struct DealersView: View {

    let brand: BrandsEntity
    let dealer: DealersEntity

    var body: some View {

        List {
            ForEach(brand.dealersArray) { dealer in
                NavigationLink(destination: CarsView(brand: brand, dealer: dealer)) {
                    HStack {
                        Text(dealer.wrappedDealersName)
                        Text("(\(dealer.availableCars))")
                    }
                }
            }
        }
        .toolbar {
            ToolbarItem(placement: .navigationBarTrailing) {
                Button {

                } label: {
                     Image(systemName: "plus")
                }
            }
        }
    }
}
import SwiftUI

struct CarsView: View {

    let brand: BrandsEntity
    let dealer: DealersEntity

    var body: some View {
        List {
            ForEach(dealer.carsArray) { car in
                    Text(car.wrappedCarsName)
            }
        }
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button {

                    } label: {
                         Image(systemName: "plus")
                    }
                }
            }
    }
}

Can anyone spot the issue? Or if that's the case, send me a hint as to how I can access the "grandchild" CarsEntity from the Parent "BrandsEntity"? This would greatly help me with my journey. I've tried to find a solution online, but it looks like I have to craft a whole essay in order to express what I'm looking for as I have to work on my coding terminology skills ...

Many thanks in advance!

2      

Does anyone have any tip for me? (Bumping as it went down pretty fast after posting, hopeing someone who didn't see it got an idea)

2      

"-[DealersEntity cars]: unrecognized selector sent to instance 0x600002a1a440"

What is the name of the relationship between DealersEntity and CarsEntity? Unknown selector usually means you have the wrong name.

Looking at your data model I recognized that how the relationship is set up every dealer would use the exactly same identical car and not a own instance of it. I'm not sure if this is what you want.

3      

hi,

this expression is suspect:

let allCars = dealers.cars?.allObjects as? [CarsEntity] ?? []

when computed for a BrandsEntity, dealers is (from what i can tell) an NSSet; an NSSet does not have a property named cars.

consider doing something more direct, and leverage what you already have. for a given object brand of type BrandsEntity, you must collect all the cars from all the dealers. this computed property do it:

extension BrandsEntity {

  var carsArray: [CarsEntity] {
    var allCars = [CarsEntity]()
    for dealer in dealersArray {
        allCars += dealer.carsArray // my original allCars.append(dealer.carsArray) doesn't make sense
    }
    return allCars
  }

}

example usage:

print("the number of cars for \(brand.wrappedBrandsName) is \(brand.carsArray.count)")

hope that helps,

DMG

3      

@Hatsushira

What is the name of the relationship between DealersEntity and CarsEntity? Unknown selector usually means you have the wrong name.

The name of the relationship is "cars" so I think that this should be correct, no?

Looking at your data model I recognized that how the relationship is set up every dealer would use the exactly same identical car and not a own instance of it. I'm not sure if this is what you want.

I'm not exactly sure how I would do that? Could you give me a nudge in the right direction here?

So far I got the issue solved with a slight variation of @delawaremathguy 's solution, which I'll post below.

2      

@delawaremathguy Your solution didn't work in the first place, but it greatly helped me getting there!

I think when I tried to call it in BrandsView, I got an error like "Instance method 'appendInterpolation(_:formatter:)' requires that '[CarsEntity]' inherit from 'NSObject'.

Searching for this issue told me to wrap the call as a String like this:

Text(String("(\(brand.carsArray))"))

With that in place, my List showed both Brands I'd saved together with all cars per brand as well as core data addresses (i guess?) like <x-coredata: //2E58640.....> all in one huge cell per brand.

Anyway, I didn't want to give up so I finally worked my way to the following solution:

   var carsArray: Int {
      var allCars = [CarsEntity]()
      for dealer in dealersArray {
          allCars += dealer.carsArray
      }
        let reservedCars = allCars.filter(\.reserved)
        let filteredCars = (allCars.count - reservedCars.count)
      return filteredCars
    }

which I could call without any issues:

Text("(\(brand.carsArray))")

As you can see I had to change the type of carsArray to Int in order to be able to calculate the difference between allCars and reservedCars.

This shouldn't be an issue, right? It's working just fine but I'm not sure if I may be missing something that could hit me later on.

Last but not least: Many, many thanks! This has helped me tremendously both with this very issue and also getting a better understanding of how to work with core data relationships!

2      

@BenjiTerv Sorry, I was away over the weekend.

I'm not exactly sure how I would do that? Could you give me a nudge in the right direction here? So far I got the issue solved with a slight variation of @delawaremathguy 's solution, which I'll post below.

Basically, you would need an entity between CarEntity and DealerEntity . It should be a m:n relationship. One Car (basically the model) can be offered by more than one dealer (if you have more than one dealer of one brand). In this entity you could store more additional data f.e. price rate etc.

There are other possible solutions but this depends on what use case you want to model and which problem your app should solve.

3      

No worries @Hatsushira!

Sounds like an idea to play around with! Thanks!

2      

Thanks for this, its worked

2      

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!

Reply to this topic…

You need to create an account or log in to reply.

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.