Recipe: Integrating Core Data Tables with Live Data Edits
Recipe 1 demonstrates how to move basic table editing tasks into the Core Data world.
Adding and deleting items are restricted to the data source—
Methods that commit an editing style (i.e., perform deletes) and that
add new cells do not directly address the table view. In the original
recipe, each method reloaded the table view data after adds and deletes.
Recipe 1 saves data to the managed context but does not call reloadData.
Data updates trigger table reloads— The actual reloadData call triggers when the fetched results delegate receives a controllerDidChangeContent:
callback. This method gets sent when the fetched results object
recognizes that the stored data has updated. That happens after data
changes have been saved via the managed object context.
The table forbids reordering— Recipe 19-3’s tableView:canMoveRowAtIndexPath: method hard codes its result to NO. When working with sorted fetched data sources, users may not reorder that data. This method reflects that reality.
Together, these changes
allow your table to work with add and delete edits, as well as content
edits. Although content edits are not addressed in this recipe, they
involve a similar fetch update approach when users modify attributes
used by sort descriptors.
Objects are added by inserting a new entity description. Their
attributes are set and the context saved. Objects are deleted from the
context, and again the context is saved. These updates trigger the
content changed callbacks for the fetched results delegate.
As this recipe shows, the
Core Data interaction simplifies the integration between the data model
and the user interface. And that’s due in large part to Apple’s
thoughtful class designs that handle the managed object
responsibilities. Recipe 1 highlights this design, showcasing the code parsimony that results from using Core Data.
Recipe 1. Adapting Table Edits to Core Data
-(void)enterEditMode
{
// Start editing
[self.tableView deselectRowAtIndexPath:
[self.tableView indexPathForSelectedRow] animated:YES];
[self.tableView setEditing:YES animated:YES];
[self setBarButtonItems];
}
-(void)leaveEditMode
{
// Finish editing
[self.tableView setEditing:NO animated:YES];
[self setBarButtonItems];
}
- (BOOL)tableView:(UITableView *)tableView
canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
return NO; // no reordering allowed
}
- (void)tableView:(UITableView *)tableView
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath
{
// Delete request
if (editingStyle == UITableViewCellEditingStyleDelete)
{
NSError *error = nil;
[self.context deleteObject:[fetchedResultsController
objectAtIndexPath:indexPath]];
if (![self.context save:&error])
NSLog(@"Error %@", [error localizedDescription]);
}
// Update buttons after delete action
[self setBarButtonItems];
// Update sections
[self performFetch];
}
- (void) add
{
// Request a string to use as the action item
NSString *todoAction =
[ModalAlert ask:@"What Item?" withTextPrompt:@"To Do Item"];
if (!todoAction || todoAction.length == 0) return;
// Build a new item and set its action field
ToDoItem *item = (ToDoItem *)[NSEntityDescription
insertNewObjectForEntityForName:@"ToDoItem"
inManagedObjectContext:self.context];
item.action = todoAction;
item.sectionName =
[[todoAction substringToIndex:1] uppercaseString];
// Save the new item
NSError *error;
if (![self.context save:&error])
NSLog(@"Error %@", [error localizedDescription]);
// Update buttons after add
[self setBarButtonItems];
// Update sections
[self performFetch];
}
- (void)controllerDidChangeContent:
(NSFetchedResultsController *)controller
{
// Update table when the contents have changed
[self.tableView reloadData];
}
|
Recipe: Implementing Undo-Redo Support with Core Data
Core
Data simplifies table undo-redo support to an astonishing degree. It
provides automatic support for these operations with little programming
effort. Here are the steps you need to take to add undo-redo to your
table based application.
Add
an undo manager to the managed object context. After establishing a
managed object context (typically in your application delegate), set its
undo manager to a newly allocated instance.
self.context.undoManager =
[[[NSUndoManager alloc] init] autorelease];
Assign
that undo manager in your view controller. Set your view controller’s
undo manager to point to the undo manager used by the managed object
context.
self.context = [(TestBedAppDelegate *)
[[UIApplication sharedApplication] delegate] context];
self.undoManager = self.context.undoManager;
Optionally,
provide shake-to-edit support. If you want your application to respond
to device shakes by offering an undo-redo menu, add the following line
to your application delegate.
application.applicationSupportsShakeToEdit = YES;
Ensure
that your view controller becomes the first responder when it is
onscreen. Provide the following suite of methods. These methods allow
the view responder to become first responder whenever it appears. The
view controller resigns that first responder status when it moves
offscreen.
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self becomeFirstResponder];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self resignFirstResponder];
}
The preceding steps provide all the setup needed to use undo management in your table. Recipe 2
integrates that undo management into the actual delete and add methods
for the table. To make this happen, it brackets the core data access
with an undo grouping. The beginUndoGrouping and endUndoGrouping
calls appear before and after the context updates and saves with
changes. An action name describes the operation that just took place.
These three calls
(begin, undo, and setting the action name) comprise all the work needed
to ensure that Core Data can reverse its operations. For this minimal
effort, your application gains a fully realized undo management system,
courtesy of Core Data. Be aware that any undo/redo data will not survive
quitting your application. This works just as you’d expect with manual
undo/redo support.
Recipe 2. Expanding Cell Management for Undo/Redo Support
- (void)tableView:(UITableView *)tableView
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.context.undoManager beginUndoGrouping];
// Delete request
if (editingStyle == UITableViewCellEditingStyleDelete)
{
NSError *error = nil;
[self.context deleteObject:
[fetchedResultsController objectAtIndexPath:indexPath]];
if (![self.context save:&error])
NSLog(@"Error %@", [error localizedDescription]);
}
[self.context.undoManager endUndoGrouping];
[self.context.undoManager setActionName:@"Delete"];
// Update buttons after delete action
[self setBarButtonItems];
// Update sections
[self performFetch];
}
- (void) add
{
// Request a string to use as the action item
NSString *todoAction = [ModalAlert ask:@"What Item?"
withTextPrompt:@"To Do Item"];
if (!todoAction || todoAction.length == 0) return;
[self.context.undoManager beginUndoGrouping];
// Build a new item and set its action field
ToDoItem *item = (ToDoItem *)[NSEntityDescription
insertNewObjectForEntityForName:@"ToDoItem"
inManagedObjectContext:self.context];
item.action = todoAction;
// Index by the first character of the action
item.sectionName =
[[todoAction substringToIndex:1] uppercaseString];
// Save the new item
NSError *error;
if (![self.context save:&error])
NSLog(@"Error %@", [error localizedDescription]);
[self.context.undoManager endUndoGrouping];
[self.context.undoManager setActionName:@"Add"];
// Update buttons after add
[self setBarButtonItems];
// Update sections
[self performFetch];
}
|