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.
Related
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.
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
Here's the scenario that I'm interested in:
1) I'm using CoreData to store my data for my app
2) I'm using a UITableView to show that data to my user
My question is whether or not I should use an [NSManagedObject] as the data for the UITableView.
Obviously it will work just fine to do it this way, but a part of make feel like it's not best practice to do this. I could use a dictionary or an array or something else to store the data and then when it's time to save, I could save that array to CoreData.
Is there any reason that one of these approaches is better than the other?
You should almost certainly use NSManagedObjects as the data for your table view, if only to save yourself the hassle of transforming arrays and dictionaries into/from NSManagedObjects in order to save/load from CoreData.
If by [NSManagedObject] you mean an array of NSManagedObjects, that is certainly possible. But best practice when using CoreData with UITableViews is to use an NSFetchedResultsController. The two main advantages are:
Easy processing of table view sections. Specify a sectionNameKeyPath and corresponding sort descriptor, and the FRC will determine which section each object should appear in, assigning objects to indexPaths accordingly.
Automatic processing of insert/update/deletes. There is boilerplate code which will enable the FRC to update the table view whenever objects are added, amended or deleted.
One other advantage to using NSManagedObjects directly, rather than arrays and dictionaries, is memory management: NSManagedObjects are fetched as "faults" initially and their properties will be populated only as required, reducing memory requirement compared to arrays and dictionaries which would presumably have to be fully populated from the outset.
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 have a view that contains a tableView. I'm using NSFetchedResultsController to display my results from Core Data.
I'd like to add a UISearchController (not a UISearchDisplayController as this one is deprecated in iOS 8) but I don't know how to link them.
Should I have only one NSFetchedResultsController or two?
I guess there is a way to fetch all the data with the NSFetchedResultsController and then just sort them according to the UISearchController am I wrong?
Thanks for the help you can give me on this.
A few thoughts...
When you create the resultsController for the searchController, you can pass to it the data that you wish to search. If you are using an NSFetchedResultsController in your main table, you could pass the fetchedObjects array. Then in response to changes to the search text, you filter the array (you could use a predicate and filteredArrayUsingPredicate to create a separate array with the search results, or you could iterate through the array to build it). The disadvantages of this route are that (unless you implement it manually) the search results will not be broken into separate sections, and the search results will not update automatically if the underlying data changes (eg. on a background thread).
I guess you could have a second NSFetchedResultsController: that would facilitate using sections, and could potentially permit the results to be updated automatically (using the delegate methods) if your data is being updated in the background, for example. But I would be nervous of the complexity that it introduces.
Another option, if you opt to apply the search in situ (i.e. specify resultsController = nil), would be to use the search criteria to update the NSFetchedResultsController itself (i.e. amending the underlying predicate and reperforming the fetch). That way your search table looks and feels exactly like the main table (including sections, if you use that) but the rows displayed obviously reduce as the search criteria become finer. This option needs care to ensure that the FRC is properly rebuilt, and might be unacceptable performance-wise, if you have a large dataset.