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()
Related
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?
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.
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 am trying to group section 0 my tableView by the "category" attribute of an item.
Example:
Drinks: (item.category = header)
Dr. Prepper
Coke
Pepsi
Kitchen: (item.category = header)
Pots
Pans...etc.
CrossOff(header)
items
I still want section1 to be the item.slcross (or the last section if each group has to be their own section...and it doesn't have to be grouped).
When I change the secondarySortDescriptor key from "slitem" to "slcategory" and use the sectionHeader code below, it returns "nil". I also tried using
let sectionHeader2 = "\(item.valueForKeyPath("slcategory"))" but still had the same effect with both "slitem" and "slcategory".
Do I have to use a sort descriptor for each category or is there a way to make it pull the category attribute for the item and group the like categories together?
FRC set up:
let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var frc : NSFetchedResultsController = NSFetchedResultsController()
var selectedItem : List?
func itemFetchRequest() -> NSFetchRequest{
let fetchRequest = NSFetchRequest(entityName: "List")
let primarySortDescription = NSSortDescriptor(key: "slcross", ascending: true)
let secondarySortDescription = NSSortDescriptor(key: "slitem", ascending: true)
fetchRequest.sortDescriptors = [primarySortDescription, secondarySortDescription]
fetchRequest.predicate = NSPredicate(format:"slist == true")
return fetchRequest
}
func getFetchRequetController() ->NSFetchedResultsController{
frc = NSFetchedResultsController(fetchRequest: itemFetchRequest(), managedObjectContext: moc, sectionNameKeyPath: "slcross", cacheName: nil)
return frc
}
TableViewHeaders:
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String?{
let entityDescription = NSEntityDescription.entityForName("List", inManagedObjectContext: moc)
let item = List(entity: entityDescription!, insertIntoManagedObjectContext: moc)
let sectionHeader = "\(item.slcategory)"
let sectionHeader1 = "Items in Cart - #\(frc.sections![section].numberOfObjects)"
if (frc.sections!.count > 0) {
let sectionInfo = frc.sections![section]
if (sectionInfo.name == "0") {
return sectionHeader2
} else {
return sectionHeader1
}
} else {
return nil
}
}
There are a few ways to do this, but probably the easiest is to add a new method to your NSManagedObject subclass. The method returns a string which will be used as the title for the section; so if slcross is false, it returns the value of slcategory, and if slcross is true it returns "True":
func sectionIdentifier() -> String {
if (self.slcross) {
return "True"
} else {
return "\(self.slcategory)"
}
}
(Note this code goes in your List class definition, not your view controller).
In the view controller, use this sectionIdentifier as the sectionNameKeyPath for your FRC:
frc = NSFetchedResultsController(fetchRequest: itemFetchRequest(), managedObjectContext: moc, sectionNameKeyPath: "sectionIdentifier", cacheName: nil)
For that to work, it is imperative that the objects are sorted correctly: first by slcross, then by slcategory:
let primarySortDescription = NSSortDescriptor(key: "slcross", ascending: true)
let secondarySortDescription = NSSortDescriptor(key: "slcategory", ascending: true)
Finally, amend your titleForHeaderInSection to use the section name (which the FRC gets from sectionIdentifier), but replacing the "True" with your computed string:
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String?{
if (frc.sections!.count > 0) {
let sectionInfo = frc.sections![section]
if (sectionInfo.name == "True") {
return "Items in Cart - #\(sectionInfo.numberOfObjects)"
} else {
return sectionInfo.name
}
} else {
return nil
}
}
From Apple Docs...
When you initialize the fetch results controller, you provide four parameters: .....
....Optionally, a key path on result objects that returns the section name. The controller uses the key path to split the results into sections (passing nil indicates that the controller should generate a single section).....
After creating an instance, you invoke performFetch: to actually execute the fetch.
If you want to sort the sections by category then you need to make the sectionNameKeyPath argument in the NSFetchedResultsController init to be your "category" property instead of your "slcross" property.
When I've used this in the past I've also included that same property that I've set as the sectionNameKeyPath in my sort descriptors, but not sure if that is actually needed or not.
Hope I have answered your question?
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.