UITableView Core Data reordering - iphone

I know this question has been asked before, and I took a look at the answer to this question. However, I'm still confused as to how to implement reordering with a UITableView in a Core Data project. What I know is that I need to have a "displayOrder" attribute in my Entity to track the order of items, and I need to enumerate through all the objects in the fetched results and set their displayOrder attribute.
In the given code in the question I linked to, the table view delegate method calls a method like this [self FF_fetchResults];, and the code for that method is not given so its hard to tell what exactly it is.
Is there any sample code that demonstrates this? That would be simpler to look at than sharing large chunks of code.
Thanks

I'm not sure which part you are having trouble with (based on the comments)... but here is my suggestion. The displayOrder is just a simple attribute on a NSManagedObject class. If you can save a managed object, then you will be able finish this feature. Lets first take a simple NSManagedObject:
#interface RowObj : NSManagedObject
{
}
#property (nonatomic, retain) NSString *rowDescription;
#property (nonatomic, retain) NSNumber *displayOrder;
Next, we need to have local copy of the data being displayed in the tableview. I have read through the comments you have made and I'm not really sure if you are using the FetchedResultsController or not. My suggestion would be to start simple and just use a normal tableviewcontroller where you update the row data whenever a user changes the display order... then save the order when the user is done editing.
The interface for this tableviewcontoller would look like this:
#interface MyTableViewController : UITableViewController {
NSMutableArray *myTableViewData;
}
#property(nonatomic,retain) NSMutableArray *myTableViewData;
#end
Next, we need to load the the table view data in the viewWillAppear method:
- (void)viewWillAppear:(BOOL)animated {
myTableViewData = [helper getRowObjects]; // insert method call here to get the data
self.navigationItem.leftBarButtonItem = [self editButtonItem];
}
There are 2 things going on here... (I'll explain the editButtonItem later) the first is that we need to get our data from CoreData. When I have to do this I have some sort of helper(call it what you want) object do the work. A typical find method would look like this:
- (NSMutableArray*) getRowObjects{
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"RowObj" inManagedObjectContext:[self managedObjectContext]];
[request setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"displayOrder" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
[sortDescriptors release];
[sortDescriptor release];
NSError *error;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle the error.
}
[request release];
return mutableFetchResults;
}
Now that you have your data, you can now wait for the user to edit the table. That is where the [self editButtonItem] comes into play. This is a built in feature that returns a bar button item that toggles its title and associated state between Edit and Done. When the user hits that button, it will invoke the setEditing:animated: method:
To update the display order you need to override the setEditing method on the UITableViewController class. It should look something like this:
- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
[super setEditing:editing animated:animated];
[myTableView setEditing:editing animated:animated];
if(!editing) {
int i = 0;
for(RowObj *row in myTableViewData) {
row.displayOrder = [NSNumber numberWithInt:i++];
}
[helper saveManagedObjectContext]; // basically calls [managedObjectContext save:&error];
}
}
We don't have to do anything when the user is in edit mode... we only want to save once they have pressed the "Done" button. When a user drags a row in your table you can update your display order by overriding the canMoveRowAtIndexPath and moveRowAtIndexPath methods:
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
return true;
}
(void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath {
RowObj *row = [myTableViewData objectAtIndex:sourceIndexPath.row];
[myTableViewData removeObjectAtIndex:sourceIndexPath.row];
[myTableViewData insertObject:row atIndex:destinationIndexPath.row];
}
Again, the reason I don't update the displayOrder value here is because the user is still in edit mode... we don't know if the user is done editing AND they could even cancel what they've done by not hitting the "Done" button.
EDIT
If you want to delete a row you need to override tableView:commitEditingStyle:forRowAtIndexPath and do something like this:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the managed object at the given index path.
RowObj *row = [myTableViewData objectAtIndex:indexPath.row];
[helper deleteRow:row];
// Update the array and table view.
[myTableViewData removeObjectAtIndex:indexPath.row];
[myTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];
}
}

Related

NSFetchedResultsController and NSOrderedSet relationships

I am having an issue (understanding issue to be honest) with NSFetchedResultsController and the new NSOrderedSet relationships available in iOS 5.
I have the following data-model (ok, my real one is not drawer's and sock's!) but this serves as a simple example:
Drawer and Sock are both NSManagedObjects in a Core Data model/store. On Drawer the socks relationship is a ordered to-many relationship to Sock. The idea being that the socks are in the drawer in a specific order. On Sock the drawer relationship is the inverse of the socks relationship.
In a UIViewController I am drawing a UITableView based on these entities. I am feeding the table using a NSFetchedResultsController.
- (NSFetchedResultsController *)fetchedResultsController1 {
if (_fetchedResultsController1 != nil) {
return _fetchedResultsController1;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Sock" inManagedObjectContext:[NSManagedObjectContext MR_defaultContext]];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"drawer.socks" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
self.fetchedResultsController1 = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[NSManagedObjectContext MR_defaultContext] sectionNameKeyPath:nil cacheName:#"SocksCache"];
self.fetchedResultsController1.delegate = self;
return _fetchedResultsController1;
}
When I run this, I get the following errror: *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'to-many key not allowed here'
This makes sense to me, as the relationship is a NSOrderedSet and not a single entity to compare against for sorting purposes.
What I want to achieve is for the Socks to appear in the UITableView in the order specified in the socks relationship. I don't really want to have a sort order but NSFetchedResultsController, which is a great component is insisting there has to be one. How can I tell it to use the socks order on the Drawer entity. I don't want the table to show Drawer entities at all.
Note: I am using this within an iOS5 application only, so ordered relationships are available.
Anyone that can offer me any direction, would be greatly appreciated. Thanks for your time.
Edit: So the tableview that display's socks does so for just one drawer. I just want the table view to honor the order that the socks relationship contains. I'm not sure what to set the sort criteria to make sure that happens.
Damien,
You should just make your NSFetchRequest use the array form of the ordered set. It will operate fine. Your controller needs an attribute to sort on. Hence, you'll need to specify that too.
Andrew
I found this thread while looking for an answer to the exact question posed by the OP.
I never found any examples of presenting such data in a tableView without adding the additional sort field. Of course, adding the sort field pretty much eliminates any benefit in using the ordered relationship. So given that I got this working, I thought it might be helpful to others with the same question if I posted my code here. It turned out to be quite simple (much simpler than using an additional sort field) and with apparently good performance. What some folks may not have realized (that includes me, initially) is that the type (NSOrderedSet) of the "ordered, to-many" relationship attribute has a method for getting objectAtIndex, and that NSMUtableOrderedSet has methods for inserting, and removing objectAtIndex.
I avoided using the NSFetchedResultsController, as some of the posters suggested. I've used no array, no additional attribute for sorting, and no predicate. My code deals with a tableView wherein there is one itinerary entity and many place entities, with itinerary.places being the "ordered, to-many" relationship field. I've enabled editing/reordering, but no cell deletions. The method moveRowAtIndexPath shows how I've updated the database with the reordering, although for good encapsulation, I should probably move the database reordering to my category file for the place managed object. Here's the entire TableViewController.m:
//
// ItineraryTVC.m
// Vacations
//
// Created by Peter Polash on 8/31/12.
// Copyright (c) 2012 Peter Polash. All rights reserved.
//
#import "ItineraryTVC.h"
#import "AppDelegate.h"
#import "Place+PlaceCat.h"
#import "PhotosInVacationPlaceTVC.h"
#interface ItineraryTVC ()
#end
#implementation ItineraryTVC
#define DBG_ITIN YES
#synthesize itinerary ;
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationItem.rightBarButtonItem = self.editButtonItem ;
}
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated] ;
UIManagedDocument *doc = UIAppDelegate.vacationDoc;
[doc.managedObjectContext performBlock:^
{ // do this in the context's thread (should be the same as the main thread, but this made it work)
// get the single itinerary for this document
self.itinerary = [Itinerary setupItinerary: doc ] ;
[self.tableView reloadData] ;
}];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown );
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section
{
return [self.itinerary.places count ];
}
- (UITableViewCell *) tableView: (UITableView *) tableView
cellForRowAtIndexPath: (NSIndexPath *) indexPath
{
static NSString *CellIdentifier = #"Itinerary Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: CellIdentifier ];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle: UITableViewCellStyleDefault reuseIdentifier: CellIdentifier];
}
Place *place = [self.itinerary.places objectAtIndex:indexPath.row];
cell.textLabel.text = place.name;
cell.detailTextLabel.text = [NSString stringWithFormat:#"%d photos", [place.photos count]];
return cell;
}
#pragma mark - Table view delegate
- (BOOL) tableView: (UITableView *) tableView
canMoveRowAtIndexPath:( NSIndexPath *) indexPath
{
return YES;
}
-(BOOL) tableView: (UITableView *) tableView
canEditRowAtIndexPath: (NSIndexPath *) indexPath
{
return YES ;
}
-(void) tableView: (UITableView *) tableView
moveRowAtIndexPath: (NSIndexPath *) sourceIndexPath
toIndexPath: (NSIndexPath *) destinationIndexPath
{
UIManagedDocument * doc = UIAppDelegate.vacationDoc ;
[doc.managedObjectContext performBlock:^
{ // perform in the context's thread
// itinerary.places is the "ordered, to-many" relationship attribitute pointing to all places in itinerary
NSMutableOrderedSet * places = [ self.itinerary.places mutableCopy ] ;
Place *place = [ places objectAtIndex: sourceIndexPath.row] ;
[places removeObjectAtIndex: sourceIndexPath.row ] ;
[places insertObject: place atIndex: destinationIndexPath.row ] ;
self.itinerary.places = places ;
[doc saveToURL: doc.fileURL forSaveOperation: UIDocumentSaveForOverwriting completionHandler: ^(BOOL success) {
if ( !success ) NSLog(#"Error saving file after reorder, startPos=%d, endPos=%d", sourceIndexPath.row, destinationIndexPath.row) ;
}];
}];
}
- (UITableViewCellEditingStyle) tableView: (UITableView *) tableView
editingStyleForRowAtIndexPath: (NSIndexPath *) indexPath
{
return ( UITableViewCellEditingStyleNone ) ;
}
- (void) prepareForSegue:(UIStoryboardSegue *) segue sender: (id) sender
{
NSIndexPath *indexPath = [self.tableView indexPathForCell: sender] ;
PhotosInVacationPlaceTVC * photosInVacationPlaceTVC = segue.destinationViewController ;
Place *place = [self.itinerary.places objectAtIndex:indexPath.row ];
photosInVacationPlaceTVC.vacationPlace = place ;
photosInVacationPlaceTVC.navigationItem.title = place.name ;
UIBarButtonItem *backButton =
[[UIBarButtonItem alloc] initWithTitle:#"Back" style:UIBarButtonItemStylePlain target:nil action:nil];
self.navigationItem.backBarButtonItem = backButton;
}
#end
You can give Sock an index attribute and order the socks by them:
sock01.index = [sock01.drawer.socks indexOfObject:sock01];
To add some more clarity to the answer from adonoho (thanks mate) which helped me work it out - rather than trying to specify the to-many relationship as any sorting key which I also couldn't get working, specify the belongs-to relationship in a predicate to select which entities you want in the fetched results controller, and specify the belongs-to relationship as the sort key.
This satisfies the primary aim of having the results in a NSFetchedResultsController with all that goodness, and respects the order in the to-many relationship.
In this particular example (fetching socks by drawer):
// socks belong-to a single drawer, sort by drawer specified sock order
fetchRequest.sortDescriptors = #[[[NSSortDescriptor alloc] initWithKey:#"drawer" ascending:YES]];
// drawer has-many socks, select the socks in the given drawer
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"drawer = %#", drawer];
Essentially, this uses a given drawer to specify which socks should go into the NSFetchedResultsController, and the ordering as specified by the to-many relationship.
Looking at the generated SQL (annotated from my example using different entity names) via a core data SQL debug:
CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, ...fields..., t0.ZDRAWER, t0.Z_FOK_DRAWER FROM ZSOCKS t0 WHERE t0.ZDRAWER = ? ORDER BY t0.Z_FOK_DRAWER
you can see the SQL ordering using the Z_FOK_DRAWER column, which Core Data uses for the position of that sock.
As far as I understand this functionality, it allows to have ordered Socks in every Drawer. As Apple writes in documentation:
You should use them only if a relationship has intrinsic ordering that
is critical to its own representation—such as the steps in a recipe.
This means you can't fetch all Socks using sorted relation. Sorted Socks will be available only in every Drawer object.
As I just answered here, I prefer just adding a new property to my NSManagedObject via a category.
Just add a method:
- (NSUInteger)indexInDrawerSocks
{
NSUInteger index = [self.drawer.socks indexOfObject:self];
return index;
}
Then in your NSFetchedResultsController, use sort descriptor:
fetchRequest.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"indexInDrawerSocks" ascending:YES]];

Array nil (even when addObject:) [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
addObject: to array not working (array still nil)
EVERYTHING UPDATED
This app is a table view with a tab bar controller. I am logging the count of the array: arrayOfFavourites and even though i add an object is continues to have a nil value, my relating code, all objects shown are allocated and initialized in the code (previous or present) some are instances and some are properties:
ListViewController.m:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"TOUCHED CELL!");
// Push the web view controller onto the navigation stack - this implicitly
// creates the web view controller's view the first time through
[[self navigationController] pushViewController:webViewController animated:YES];
// Grab the selected item
entry = [[channel items] objectAtIndex:[indexPath row]];
if (!entry) {
NSLog(#"!entry");
}
// Construct a URL with the link string of the item
NSURL *url = [NSURL URLWithString:[entry link]];
// Construct a request object with that URL
NSURLRequest *req = [NSURLRequest requestWithURL:url];
// Load the request into the web view
[[webViewController webView] loadRequest:req];
// Take the cell we pressed
// IMPORTANT PART
CELL = [tableView cellForRowAtIndexPath:indexPath];
[webViewController setItem:entry];
webViewController = nil;
webViewController = [[WebViewController alloc] init];
[entry release];
}
WebViewController.m:
You shake to favorite a cell
-(void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
cellToPassOn = nil;
NSLog(#"Favouriting"); // YES I KNOW SPELLING
// This is pretty simple, what we do is we take the cell we touched and take its title and link
// then put it inside an array in the Favourites class
Favourites *fav = [[Favourites alloc] init];
ListViewController *list = [[ListViewController alloc] init];
[self setCellToPassOn: [list CELL]];
if (!item) {
NSLog(#"NILLED ITEM");
}
[[fav arrayOfFavourites] addObject:[item autorelease]];
[fav setCell: cellToPassOn];
}
Favourites.m:
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"ROWS NO.");
NSLog(#"%i", [arrayOfFavourites count]);
return [arrayOfFavourites count];
}
Favourites:
A CLASS, ON TAB BAR CONTROLLER
WEBVIEWCONROLLER:
CONTROLLER FOR DIFFERENT WEB VIEWS
LISTVIEWCONTROLLER:
DATA PROVIDER
Actually what happens is when i shake i reload the table view data and i add an object to an array (array of favorites) the count is one.... GOOD! But then when i shake again (in a different article, my app has different webViews when i press different cells.) It is still 1... weird.... if i go to favorites class that array remains one.. ok... so as you can see i am returning the arrayOfFavourites count to numberOfRowsInSection (which is 1) but no cells appear and the cellForRowAtIndexPath is never called (using an NSLOG) why is this happening I AM VERY ANNOYED!
In your Favourites.m numberOfRowsInSection function, looks like you should do this:
if(arrayOfFavourites == NULL)
{
arrayOfFavourites = [[NSMutableArray alloc] init];
}
Because you're reinitializing (and likely leaking) in every single call to numberOfRowsInSection (which gets called each time the table needs to know how many rows it must display -- i.e. very often).
You create and destroy a new Favourites object every time you go through -motionBegan:withEvent:, and you create a new array every time you go through -tableView:numberOfRowsInSection:. If you want data to persist beyond those events, you need to keep the objects around.
Let review this part of your code :
-(void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event {
cellToPassOn = nil;
NSLog(#"Favouriting"); // YES I KNOW SPELLING
// This is pretty simple, what we do is we take the cell we touched and take its title and link
// then put it inside an array in the Favourites class
// HERE you are creating a NEW fav
Favourites *fav = [[Favourites alloc] init];
// HERE you are creating a NEW list
ListViewController *list = [[ListViewController alloc] init];
// SO HERE what is "CELL" doing, returning some constant or static object?
[self setCellToPassOn: [list CELL]];
// HERE what is item and where does it come from
if (!item) {
NSLog(#"NILLED ITEM");
}
// Here you take an array of an object you just created and autoreleasing the item
// this is not the regular way to handle memory management in Cocoa,
// depending on what you are doing to item else where you could get item == deallocated pretty soon
[[fav arrayOfFavourites] addObject:[item autorelease]];
[fav setCell: cellToPassOn];
HERE you are releasing fav
[fav release];
HERE fav don't exist anymore as well as the array to which you've added something to it.
[list release];
item = nil;
}
Unless I'm reading your code incorrectly I have the feeling that you are trying to have some persistence with "volatile" object.
You need to make a #property for those object to survive longer that one method call.
Every time you create a new object it's new and have no knowledge of the precedent one.
And if we look at this code :
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// HERE everytime the tableview is asking for the numberOfRowsInSection, you create a new array
// And that new empty array is replacing your old one.
// That is your "weird" thing, it's doing what you are asking it to do, set it back to a new empty array.
arrayOfFavourites = [[NSMutableArray alloc] init];
NSLog(#"ROWS NO.");
NSLog(#"%i", [arrayOfFavourites count]);
return [arrayOfFavourites count];
}

Select multiple items from a tableview - Beginner

I need to add a TableView and i should be able to click several Items in that tableview and save it to a NSMutable dictionary or something suitable.
I know that you have to use a NSMutable Dictionary for this. But i don't understand how to do this.
Can someone please point a good tutorial or provide some sample codes for me.
You will have to use a delegate method for that.
First make sure your table view is set up well (delegate and datasource) and then
implement delegate 's :
(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath.
You access the selected row index with : [indexPath row].
And then you can store this value.
THere was a post by Matt Gallagher at Cocoa With Love about this that you might find illuminating.
A bit dated, but the principles will be the same.
It really depends on your data model.
First of all you need a NSMutableArray not a NSMutableDictionary;
//declare an NSMutableArray in the interface
#interface Class : SuperClass {
NSMutableArray *_arrayWithMySelectedItems;
}
//in one of your init/preparation methods alloc and initialize your mutable array;
- (void)viewDidLoad {
[super viewDidLoad];
_arrayWithMySelectedItems = [NSMutableArray alloc] init];
}
//now before you forget it add release in your dealloc method
- (void)dealloc {
[_arrayWithMySelectedItems release];
[super dealloc];
}
//add this following code to your didSelect Method part of tableView's delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//if you have only one category, you will probably have something like this
[_arrayWithMySelectItems addObject:[dataModelArray objectAtIndex:indexPath.row];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
You can use NSMutableDictionary in this method as:
(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSMutableDictionary *dict = [NSMutableDictionary alloc] init];
[dict setObject:[NSString stringWithFormat:"%#", cell.Label.text] forKey:[NSString stringWithFormat:"Key%d", indexPath.row]];
}
declare a string inside didselect row of tableview then give that string = [your populated array objectAtIndex:indexpath.row]; then add that into dictionary or nsmutable array according to your wish.

"Attempt to create two animations for cell" Crash on ios

Here is my first post on stackoverflow. Any of your help will be greatly appreciated!
I am totally new to ios development. As a practice, I am trying to develop a simple app that keeps track of diffrent items in my lab. Each item has a to-one relationship to its location and each location has a to-many relationship to different items.
I have a UITableViewController(LocationListTableViewController) that uses NSFectchedResultController as its data source for the tableview. The tableview controller is also conformed to NSFetchedResultController Delegate to keep all the NSManagedObject updated.
Once a location on the LocationListTableViewController is tapped, a DetailedTableViewController will be pushed and shows the items at that location. This DetailedTableViewController has an NSUndoManager instance variable to support canceling of changes. I use a NSMutableArray as the data source of the DetailedTableViewController:
NSSet *items = self.location.items;
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"itemName" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray *array = [[items allObjects]sortedArrayUsingDescriptors:sortDescriptors];
// itemList is the data source for the table view
self.itemList = [array mutableCopy];
When the "Edit" button is tapped, Users can delete items and those item's location is set to nil. The deleted Items are transferred to a "changedItem" NSMutableArray so that the change can be canceled:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (!changedItems)
changedItems = [[NSMutableArray alloc] init];
// Set the company attribute of selected company to nil
Item *editingItem = [itemList objectAtIndex:indexPath.row];
editingItem.company = nil;
// Add to changedItem array to support canceling
[changedItems addObject:editingItem];
// Remove from the data source array
[itemList removeObject:editingItem];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
I put a Cancel button as the left bar button item, and when it's tapped:
[undoManager endUndoGrouping];
NSManagedObjectContext *moc = self.location.managedObjectContext;
[moc rollback];
// Add item from changedItem array back to itemList array (data source array)
if ([changedItems count] > 0) {
[itemList addObjectsFromArray:changedItems];
[itemList sortUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
}
Here comes the problem. When I delete more than one item and try to cancel these change, the app does not crash but I get the error message:
*** Assertion failure in -[_UITableViewUpdateSupport _setupAnimationsForNewlyInsertedCells], /SourceCache/UIKit_Sim/UIKit-1448.89/UITableViewSupport.m:599
Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Attempt to create two animations for cell with userInfo (null)
When only deleting one item, the app seems fine.
This is very anoying. Is there anybody could help me with this?
Many thanks!
The cell probably doesn't exist - try NSLogging its value or making sure it's created properly.
Just have a try:
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];

Can you edit core data on ListViewController

I have a Core Data application set up with a ListViewController, a DetailViewController and an EditingViewController, where most of the editing occurs. On the ListViewController, I have a graphic of a checkbox, and I can toggle two images by selecting the row in
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
Is it possible to edit the Core Data record from the ListViewController so that I can make my selection persistent? I cannot work out the syntax to select the record, edit, and save the value - which will simply toggle from true to false.
jon
Thanks for the prompt response! Additional information after Answer 1. Your assumption is correct. I am modeling this application on the CoreData Books sample. I am not using a button, but rather using two images to create a checked and unchecked checkbox. I added a boolean "check" to my entity, recreated the header file, and added the header file to my ListViewController. Here is a simplified version of the datamodel header file.
#interface Patient : NSManagedObject
{
}
#property (nonatomic, retain) NSString * location;
#property (nonatomic, retain) NSNumber * check;
#property (nonatomic, retain) NSString * lastName;
#end
And here is my modification of your code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
NSManagedObject *entityObject = [self.fetchedResultsController objectAtIndexPath:indexPath];
if (![entityObject.check boolValue]) {
entityObject.check = [NSNumber numberWithBool:YES];
cell.imageView.image = [UIImage imageNamed:#"check.png"];
}
else {
entityObject.check= [NSNumber numberWithBool:NO];
cell.imageView.image = [UIImage imageNamed:#"uncheck.png"];
}
However, this gives me the error "Request for member 'check' in something not a structure or union". I verified that the new attribute is a boolean and that the header file is imported to the ListViewController. Any thoughts?
Lastly, does this code eliminate the need to save entityObject.check to the database? Thanks again.
It would be nice to get a few more details about what your app does before we can provide a more informed recommendation. I'll make a few assumptions anyway to see if it helps...
Firstly, when dealing with BOOL properties in Coredata, it is important to remember that they are actually saved as NSNumbers so keep that in mind when you need to test their values and make sure you are using the right methods.
Assuming the data in your ListViewController is being populated by CoreData and your data model defines an entity "Entity" with an attribute of type BOOL called "favourite", here's what you can try:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
NSManagedObject *entityObject = [self.fetchedResultsController objectAtIndexPath:indexPath];
if (![entityObject.favourite boolValue]) {
entityObject.favourite = [NSNumber numberWithBool:YES];
[self.yourToggleButton setSelected:YES]; // this assumes your checkbox is a UIButton and you want its view to be updated to checked when your BOOL == YES
}
else {
entityObject.favourite = [NSNumber numberWithBool:NO];
[self.watchListButton setSelected:NO];
}
The above assumes you have a fetchedResultsController taking care of the data fetching from your CoreData database too. If not I strongly recommend you check out one the tutorials from Apple (CoreData Books and CoreData Recipes) to understand how they work as it will make your life much easier - NSFetchedResultsController make it pretty straight forward to keep your data syncronised between different views which is something you are going to have to consider specially because you are allowing different attributes to be edited in different view controllers.
I hope this helps but feel free to ask any additional questions if you need to.
Cheers,
Rog
I came up with a slightly different solution, but Rog's post definitely got me on the right track. I had some trouble with the boolean syntax, so I changed the entity attribute to a string and used "N" and "Y" instead. I also found that I needed to save the value to make it persistent. I appreciate the help.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Establish connections
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
Patient *patient = [self.fetchedResultsController objectAtIndexPath:indexPath];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
// Toggle images and check value with each row tap
if ([patient.check isEqualToString: #"N"] == YES) {
cell.imageView.image = [UIImage imageNamed:#"check.png"];
patient.check = #"Y";
}
else {
cell.imageView.image = [UIImage imageNamed:#"uncheck.png"];
patient.check = #"N";
}
// Save check value
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}