Rearranging order of TableView cells using Core Data - ios

I have a custom UITableView class that handles animations for table view, and have tested the following code with an Array that is displayed in a table view.
tableView.didMoveCellFromIndexPathToIndexPathBlock = {(fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) -> Void in
self.objectsArray.exchangeObjectAtIndex(toIndexPath.row, withObjectAtIndex: fromIndexPath.row)
}
This works fine with a basic array, but I actually want to rearrange a managed object of a type NSSet. So in my file I have declared the following which creates returns an array of type Item which is used by the table view.
Folder class function:
func itemArray() -> [Item] {
let sortDescriptor = NSSortDescriptor(key: "date", ascending: true)
return iist.sortedArrayUsingDescriptors([sortDescriptor]) as! [Item]
}
Table View Controller declaration
var itemArray = [Item]()
itemArray = folder.itemArray() //class function to return a NSArray of the NSSET [Item]
I am trying to make it so when rearranging the cells it changes the order of the NSSet so it is saved when the app reloads, does anyone have any suggestions ?

By definition, NSSets do not have an order so there is no native way for you to preserve the order of the NSSet. I am not saying it is not possible but you cannot do it the way you are thinking.
From Apple's NSSet Documentation:
The NSSet, NSMutableSet, and NSCountedSet classes declare the programmatic interface to an unordered collection of objects.
You will need to convert your NSSet to NSMutableArray and reference that array instead of the NSSet.

Related

Remove / delete selected cells from UICollectionView causes index out of bounds [sometimes]

I have a comments array declared as: var comments: [String] which I populate it with some Strings and I also have a UICollectionView within which I present the comments. My code is the following when I try to delete the selected cells from the UICollectionView:
if let indexPathsForSelectedItems = collectionView.indexPathsForSelectedItems {
for indexPath in indexPathsForSelectedItems {
comments.remove(at: indexPath.item) //I have only one section
}
collectionView.deleteItems(at: indexPathsForSelectedItems)
}
The issue is that sometimes when I delete the selected items, it creates an out of bounds exception on the comments array.
However when I use the following approach (create a copy array and replace the original one with its copy) no problem occurs:
var indexes: [Int] = []
for indexPath in indexPathsForSelectedItems {
indexes.append(indexPath.item)
}
var newComments: [String] = []
for (index, comment) in comments.enumerated() {
if !indexes.contains(index) {
newComments.append(comment)
}
}
comments = newComments
Why is this happening?
I am using Swift 3 and XCode 8.2.1
Sorting
If you're not sure that indexPathsForSelectedItems are sorted in descending order, and hence always deletes the highest index first, you will eventually run into an out of bounds. Deleting an item will change the indices for all array elements with higher indices.
You probably want to use indexPathsForSelectedItems.sorted(by: >).

Swift 3 - Core Data - Fetched Results Controller Access Relationship Attribute

The issue being faced is my inability to access an entity's attributes when it is a relationship to the entity I am fetching.
In relation to my app, I am creating a fitness tracking app, I have a detail table view controller with my tracked activities. When I tap a cell the view segues into another View Controller to display a map of the tracked locations.
Using the fetched results controller I am fetching "Entity1". When I tap a cell 'I think' I want to segue & pass "Entity2" attribute values into another view controller. Except the relationship from "Entity1" to "Entity2" is a "To Many" relationship and in the core data properties for "Entity1",
extension Entity1
{
// instead of "Entity2" being represented as
// #NSManaged var entity2: Entity2?
//it is represented as
#NSManaged var entity2: NSOrderedSet?
}
thus i can not access "Entity2" properties.
How I fetch "Entity1":
func fetchEntity1ResultsController( _ context: NSManagedObjectContext )
{
let request: NSFetchRequest<Entity1> = NSFetchRequest( entityName: "Entity1" )
request.sortDescriptors = [ NSSortDescriptor( key: "timestamp", ascending: false ) ]
fetchedResultsController = NSFetchedResultsController( fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil )
fetchedResultsController?.delegate = self
do
{
try fetchedResultsController?.performFetch()
}
catch
{
print("Couldn't fetch results controller")
}
}
How I would attempt to access "Entity2" properties.
private func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
guard let entity1 = fetchedResultsController?.object(at: indexPath) else { return }
entity1.entity2.//No core data properties listed here
}
To conclude: My question is - How can I tap a cell that is the "Entity1" description where "Entity2" is in relationship to and access "Entity2" attribute values to display that data in my map view controller.
Since this is a to-many relationship, what you're calling entity2 is a set of multiple instances of Entity2. That's what the "many" part of "to-many" means here-- one Entity1 is related to a collection of multiple Entity2 instances. You can't access Entity2 attributes on a set, because it's a collection of more than one instance. To access Entity2 attributes you first need to select one object from the set.
How you do that depends on how your app is supposed to work. You have an NSOrderedSet, and it has a variety of options for selecting one of the objects it contains. You could ask for the first object, or the last one, or the Entity2 at a specific location in the ordered set. There are other options besides these; see the NSOrderedSet documentation for more info on them.

How to add additional informations in NSFetchedResultsController's section

I have a custom section in UITableView, it has an UIImageView and a UILabel. Using NSFetchedResultsController can i have set image based on section model. Let say section model has imageName and sectionTitle. Based on current implementation i got <NSFetchedResultsSectionInfo> as a section Object. Is this possible to get by custom object in section's object.
Thanks
I think I know what you mean. You created a NSFetchedResultsController object passing a certain entity as an argument for sectionNameKey. So your custom section is based on this entity. This is what you mean by 'section model', I guess.
If you want to access the objects in the section, you can use the following code:
//create array for demonstration purposes
var exampleArray = [[YourEntityType]]()
for sectionInfo in fetchedResultsController.sections! {
//this will give you an array with all the objects in the section
let objectsForSection = sectionInfo.objects as! [YourEntityType]
//this will add the array above to another array, so you will have access to all the objects of all the sections
exampleArray.append(objectsForSection)
}
You can also use keyValue coding, like this:
//create sectionInfo variable, where 2 is the number of the section (the third section in this example
let sectionInfo = fetchedResultsController.sections![2]
//access the needed entity object from the sectionInfo
let exampleVariable = (sectionInfo.objects as! AnyObject).valueForKeyPath("yourEntity") as! YourEntityType
//access the needed attribute object from the sectionInfo
let anotherExampleVariable = (sectionInfo.objects as! AnyObject).valueForKeyPath("yourEntity.yourAttribute") as! YourAttributeType
Once you know the object associated with the section, you can figure out which image and label text to display.
Make sure your object (presumably a relationship to the main entity fetched by the fetched results controller) provides the necessary information.

Sorting NSFetchedResultsController by Swift Computed Property on NSManagedObjectSubclass

I'm building an app using Swift with Core Data. At one point in my app, I want to have a UITableView show all the objects of a type currently in the Persistent Store. Currently, I'm retrieving them and showing them in the table view using an NSFetchedResultsController. I want the table view to be sorted by a computed property of my NSManagedObject subclass, which looks like this:
class MHClub: NSManagedObject{
#NSManaged var name: String
#NSManaged var shots: NSSet
var averageDistance: Int{
get{
if shots.count > 0{
var total = 0
for shot in shots{
total += (shot as! MHShot).distance.integerValue
}
return total / shots.count
}
else{
return 0
}
}
}
In my table view controller, I am setting up my NSFetchedResultsController's fetchRequest as follows:
let request = NSFetchRequest(entityName: "MHClub")
request.sortDescriptors = [NSSortDescriptor(key: "averageDistance", ascending: true), NSSortDescriptor(key: "name", ascending: true)]
Setting it up like this causes my app to crash with the following message in the log:
'NSInvalidArgumentException', reason: 'keypath averageDistance not found in entity <NSSQLEntity MHClub id=1>'
When I take out the first sort descriptor, my app runs just fine, but my table view isn't sorted exactly how I want it to be. How can I sort my table view based on a computed property of an NSManagedObject subclass in Swift?
As was pointed out by Martin, you cannot sort on a computed property. Just update a new stored property of the club every time a shot is taken.

CoreData many to many relationship Best practices

give the structure:
Person
field1
...
fieldn
>>itemsTaken(inverse: takenFrom)
Item
field1
...
fieldn
>> takenFrom(inverse: itemsTaken)
Person.itemsTaken <<------>>Items.takenFrom
the scenario is that I have a list of Persons and a list of Items
now I would to show within the Person Detail View the items he taken (this is simply solved), and to show on the Item detail View the complete list of persons and select a subset of person that taken that item.
the problem is the 2nd view where I would to add/remove from the orderedset "takenFrom" a person.
override func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
let mo = self.fetchedResultsController.objectAtIndexPath(indexPath) as NSManagedObject
if var x: NSMutableOrderedSet = mo.valueForKey("itemsTaken") as? NSMutableOrderedSet {
x.addObject(detailItem)
mo.setValue(x, forKey: "itemsTaken")
}
if var x: NSMutableOrderedSet = detailItem.valueForKey("takenFrom") as? NSMutableOrderedSet {
x.addObject(mo)
detailItem.setValue(x, forKey: "takenFrom")
}
(UIApplication.sharedApplication().delegate as AppDelegate).saveContext()
}
override func tableView(tableView: UITableView!, didDeselectRowAtIndexPath indexPath: NSIndexPath!) {
let mo = self.fetchedResultsController.objectAtIndexPath(indexPath) as NSManagedObject
if var x: NSMutableOrderedSet = mo.valueForKey("itemsTaken") as? NSMutableOrderedSet {
x.removeObject(detailItem)
mo.setValue(x, forKey: "itemsTaken")
}
if var x: NSMutableOrderedSet = detailItem.valueForKey("takenFrom") as? NSMutableOrderedSet {
x.removeObject(mo)
detailItem.setValue(x, forKey: "takenFrom")
}
(UIApplication.sharedApplication().delegate as AppDelegate).saveContext()
}
that works but when I restarted the app all references for relationship are lost
but I'm not sure I'm proceeding in the correct way.
before this approach I had 2 more entities in order to have for each entity a one 2 many relationships. I'm looking for a cleaner solution
do you have any suggestion or question?
just a clarification. the target is to add/remove references from person.itemsTaken and/or Item.takenFRom
I would to avoid to delete Person. can I remove only the reference within the relationship?
"Cleaner solution": first step would be to make your code more readable. Why call a variable mo or x if the object is better described as a person or as itemsTaken? An additional strategy to make your code more readable is to use NSManagedObject subclasses.
Second, it seems that you are adding the relationship twice (once for each side). I don't think this is necessary.
Third, you might want to check if your mutable ordered set is extracted the way you expect. I think it might be advisable to use the mutable accessor instead:
var itemsTaken = person.mutableSetValueForKey("itemsTaken")
Not sure if you still have to cast or do other things in order to keep the ordering in the ordered set. In my experience, the ordered set never really worked reliably even in Objective-C, so you might just want to keep an additional attribute for the ordering and change the model to use a simple many-to-many relationship.

Resources