I currently have two managed objects for Core Data that has one-to-many relationship.
Goal
extension Goal {
@nonobjc public class func createFetchRequest() -> NSFetchRequest<Goal> {
return NSFetchRequest<Goal>(entityName: "Goal")
}
@NSManaged public var title: String
@NSManaged public var date: Date
@NSManaged public var progress: NSSet?
}
Progress
extension Progress {
@nonobjc public class func createFetchRequest() -> NSFetchRequest<Progress> {
return NSFetchRequest<Progress>(entityName: "Progress")
}
@NSManaged public var date: Date
@NSManaged public var comment: String?
@NSManaged public var goal: Goal
}
For every goal, you can have multiple Progress
objects. The problem is when I request a fetch for Progress
with a particular Goal
as the predicate, nothing is being returned. I have a suspicion that I'm not using the predicate properly.
This is how I request them.
Goal
for a table view controller:var fetchedResultsController: NSFetchedResultsController<Goal>!
if fetchedResultsController == nil {
let request = Goal.createFetchRequest()
let sort = NSSortDescriptor(key: "date", ascending: false)
request.sortDescriptors = [sort]
request.fetchBatchSize = 20
fetchedResultsController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: self.context, sectionNameKeyPath: "title", cacheName: nil)
fetchedResultsController.delegate = self
}
fetchedResultsController.fetchRequest.predicate = goalPredicate
do {
try fetchedResultsController.performFetch()
} catch {
print("Fetch failed")
}
Detail
view controller:if let vc = storyboard?.instantiateViewController(withIdentifier: "Detail") as? DetailViewController {
vc.goal = fetchedResultsController.object(at: indexPath)
navigationController?.pushViewController(vc, animated: true)
}
Progress
using the Goal
as the predicate from Detail
view controller:var goal: Goal!
let progressRequest = Progress.createFetchRequest()
progressRequest.predicate = NSPredicate(format: "goal == %@", goal)
if let progress = try? self.context.fetch(progressRequest) {
print("progress: \(progress)")
if progress.count > 0 {
fetchedResult = progress[0]
print("fetchedResult: \(fetchedResult)")
}
}
Goal
is being returned properly, but I get nothing back for Progress
. I've tried:
progressRequest.predicate = NSPredicate(format: "goal.title == %@", goal.title)
or
progressRequest.predicate = NSPredicate(format: "ANY goal == %@", goal)
but still the same result.
Following is how I set up the relationship:
// input for Progress from the user
let progress = Progress(context: self.context)
progress.date = Date()
progress.comment = commentTextView.text
// fetch the related Goal
var goalForProgress: Goal!
let goalRequest = Goal.createFetchRequest()
goalRequest.predicate = NSPredicate(format: "title == %@", titleLabel.text!)
if let goal = try? self.context.fetch(goalRequest) {
if goal.count > 0 {
goalForProgress = goal[0]
}
}
// establish the relationship between Goal and Progress
goalForProgress.progress.insert(progress)
// save
if self.context.hasChanges {
do {
try self.context.save()
} catch {
print("An error occurred while saving: \(error.localizedDescription)")
}
}
Actually you don't need to refetch the data. You can get the progress from the relationship
Declare progress
as native Set
@NSManaged public var progress: Set<Progress>
In DetailViewController
delete the fetch code in viewDidLoad
and declare
var progress: Progress!
In the first view controller filter the progress
let goal = fetchedResultsController.object(at: indexPath)
if let vc = storyboard?.instantiateViewController(withIdentifier: "Detail") as? DetailViewController,
let progress = goal.progress.first(where: {$0.goal.title == goal.title}) {
vc.progress = progress
navigationController?.pushViewController(vc, animated: true)
}
And consider to name the to-many relationship in plural form (progresses
)