Search code examples
core-datanspredicatensfetchedresultscontrollerswift3nssortdescriptor

NSFetchedResultsController and to-many relationship not working


Ok, I was searching and trying in that case for the last 1-2 weeks and I didn't get it work. I would be able to achieve what I want without NSFRC but for performance reasons and convienience I would like to do it with the NSFRC. So, I have a DataModel with 2 Entities - see the picture to-many Relationship

There is one Account and one account can have many accountchanges - which is quite obvious. So I want to be able to choose an Account and then show all AccountChanges for that specific Account. So far I was able to get the Account and also accessing the NSSet in cellForRow Function but I am not getting the correct sections and numberOfRowsInSection - this is the main issue.

Here is some code:

    func numberOfSections(in tableView: UITableView) -> Int {

    print("Sections : \(self.fetchedResultsController.sections?.count)")
    if (self.fetchedResultsController.sections?.count)! <= 0 {
        print("There are no objects in the core data - do something else !!!")
    }
    return self.fetchedResultsController.sections?.count ?? 0
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    print("Section Name")
    print(self.fetchedResultsController.sections![section].name)
    let sectionInfo = self.fetchedResultsController.sections![section]
    print("Section: \(sectionInfo) - Sections Objects: \(sectionInfo.numberOfObjects)")
    return sectionInfo.numberOfObjects
}

There are some print statements which are only for information!

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let myCell = myTable.dequeueReusableCell(withIdentifier: "myCell")! as UITableViewCell
    let accountBalanceChanges = self.fetchedResultsController.object(at: indexPath)
    print("AccountBalanceChanges from cell....")
    print(accountBalanceChanges)

    let details = accountBalanceChanges.accountchanges! as NSSet
    print("Print out the details:")
    print(details)
    let detailSet = details.allObjects
    let detailSetItem = detailSet.count // Just for information!


    let myPrint = detailSet[indexPath.row] as! AccountChanges
    let myVal = myPrint.category

    myCell.textLabel?.text = myVal

    return myCell
}

So, I am able to get the data but always only one item and not the whole set - I guess due to the fact that the sections/ numberOfRows are wrong.

Here is my NSFRC

    var fetchedResultsController: NSFetchedResultsController<Accounts> {
    if _fetchedResultsController != nil {
        return _fetchedResultsController!
    }
    let fetchRequest: NSFetchRequest<Accounts> = Accounts.fetchRequest()
    // Set the batch size to a suitable number.
    fetchRequest.fetchBatchSize = 20
    // Edit the sort key as appropriate.

    let sortDescriptor = NSSortDescriptor(key: "aName", ascending: false)
    fetchRequest.sortDescriptors = [sortDescriptor]


    let predicate = NSPredicate(format: "(ANY accountchanges.accounts = %@)", newAccount!)
    fetchRequest.predicate = predicate
    // Edit the section name key path and cache name if appropriate.
    // nil for section name key path means "no sections".

    let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.coreDataStack.context, sectionNameKeyPath: nil, cacheName: nil)
    aFetchedResultsController.delegate = self
    _fetchedResultsController = aFetchedResultsController

    do {
        try _fetchedResultsController!.performFetch()
    } catch {
        // Replace this implementation with code to handle the error appropriately.
        // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
        let nserror = error as NSError
        fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
    }

    return _fetchedResultsController!
}

I am assuming it is the SortDescriptor or the predicate - or maybe both?

Any help or at least directions are well appreciated. I already tried many different approaches but none was giving me the correct results.


Solution

  • I would do the opposite, I mean using the FRC to fetch all the changes for an account with a certain Id, and use the following predicate:

    let predicate = NSPredicate(format: "accounts.aId = %@", ACCOUNTID)
    

    or

    let predicate = NSPredicate(format: "accounts = %@", account.objectID)
    

    I would rename Accounts entity to Account and same for the relationship since it's a to-one relationship. That's assuming you have a table view with all the accounts and when you click on one it gives you back its changes.

     var fetchedResultsController: NSFetchedResultsController<AccountChanges> {
        if _fetchedResultsController != nil {
            return _fetchedResultsController!
        }
        let fetchRequest: NSFetchRequest<AccountChanges> = AccountChanges.fetchRequest()
        // Set the batch size to a suitable number.
        fetchRequest.fetchBatchSize = 20
        // Edit the sort key as appropriate.
    
        let sortDescriptor = NSSortDescriptor(key: "aName", ascending: false)
        fetchRequest.sortDescriptors = [sortDescriptor]
    
    
        let predicate = NSPredicate(format: "accounts.aId = %@", ACCOUNTID)
        fetchRequest.predicate = predicate
        // Edit the section name key path and cache name if appropriate.
        // nil for section name key path means "no sections".
    
        let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.coreDataStack.context, sectionNameKeyPath: nil, cacheName: nil)
        aFetchedResultsController.delegate = self
        _fetchedResultsController = aFetchedResultsController
    
        do {
            try _fetchedResultsController!.performFetch()
        } catch {
            // Replace this implementation with code to handle the error appropriately.
            // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
            let nserror = error as NSError
            fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
        }
    
        return _fetchedResultsController!
    }
    

    Cheers