Maybe the title of question is not apriopriate, but here is what I need to achieve with my NSFetchedResultsController:
private func setupOnceFetchedResultsController() {
if fetchedResultsController == nil {
let context = NSManagedObjectContext.MR_defaultContext()
let fetchReguest = NSFetchRequest(entityName: "WLComment")
let createdAtDescriptor = NSSortDescriptor(key: "createdAt", ascending: true)
fetchReguest.predicate = NSPredicate(format: "item.identifier = %d", item.identifier)
fetchReguest.sortDescriptors = [createdAtDescriptor]
fetchReguest.fetchLimit = 10
fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchReguest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
fetchedResultsController.delegate = self
try! fetchedResultsController.performFetch()
tableView.reloadData()
}
}
Suppose I have about 100 comments. The oldest on the top, and the latest on the bottom. There is no problem when I need to display all of them, but here I need only 10 of them. The above NSFetchedResultsController will display the first 10 of 100 messages, but I need to display the latest 10.
Is there a way to let it know what I need?
Depending on how you are going to use the data later on, you can choose from at least two options:
revert the sort descriptor, will return the end of the sorted list with reverted order;
get the number of items first and set the appropriate fetchOffset, will return the required number of items, ordered as needed.
Related
I am using NSFetchedResultsController to display a list of workouts grouped in sections with the date on which they were completed.
Graphically it can be represented as this:
JAN 20, 2020
Workout 1
Workout 2
JAN 21, 2020
Workout 1
Workout 2
Each section display's the total distance covered by the user on that day.
Whenever I update any workout (change it's distance) the tableView cell's are updated but the section which contains the total distance for day doesn't change.
I have to reload the tableView to get the changes in effect.
The delegate method in NSFetchedResultsControllerDelegate : controller(_:didChange:atSectionIndex:for:) is only called when a new section is added or removed.
I have searched many SO questions but haven't found any solution. Would be great if any one pointed me in the right direction regarding this.
Below is how I am creating my NSFetchResultsController
lazy var fetchedResultsController: NSFetchedResultsController<CD_Run> = {
let fetchRequest = NSFetchRequest<MY_ENTITY>(entityName: "MY_ENTITY")
let sectionSortDescriptor = NSSortDescriptor(key: "daySectionIdentifier", ascending: false)
let startTimeSortDescriptor = NSSortDescriptor(key: "startTime", ascending: false)
fetchRequest.sortDescriptors = [sectionSortDescriptor, startTimeSortDescriptor]
fetchRequest.fetchBatchSize = 200
let controller = NSFetchedResultsController(fetchRequest: fetchRequest,
managedObjectContext: CoreDataManager.shared()._managedObjectContext,
sectionNameKeyPath: "daySectionIdentifier",
cacheName: nil)
controller.delegate = self
return controller
}()
I am at the stage of optimizing my core data code,
I am making a messaging app, and I only want to load 50 messages at a time.
However, looking at the timeprofile Data below, the fetchrequest loads all the messages at once. Afterwards, it loads all the messages in batches of 50 without even scrolling. So it's like not even doing anything.
Any help would be greatly appreciated.
lazy var fetchedResultsControler: NSFetchedResultsController = {
let fetchRequest = NSFetchRequest(entityName: "Mesages")
fetchRequest.fetchBatchSize = 20
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "timestamp", ascending: true)]
fetchRequest.predicate = NSPredicate(format: "user.id = %#", self.friend!.id!)
let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
let frc = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: moc, sectionNameKeyPath: nil, cacheName: nil)
frc.delegate = self
return frc
}()
override func viewDidLoad() {
super.viewDidLoad()
do {
try fetchedResultsControler.performFetch()
} catch let err {
print(err)
}
self.fetchData = fetchedResultsControler.fetchedObjects as! [Mesages]}
This is the CoreData time profiling data:
That's correct behavior, per the NSFetchRequest documentation:
When the fetch is executed, the entire request is evaluated and the identities of all matching objects recorded, but no more than batchSize objects’ data will be fetched from the persistent store at a time. The array returned from executing the request will be a proxy object that transparently faults batches on demand. (In database terms, this is an in-memory cursor.
If, instead, you want explicit control of the fetch grouping, use a combination of fetchLimit and fetchBatchSize instead. But at that point you're fighting the features of NSFetchedResultsController.
What are you really trying to do with the fetchData array?
I'm teaching myself to programme and have thought up this project for myself to learn. I'm having trouble with my code, I was able to save it correctly and load the first state population TVC. However, I'm having problems with the state and number of animals per state TVC. I want to total it per a state. So I would be adding the dogs and cats population together and get the total per a state, but it brings Alabama separately with two different population, can someone help me with this please.
the model below shows how I want it, I'm able to output to State Population correctly but now the second one.
What my code is doing for the second one is that it's getting the data from coredata but I'm using sort descriptor because I don't know any other way to pull the data.
var totalEntries : Int = 0
let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var frc : NSFetchedResultsController = NSFetchedResultsController()
func fetchRequest() -> NSFetchRequest {
let fetchRequest = NSFetchRequest(entityName: "Animals")
let sortDescriptor = NSSortDescriptor(key: "state", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
return fetchRequest
}
func getFRC() -> NSFetchedResultsController {
frc = NSFetchedResultsController(fetchRequest: fetchRequest(), managedObjectContext: moc, sectionNameKeyPath: nil, cacheName: nil)
return frc
}
override func viewDidLoad() {
super.viewDidLoad()
frc = getFRC()
frc.delegate = self
do {
try frc.performFetch()
} catch {
print("Failed to fetch data")
return
}
totalEntries = moc.countForFetchRequest(fetchRequest(), error: nil) as Int!
self.tableView.reloadData()
}
override func viewDidAppear(animated: Bool) {
frc = getFRC()
frc.delegate = self
do {
try frc.performFetch()
} catch {
print("Failed to fetch data")
return
}
totalEntries = moc.countForFetchRequest(fetchRequest(), error: nil) as Int!
self.tableView.reloadData()
}
Your problem is the fetched results controllers aren't designed to show aggregated fetch results like you desire, hence you see all the underlying data instead of the aggregate.
You could use the FRC if you cheat... Set the section name of the FRC to the state name, then you will have one section in the table per state. In the table delegate return 1 for the number of rows in each section. When configuring the cell use KVC to #sum the populations of all of the animals in that state (the rows for that section as understood by the FRC).
This is a memory and runtime inefficient solution... It could be improved by caching the calculated sums, but you're adding logic on top of bad design then.
The correct approach would be to abandon the FRC and use a simple array of dictionaries. This would be generated by changing your fetch request to return dictionary type and configuring it to calculate the sums for you. That's done with an expression something like:
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
expressionDescription.name = #"sumOfPopulations";
expressionDescription.expression = [NSExpression expressionForKeyPath:#"#sum.population"];
expressionDescription.expressionResultType = NSDecimalAttributeType;
I'm trying to develop a timetable app.
I have TableViewController which shows the classes on current day. In viewDidLoad( ) I fetch the classes with NSFetchedResultsController:
let fetchRequest = NSFetchRequest(entityName: "Lessons")
let predicate = NSPredicate(format: "startsday = '\(dateFormatter.stringFromDate(NSDate()))'", NSDate())
fetchRequest.predicate = predicate
let sortDescriptorStarts = NSSortDescriptor(key: "starts", ascending: true)
let sortDescriptorTitle = NSSortDescriptor(key: "title", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptorStarts, sortDescriptorTitle]
fetchRequest.fetchBatchSize = 30
fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: moc, sectionNameKeyPath: "sectionIdentifier", cacheName: nil)
fetchedResultsController.delegate = self
fetchedResultsController.performFetch(nil)
And when I set sectionNameKeyPath: "sectionIdentifier"(which is a transient property) they are sorted into three groups "Past", "Now", "Next" depend on current time. It is working. I have a screenshot here.
My problem is: As time goes by the sections aren't updated
The rows should move out and move in other sections and eventually they should all go into the "Past" section, and the "Now", "Next" sections should be deleted.
I can update the sectionIdentifier transient property, but my fetchedResultController doesn't want to notice the changes...
I don't want to use tableView.reloadData( ) because i want animations.
(There is a similar question, but the end of it is different. I can't get my answer.)
Thanks!
I have found a solution for this problem. It is definitely not the best but it's working nicely. (Swift 2.3)
First, I have a transient property called "sectionIdentifier".This could be "NOW", "NEXT" and "PAST" depend on the current time.I added a new persistent property next to this transient called "sectionID". This property's purpose is to tell if the lesson status (NOW, NEXT, PAST) changed. I changed my original fetch to this:
let fetchRequest = NSFetchRequest(entityName: "Lessons")
let predicate = NSPredicate(format: "startsday = '\(dateFormatter.stringFromDate(NSDate()))'", NSDate())
fetchRequest.predicate = predicate
let sortDescriptorStarts = NSSortDescriptor(key: "starts", ascending: true)
let sortDescriptorTitle = NSSortDescriptor(key: "title", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptorStarts, sortDescriptorTitle]
fetchRequest.fetchBatchSize = 30
fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: moc, sectionNameKeyPath: "sectionIdentifier", cacheName: nil)
fetchedResultsController.delegate = self
do {
try fetchedResultsController.performFetch()
} catch _ {
//Handle error
}
if let lessons = fetchedResultsController.fetchedObjects as? [Lessons] {
for lesson in lessons {
if lesson.sectionID != lesson.sectionIdentifier! {
lesson.setValue(lesson.sectionIdentifier!, forKey: "sectionID")
moc.refreshObject(lesson, mergeChanges: true)
}
}
}
After the fetch I set this persistent property to the transient's value. (last 8 line above)
In viewDidLoad() I added this line.
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(TodayViewController.update), userInfo: nil, repeats: true)
I update my transient property every second.
My update method looks like this:
func update() {
for lesson in fetchedResultsController.fetchedObjects as! [Lessons] {
if lesson.sectionID != lesson.sectionIdentifier! {
lesson.setValue(lesson.sectionIdentifier!, forKey: "sectionID")
moc.refreshObject(lesson, mergeChanges: true)
do {
try moc.save()
}
catch _ {
//Handle error
}
}
}
}
So my transient property will always give the lessons current status and the persistent one will alway give the tableviewcell's status. If there is difference I put my transient value into the persistent property and with moc.refreshobject() i will get a the proper FetchedResultControllerDelegate method and i can handle the tableview changes there.
I have the following code in my viewDidLoad()
fetchedResultsController = NSFetchedResultsController(fetchRequest: FetchRequest("Teams", key: "team_name"), managedObjectContext: managedObjectContext!, sectionNameKeyPath: nil, cacheName: nil)
fetchedResultsController?.delegate = self
fetchedResultsController?.performFetch(nil)
and this code in my TableView cellForRowAtIndex:
var cell = teamView.dequeueReusableCellWithIdentifier("team_cell", forIndexPath: indexPath) as! TeamCellSettings
if let cellContact = fetchedResultsController?.objectAtIndexPath(indexPath) as? Team {
//create cell here
}
return cell
I have read online on how to use NSPredicate, but I cannot seem to get it to work. My goal is to use a segmented control to switch between the results displayed in the UITableView based off of the contents of a row's attribute. So if my entity X had an attribute named A, A would store either a 1 or a 0, so basically I need the segmented control to switch between all the results containing a 1 in attribute A or a 0 in attribute A
My creative writing skills are not the best. Hope you can understand my goal. Thank you.
The predicate needs to be added to the fetch request, for example:
let request = NSFetchRequest(entityName: "Your entity name")
let predicate = NSPredicate(format: "A == %d", 1)
request.predicate = predicate
fetchedResultsController = NSFetchedResultsController(
fetchRequest: request,
managedObjectContext: managedObjectContext!,
sectionNameKeyPath: nil,
cacheName: nil)
What goes to the predicate depends on your goals. In a next step you will need to update the fetch request and reload the data as you change the selected item in the segmented control. In your IBAction that is called when the segmented control changes, get sender.selectedSegmentIndex and create the new predicate based on the index.
The code to refetch will look something like this:
Swift 2
fetchedResultsController.fetchRequest.predicate = NSPredicate(format: "A = %d", some_int_goes_here)
do {
try fetchedResultsController.performFetch()
}
catch let error as NSError {
NSLog("%#", error.localizedDescription)
}
catch {
fatalError()
}
tableView.reloadData()
If you do Swift 1.2, omit the error handling, of course, something like this:
fetchedResultsController.fetchRequest.predicate = NSPredicate(format: "A = %d", some_int_goes_here)
fetchedResultsController.performFetch()
tableView.reloadData()