MagicalRecord Tutorial for iOS
A MagicalRecord tutorial for iOS that introduces the basics of setting up a CoreData stack and working with this popular library. By Andy Pereira.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
MagicalRecord Tutorial for iOS
35 mins
A Magical Debugger
When the app started, MagicalRecord logged four things during the Core Data stack setup. It is showing that the Core Data setup process happened, and that it created a defaultContext. This is the same defaultContext that was referenced earlier when you saved your beer object.
The next several logs come from when you selected Done and performed a Save with MagicalRecord. You can see the following were logged during your save:
- The defaultContext saved to the Main Thread.
- Any parents of the context will be saved, designated with the flag 1.
- The save will be not be synchronous, designated with the flag 0.
- The final logs show that MagicalRecord knows that two objects need to be saved (a Beer and BeerDetails entity), and that it successfully saved.
MagicalRecord logs a lot of information for you. If you’re having a problem, or something isn’t behaving as expected, you should check your logs for some really useful information.
Note:
While I do not recommend it, if for you really must see what happens when you disable the MagicalRecord logging, you can go to MagicalRecord.h, and change line 17 from:
to:
Note:
While I do not recommend it, if for you really must see what happens when you disable the MagicalRecord logging, you can go to MagicalRecord.h, and change line 17 from:
#define MR_ENABLE_ACTIVE_RECORD_LOGGING 1
to:
#define MR_ENABLE_ACTIVE_RECORD_LOGGING 0
#define MR_ENABLE_ACTIVE_RECORD_LOGGING 1
#define MR_ENABLE_ACTIVE_RECORD_LOGGING 0
Noch ein Bier, bitte
If this app is going to be worth all this work, you’ll need to be able to see the beers you’ve added. Open MasterViewController.m, and import Beer.h, and BeerDetails.h at the top.
In order to get all of the beers’ Core Data saved, you’ll need to do a fetch. In viewWillAppear:
, add the fetch just before the call to reloadData
.
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// Check if the user's sort preference has been saved.
...
[self fetchAllBeers];
[self.tableView reloadData];
}
When the view loads the first time, or when returning from viewing or adding a beer, it will fetch and load all the beers into the table.
Find fetchAllBeers
, and add the following:
- (void)fetchAllBeers {
// 1. Get the sort key
NSString *sortKey = [[NSUserDefaults standardUserDefaults] objectForKey:WB_SORT_KEY];
// 2. Determine if it is ascending
BOOL ascending = [sortKey isEqualToString:SORT_KEY_RATING] ? NO : YES;
// 3. Fetch entities with MagicalRecord
self.beers = [[Beer findAllSortedBy:sortKey ascending:ascending] mutableCopy];
}
The MasterViewController allows a user to sort beers by rating – ordered from a “5-beer” rating to a “1-beer” (Get it? Beers instead of stars? Uber kuhl!) rating, or alphabetically (A-Z). The first time the app launches, it creates an NSUserDefault
value to sort by rating, and establishes that as the default. In this method, you have:
- Retrieved the sort key stored in NSUserDefaults
- If the sort key is “rating,” the ascending variable is set to NO. If alphabetically, it is set to YES.
- Perform a fetch.
Yep, that’s really all there is to it!
Once again, you are using a MagicalRecord method to interact with Core Data. findAllSortedBy:ascending
is just one of the many ways to perform a fetch of Core Data entities using MagicalRecord. Some others include (pay attention – you’ll need to use one of these later):
-
findAllInContext:
– will find all entities of a type in context provided -
findAll
– will find all entities on the current thread’s context -
findAllSortedBy:ascending:inContext:
– similar to the one used earlier, but limited to the provided context -
findAllWithPredicate:
– allows you to pass in an NSPredicate to search for objects. -
findAllSortedBy:ascending:withPredicate:inContext:
– allows a sort to be done, with an ascending flag, in a particular context. It also allows you to pass in an NSPredicate for filtering.
There are many, many others you can take advantage of – just check out NSManagedObject+MagicalFinders.m.
To add the beer’s name and rating to a cell, find configureCell:atIndex:
, and add the following:
- (void)configureCell:(UITableViewCell*)cell atIndex:(NSIndexPath*)indexPath {
// Get current Beer
Beer *beer = self.beers[indexPath.row];
cell.textLabel.text = beer.name;
// Setup AMRatingControl
AMRatingControl *ratingControl;
if (![cell viewWithTag:20]) {
ratingControl = [[AMRatingControl alloc] initWithLocation:CGPointMake(190, 10) emptyImage:[UIImage imageNamed:@"beermug-empty"] solidImage:[UIImage imageNamed:@"beermug-full"] andMaxRating:5];
ratingControl.tag = 20;
ratingControl.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
ratingControl.userInteractionEnabled = NO;
[cell addSubview:ratingControl];
} else {
ratingControl = (AMRatingControl*)[cell viewWithTag:20];
}
// Put beer rating in cell
ratingControl.rating = [beer.beerDetails.rating integerValue];
}
Now find prepareForSegue:sender:
, and within the if statement that checks if the segue identifier is “editBeer,” add:
if ([[segue identifier] isEqualToString:@"editBeer"]) {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
Beer *beer = self.beers[indexPath.row];
upcoming.beer = beer;
}
This will pass the Beer object to the BeerViewController, so that it displays the Beer’s information, allowing you to edit it.
Go ahead and run your project again.
You’ll now see the beer you added earlier, with its rating. You can also select the beer and edit its information. When you go back, the table will be updated! Sehr gut!
Try viewing an individual Beer, and without editing information, go back to the main list. Take a look at the log. You should see:
-[NSManagedObjectContext(MagicalSaves) MR_saveWithOptions:completion:](0x8b6bfa0) NO CHANGES IN ** DEFAULT ** CONTEXT - NOT SAVING
When you leave the details view, the code you’ve written will save the default context in viewWillDisappear:
. However, since no changes were made, MagicalRecord recognizes there is no need to perform a save operation, and so it skips the process. The benefit of this is there is no need for you to think about whether you need to save – just try and save, and let MagicalRecord figure it out for you.
Finishing Touches
There are a few more things you’ll want the app to do for your users – like letting them delete beers, pre-populatin the list with your favorite beers and perform searches.
Delete
In MasterViewController.m, find tableView:commitEditingStyle:forRowAtIndexPath:
, and add the following code:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
Beer *beerToRemove = self.beers[indexPath.row];
// Remove Image from local documents
if (beerToRemove.beerDetails.image) {
[ImageSaver deleteImageAtPath:beerToRemove.beerDetails.image];
}
// Deleting an Entity with MagicalRecord
[beerToRemove deleteEntity];
[self saveContext];
[self.beers removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
}
Notice that there is a call to saveContext
. You’ll need to add some code there to make sure the delete went through. You’ve already done this once – can you figure out how to make it work? Ready……? Go!
[spoiler]
Add the following code to saveContext:
// Save ManagedObjectContext using MagicalRecord
[[NSManagedObjectContext defaultContext] saveToPersistentStoreAndWait];
Since you don’t technically need to know when it finishes, you can use one of the other MagicalRecord options to save the managedObjectContext.
[/spoiler]
Run the app and delete a beer (using the traditional swipe of a cell). If you re-launch the app and the beer is still gone, you did something very right! It saved your change, and that means you’ve used MagicalRecord correctly. Pat yourself on the back!