I have an app that displays a section title followed by the detail items followed by the next section and its details. Everything works fine, but I would like to rearrange the order in which the sections are shown. The problem is that I need to order the core data by the report-id then status then the date to get the correct detail items to show under the proper section.
let sortDescriptor1 = NSSortDescriptor(key: #keyPath(Item.report.id), ascending:true)
let sortDescriptor2 = NSSortDescriptor(key: #keyPath(Item.report.status), ascending:true)
let sortDescriptor3 = NSSortDescriptor(key: #keyPath(Item.report.dateStarted), ascending:false)
let sortDescriptor4 = NSSortDescriptor(key: #keyPath(Item.date), ascending:true)
How can I change the sort/display order while still maintaining the proper relationship between the section (report) and the detail items associated with it? The report.id is a UUID so currently the reports end up in random order.
A fetchedResultsController has a property sectionNameKeyPath which can be used to group items togethers. This only works if the sectionNameKeyPath groups the items in the same order that they are sorted. For example: you can sort by date and then group by hour or week or any other time based grouping, but not by name. In your case you want the sections to be sorted in a way that does not not match how the items are grouped. There may be some clever solution for your particular situation, but since you didn't give any details I can only give a general solution.
The indexPath that is returned from a fetchedResultsController is really useful for interoperability with a tableView or collectionView. But it does not have to be a one-to-one relationship. For example you could have a setup where one section points to one fetchedResultsController and another section points to a different one. The key in doing this setup is to make sure to not confuse the fetchedResultsController indexPath with your collectionView indexPath. Generally I find having a separate object to manage converting the indexPath the easily way to keep it straight.
Create a separate object that sorts the sections after the fetchedResultsController does a fetch (and after a section is inserted or deleted). Inside the indexPathsManager have a dictionary the maps between the "local" indexPath and the fetchedResultsController indexPath. Make sure to sure use this object EVERY TIME you use indexPaths in the viewController. Also make sure to convert the indexPaths when you update the view after the fetchedResultsController delegates that there is a change. And updating the indexPathsManager when any sections are inserted or deleted.
TL;DR Sort the sections of the fetchedResultsController after the fetch and convert your tableView's indexPath to fetchedResultsController indexPaths.
Related
For now I'm sorting all my sections with the next sort descriptors:
let sortDescriptors = [NSSortDescriptor(key: "category", ascending: true), NSSortDescriptor(key: "name", ascending: true)]
let request = DBOShopCardBox.createFetchRequest(predicate: predicate, sortDescriptors: sortDescriptors)
fetchedResultsController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: DBContext.defaultContext, sectionNameKeyPath: "categoryOrderIndex", cacheName: nil) as? NSFetchedResultsController<NSManagedObject>
But now I want to the next:
I have an additional column, type. It has 3 values box, folder, free. I want to add one more(+1) section with ONLY records where type ==folder` in it and sort them alphabetically.
I was trying to add one more sort descriptor into my array like:
NSSortDescriptor(key: "type", ascending: true)
but it will sort ALL of them by type. But I need to get the records with type == folder, put them into the separate section and just later sort them. I'm confused now. Maybe someone can give me some hints or help me?
Thanks in advance!
That's not what NSFetchedResultsController does. It uses the result of a fetch (hence the name) to control (hence the name) the table view by serving as its model object. If you want the table view to express something the fetched results controller doesn't express (a different number of sections, etc.), you'll have to implement that yourself, probably as part of the data source methods (or you might subclass NSFetchedResultsController; I'm not sure about that part though). You can consult the fetched results controller's fetchedObjects to see what it fetched, but you will have to dictate explicitly that there is an extra section and what goes in it.
NSFetchedResultsController is a really cool class. It fetches from core data and updates the viewController when stuff is added, deleted, changed, or moved. It assigns indexPaths for every object it is monitoring so it can interface easily with a collectionView or a tableView. But you don't need to have a 1-to-1 relationship between a fetchedResultsController and a collectionView. You can have two fetchedResultsControllers - one for sections 0-3 and a different one for section 4.
The big challenge with this setup is to keep track of what kinds of indexPaths you are dealing with; the fetchedResultsControllers' indexPaths are no longer a one-to-one relation to the collectionView's. Make a few methods to convert from different indexPath spaces and back and make sure to call them correctly whenever you see an indexPath.
I simply use NSFetchedResultsController with standard approach, but it quite a lot for me to find out why it didn't work. For some reason while adding new records and creating another section the same time, for first section
fetchedResultsController.sections![section].objects!.count
returned wrong number of objects, opposite to:
fetchedResultsController.sections![section].numberOfObjects
Is there any difference between them?
Related question: NSFetchedResultsController inserts the same cell into two sections only when controller is about to insert another section
I have a problem that's being plaguing me for a few days now.
I have an array called countries. This array contains a custom object which holds data such as Country, City, ip, status etc.
I need to present these objects in a tableview sectioned by the property "country".
This means that if I have two custom objects both with the country "Australia" I need to add it to a section called "Australia" and if I have 5 objects with the country "Spain" I need those 5 objects to be in the Spain section.
I need to find a way to make sure that even if an object is added with a different country it goes under a section with it's country name country name.
I've tried a variety of things but nothing has come even close to fixing this.
The reason why this is dynamic is because I don't know how many objects are going to be in each section. This means the number of objects in each section can change.
I used this code to sort the array alphabetically I just need to put each object into a section based on the property "Country" I do not know how many sections there will be or how many objects will be in each section.
self.countries = self.countries.sorted { $0.country < $1.country }
Could anyone give me a hand?
edit: added my cellForRowAtIndexPath.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if recievedData == true {
var cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! CountryListCell
var serverInfo = countries[indexPath.row] as ServerInfoObject
cell.countryTitle.text = serverInfo.country
cell.flagImage.image = UIImage(named: serverInfo.flag)
cell.serverInfo = serverInfo
return cell
}
return CountryListCell()
}
So you need to sort you servers into a dictionary. E.G [String:[Server]] the string being the section name (country). So an example dict ["Albania":[serv1,server2,...]]
You can simply do this by sorting through the server array (I assume you can figure this out).
You will also need to save an array of your section titles so that you can access it in cellForRowAtIndexPath. For example ["Albania","Brazil","Cuba","Germany","Russia"]
So basically when in cellForRowAtIndexPath you access the current country with
var country = yourSectionArray[indexPath.section]
Then access the array of servers that are valid to that country through your dictionary like so:
var serversOfCurrentSection = yourDictionary[country] as [Server]
Then to get the current server for the index just do
var server = serversOfCurrentSection[indexPath.row]
To get the number of objects in each section:
var country = yourSectionArray[indexPath.section]
yourDictionary[country].count
Did you try to use NSPredicate to help you filter your array by country name? I think you can either split your servers into an array of array on the run or at the beginning of load, depending on whether the list updates.
In numberOfRowsInSection:, just count the rows of the sub array at index section.
Similarly, in cellForRowAtIndexPath:, use your sorted arrays to get info from your custom class.
I had to deal with something similar with sorting contacts. Hopefully this helps!
There are lots of ways to do this.
Table views need stable data in order to work. If you change your data model, you need to tell your table view to reload.
I'd probably store the records 2 ways: In a flat array, and then in an array by countries where each entry in that array was an array of the records for that country. Collection objects like arrays save the objects inside them by reference, so there's not a lot of overhead in storing the same records in multiple arrays.
That makes creating your sectioned table view very clean.
You could sort your flat array of record by country, and then do a pass through the sorted array, building your arrays of records by country. If you need your records sorted by some other criteria within a country you might want to use an NSPredicate to do the sorting rather than sorted, since it can take an array of sort keys.
If the user can enter a new record at any spot, you could collect the data for that new record, then find the spot where it belongs in the sorted array, insert it at that spot, and then rebuild your 2 dimensional array of arrays from the sorted flat array. That would avoid re-sorting the entire array, which can be slow for large arrays of data.)
I've got a bunch of NSManagedObjects that are divided into sections in a UITableView.
[Section Header 0]
[NSManagedObject]
[NSManagedObject]
[NSManagedObject]
[Section Header 1]
[NSManagedObject]
[Section Header 2]
[NSManagedObject]
[NSManagedObject]
[NSManagedObject]
The user can edit the objects on a per-section basis, including deleting them. Say I delete the [NSManagedObject] in [Section 1] above. This updates my NSFetchedResultsController and removes both the row and the section, including the section header.
Is there a way to keep a blank section with any objects? I've considered putting in some code which sets a minimum count for sections, but the issue is that it won't be in sync with the NSFetchedResultsController. Adding an extra section would just add it to the bottom, but I'd like to add it after the first section, regardless of whether there are one or more sections after.
The objects are all server-driven, so I'd like to make so I won't have a lot of hardcoded sections in there.
Thanks.
You should include an attribute or a relationship in your data model to keep track of the sections. Seeing that you group your user, there must be a criterion that you can model in your data.
Then initialize the NSFetchedResultsController to with the appropriate sectionNameKeyPath. Use Apple's plain vanilla code to display section information, if any.
What you actually display in each section is still up to you. (It's what you put in your datasource methods). But at least you can have a consistent system of sections and grouping of objects.
I have a list of items that are added to a shopping list. Each item has a department, and a flag to say whether it has been collected or not.
I would love to use a single table view to display this. The problem is, I need to group the sections by department, and then add a final additional section at the bottom for collected items (regardless of departments).
I can create a fetchedResultsController filtering out collected = yes and sorting by department easily enough. But I need this additional section appended with those items where collected = yes.
Should I be trying to create a fetchedResultsController with a sophisticated query to do this? Or try to manually add a section and some rows? If so, from where? A second FRC? Is it possible to have two FRCs feed into a single tableview?
Ben,
The NSFetchedResultsController is designed to provide a single fetch to fill a table view. But you have the choice of how that data is fed into the table view. You can manually translate the fetched items where ever you want. You can add sections, extra rows, whatever. That is why they surface the update of the model in the delegate. They give you the chance to make those adjustments.
Hence, if you construct your model such that a single fetch does the job, then you have a great deal of flexibility on how you use it.
Andrew
sectionKeypathName is available which takes a parameter to decide how many sections should be there. If you pass nil, there will be only one section but if you pass some sectionKeypathName in FetcherResultsController and set the first sortDescriptor same as the sectionKeyPathName, you will get different sections in fethedResultsControllerDelegate. That sectionKeypathName must be attribute in an entity you are fetching.
let sortDescriptor = NSSortDescriptor(key: "sectionView", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: mainContext, sectionNameKeyPath: "sectionView", cacheName: nil)