BigBoy1337
BigBoy1337

Reputation: 5003

How to setup a collectionView (or TableView) with NSFetchedResultsController and multiple fetched entities

I have read here that the way to fetch multiple entities with 1 NSFetchedResultsController is to use a parent child inheritance model. I have such a model:

https://i.sstatic.net/Cq1ZB.jpg

As you can see, TextPost,VideoPost, and ImagePost all have Skill as a parent entity. I am trying to make a single collectionView for which all three children show up. I am a little confused as to how to set the delegate methods though...

Here is the code for the view controller

class Timeline2ViewController: UIViewController {

    @IBOutlet var postsCollectionView: UICollectionView!
    var skillName: String?

    fileprivate lazy var skillFetchedResultsController: NSFetchedResultsController<Skill> = {
        let appDelegate =
            UIApplication.shared.delegate as? AppDelegate
        let managedContext =
            appDelegate?.persistentContainer.viewContext
        let request: NSFetchRequest<Skill> = NSFetchRequest(entityName: "Skill")
        request.predicate = NSPredicate(format: "name == %@", self.skillName!)
        let timeSort = NSSortDescriptor(key: "timeStamp", ascending: true)
        request.sortDescriptors = [timeSort]

        let skillFetchedResultsController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: managedContext!, sectionNameKeyPath: nil, cacheName: nil)
        return skillFetchedResultsController
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        do {
            try skillFetchedResultsController.performFetch()
        } catch let error as NSError {
            print("SkillFetchError")
        }
    }
}
extension Timeline2ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        guard let sectionInfo = skillFetchedResultsController.sections?[section] else { return 0 }
        return sectionInfo.numberOfObjects
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = postsCollectionView.dequeueReusableCell(withReuseIdentifier: "pcell", for: indexPath) as? PostViewCell else { return fatalError("unexpected Index Path")
        }
        let post = skillFetchedResultsController[indexPath.row] /* This is the line im not sure about
        cell.background
        return cell
    }
}

Since only 1 entity is actually, returned, I am not sure how to access an element at a specific index path. For instance skillFetchedResultsController[indexPath.row] would I think only have 1 entity - the skill itself. I really want to be accessing its children. Do I have to somehow subclass skillFetchedResultsController and return only the children Im interested in?

Edit: with @pbasdf suggestions - I have this model:

enter image description here

Now when I create an entity like so:

        guard let appDelegate =
         UIApplication.shared.delegate as? AppDelegate else {
             return
         }
         let managedContext = appDelegate.persistentContainer.viewContext
        let textPost = NSEntityDescription.insertNewObject(forEntityName: "TextPost", into: managedContext) as! TextPost
        textPost.text = "test text post"
        try! managedContext.save()

and I setup my fetched results controller to look at "Post2" like so:

    fileprivate lazy var skillFetchedResultsController: NSFetchedResultsController<Post2> = {
        let appDelegate =
            UIApplication.shared.delegate as? AppDelegate
        let managedContext =
            appDelegate?.persistentContainer.viewContext
        let request: NSFetchRequest<Post2> = NSFetchRequest(entityName: "Post2")
//        request.predicate = NSPredicate(format: "skill = %@", self.skill!)
        let timeSort = NSSortDescriptor(key: "timeStamp", ascending: true)
        request.sortDescriptors = [timeSort]
        let skillFetchedResultsController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: managedContext!, sectionNameKeyPath: nil, cacheName: nil)
        return skillFetchedResultsController
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        do {
            try skillFetchedResultsController.performFetch()
        } catch _ as NSError {
            print("SkillFetchError")
        }

    }

I see no returned results in:

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    guard let sectionInfo = skillFetchedResultsController.sections?[section] else { return 0 }
    return sectionInfo.numberOfObjects
}

Do I somehow need to link the two? Like create the Post object and the TextPost object at the same time? When I try and fetch TextPost objects directly it works.

Upvotes: 1

Views: 84

Answers (1)

pbasdf
pbasdf

Reputation: 21536

I think the problem lies in your model. You should create a Post entity, and make it the parent entity of TextPost, VideoPost, and ImagePost. If your subentities have any attributes in common, move them from the subentities to the Post entity. Then establish a one-many relationship from Skill to Post.

Your FRC should fetch Post objects (which will by default include all the subentities), using a predicate if necessary to restrict it to those Post objects related to your desired Skill object, eg.

NSPredicate(format:"skill.name == %@",self.skillName!)

Upvotes: 1

Related Questions