Getting Started with MagicalRecord

Ed Sasena
Space game

Beer ahead!
Photo: Krzysztof Szkurlatowski

Update note: This tutorial was updated for iOS 8 and Swift by Ed Sasena. Original tutorial was by team member Andy Pereira.

Core Data is Apple’s built-in solution for the persistence and querying of user data in your OS X and iOS apps. Although Apple constantly tinkers with things to make it easier for developers to use, Core Data remains a challenging API to master.

Even when you know how to use Core Data, everyday tasks can feel clunky and cumbersome. Good thing there is MagicalRecord, a third-party library for Core Data created by MagicalPanda. MagicalRecord provides convenience methods that wrap common boilerplate code for Core Data setup, query and update. It takes many design cues from the venerable Active Record design pattern.

This tutorial will get you up to speed with MagicalRecord quickly and easily. You’ll create an app to keep track of your favorite beers (or other such beverage). It will allow you to:

  1. Add a beer.
  2. Rate the beer.
  3. Make notes about the beer.
  4. Take a photo of the beer.

To follow along with this tutorial, you should already have a basic understanding of Core Data. For a review of the basics, check out our introductory Core Data tutorial before moving on.

Getting Started

Start by downloading this Starter Project. Open the project by double-clicking BeerTracker.xcworkspace (not BeerTracker.xcodeproj).

Use xcworkspace file

Run the application and begin checking it out. You might see some build warnings related to MagicalRecord, which you can safely ignore.


The app has a basic navigation controller with an Add button, table view, search bar and a segmented control for sorting alphabetically or by rating. Tapping the + button exposes the scene for entering and viewing beer information. The app will not save new entries – yet.

Take a look around the project. Expand the BeerTracker target and then the BeerTracker group.


While poking around, you’ll notice that a Core Data model exists but the code doesn’t actually use it. Over the course of this tutorial, you’ll update the project to use the model with MagicalRecord without having to build the Core Data infrastructure.

Exploring MagicalRecord

In the Project Navigator, expand the Pods target followed by the PodsMagicalRecord, and Shorthand groups.

Pods target.

In the Shorthand group, find and open the file named NSManagedObjectModel+MagicalRecord.h.
Notice methods in the header are all prefixed with MR_. Since MagicalRecord adds extensions to Core Data classes, this namespacing ensures the added methods won’t conflict with existing names.

However, using this prefix in so many places can be cumbersome. Luckily, MagicalRecord makes it easy to eliminate the prefix with Shorthand Support.

Again, in the Shorthand group, expand the Support Files group, and open Pods-BeerTracker-MagicalRecord-prefix.pch. This is the project’s precompiled header containing two important lines:

#define MR_SHORTHAND 0
#import “CoreData+MagicalRecord.h”

These two lines are part of what enables MagicalRecord for the project:

  1. MR_SHORTHAND tells MagicalRecord the project will not use the MR_ prefix before any MagicalRecord method name. MagicalRecord+ShorthandSupport.m contains the code to make this work, for those who might be interested in more details.
  2. Importing CoreData+MagicalRecord.h provides access to any of the MagicalRecord APIs.

Beer Model

It’s time to use the data model to start tracking your favorite beers. You can’t expect yourself to remember them, right? To make the data model classes accessible to Objective-C code from the MagicalRecord library, open Beer.swift. Add the following line before the class declaration:


This will ensure the class is visible to the Objective-C runtime and Core Data.

Next, open BeerDetails.swift and perform a similar step – add the following line before the class declaration:


Next, it’s time to initialize the Core Data stack. Open AppDelegate.swift and find application(_:didFinishLaunchingWithOptions:). Add the following lines of code just before the return statement:


If you’ve ever worked on an app that uses Core Data, you’ve seen how much code it takes to set up and initialize the stack. With MagicalRecord, it only takes only this one line!

MagicalRecord provides a few alternative methods for setting up the Core Data stack, depending on the following:

  • The type of backing store.
  • Enabling or disabling auto-migration.
  • The Core Data Model filename matching or not matching the project name.

The MagicalRecord convenience methods corresponding to the list above are:

  • setupCoreDataStackWithInMemoryStore
  • setupAutoMigratingCoreDataStack
  • setupCoreDataStack

If the data model file has the same base name as the project (e.g., a model file BeerTracker.xcdatamodeld, within a project named BeerTracker), then you can use one of these convenience methods listed above. In this case, the model file is named differently from the project so you need to specify the store name with either setupCoreDataStackWithStoreNamed(_:) or setupCoreDataStackWithAutoMigratingSqliteStoreNamed(_:).

Once you have an initial data model, Core Data requires some additional code to handle small changes made to the model. With the setup methods described as AutoMigrating, MagicalRecord will handle the migration from the old data model to the new model if the migration is possible.

Build and run. There should be no changes to the functionality or look of the app. All changes so far have been behind-the-scenes setup for saving beer information.

Brewing a Beer (entity)

Now that the data model and Core Data stack are set up, it’s time to start adding beers to the list. Open BeerDetailViewController.swift and add the following property to the class:

var currentBeer: Beer!

This will hold a Beer entity to keep track of the currently displayed beer.

Next, find viewDidLoad() and add the following code to the end of the method:

if let beer = currentBeer {
	// A beer exists.  EDIT Mode.
} else {
	// A beer does NOT exist.  ADD Mode.
	currentBeer = Beer.createEntity() as! Beer = ""

When BeerDetailViewController loads, it will be a result of one of two events in BeerListViewController:

  • The user selected a beer from the list – Edit mode
  • The user tapped the + button – Add mode

The code just added checks whether a Beer object is set. If so, it must be edit mode; if not, then the user is adding a new beer and you create a new Beer object instead.

Next, you’ll need to do something similar for the beer details. Add the following code to the end of viewDidLoad():

let details: BeerDetails? = currentBeer.beerDetails
if let bDetails = details {
  // Beer Details exist.  EDIT Mode.
} else {
  // Beer Details do NOT exist.  Either ADD Mode or EDIT Mode with a beer that has no details.
  currentBeer.beerDetails = BeerDetails.createEntity() as! BeerDetails

This will try to access the beer details, and do the same check as before to go into either edit or add mode.

Build and run to make sure things still compile. There are still no changes to the look of the app, but be patient! Just like a well-crafted brew, well-crafted code takes time.

Setting Up the User Interface

If an existing beer is being edited, then the UI must be populated with the information currently associated with that beer. Otherwise, the UI should be blank in preparation for a new beer being added.

In BeerDetailViewController.swift, add the following UI setup at the end of viewDidLoad():

let cbName: String? =
if let bName = cbName {
	beerNameTextField.text = bName

If a name attribute exists for the Beer object currentBeer, then you populate the text field with the beer name.

Similarly, to set up the UI with the beer details, add the following code to the end of viewDidLoad():

// Note
if let bdNote = details?.note {
	beerNotesView.text = bdNote
// Rating
let theRatingControl = ratingControl()
if let bdRating = details?.rating {
	theRatingControl.rating = Int(bdRating)
} else {
	// Need this for ADD Mode.
	theRatingControl.rating = 0
// Image
if let beerImagePath = details?.image {
	let beerImage = UIImage(contentsOfFile: beerImagePath)
	if let bImage = beerImage {

Here, you populate the UI with any one or all of the three BeerDetails attributes: note, rating, and image.

How about making the scene title relevant? Add the following code to the end of viewDidLoad:

if == "" {
  title = "New Beer"
} else {
  title =

Now that you have the data displaying, you’ll need some data to actually display! To do that, you’ll add the functionality to add new beers next.

Adding a Beer

When the user taps the + button in the beer list view, prepareForSegue(_:sender:) is called to prepare for the transition to BeerDetailViewController. In BeerListViewController.swift, find prepareForSegue(_:sender:). Look at the branch of the if statement that handles identifier, addBeer. One of the two things this code does is set up a Done button in the navigation controller to execute addNewBeer when the button is clicked.

Open BeerDetailViewController.swift and find addNewBeer(). This method pops BeerDetailViewController from the stack. That triggers a call to viewWillDisappear which, in turn, calls saveContext().

In BeerDetailViewController.swift, add the following code to saveContext():


This is a call to a MagicalRecord method that saves the Beer object – either newly created in viewDidLoad() in Add mode or sent to BeerDetailViewController from BeerListViewController for editing.

There’s a lot going on here in just one line of code! In AppDelegate.swift, you set up the Core Data stack with MagicalRecord. This created a default managedObjectContext the entire app can access. When you created the Beer and BeerDetails entities, they were inserted into this defaultContext. MagicalRecord allows any managedObjectContext to be saved using saveToPersistentStoreAndWait(_:).

Next, you’ll assign the Beer and BeerDetails attributes based on the user’s input. Open BeerDetailViewController.swift and find textFieldDidEndEditing(_:). Add the following code inside the if statement: = textField.text

This will set the current beer’s name to whatever’s in the text field when the user finishes editing.

Next, find textViewDidEndEditing(_:) and add the following code to the end of the method:

if textView.text != "" {
  currentBeer.beerDetails.note = textView.text

Similarly to how the text field code works, this code will ensure the beer notes are stored when the user finishes editing the text view.

Finally, find updateRating() and add the following line to the end of the method:

currentBeer.beerDetails.rating = ratingControl().rating

That takes care of attributes namenote, and ratingimage is taken care of already in viewDidLoad() which utilizes the ImagePickerControllerDelegate code. There is one more block of code to add to the ImagePickerControllerDelegate.

Still in BeerDetailViewController.swift, find imagePickerController(_:didFinishPickingMediaWithInfo:). Just before the call to tableView.reloadData(), add the following code:

// 1
if let imageToDelete = currentBeer.beerDetails.image {
// 2
if ImageSaver.saveImageToDisk(image!, andToBeer: currentBeer) {

This code does the following:

  1. Do some clean-up work in getting rid of the original image if the user did a move and scale operation on it.
  2. Save the image to disk and display the image in the UI.

Now that you’re calling through to saveImageToDisk(_:andToBeer:), that method will need a few additions. Open ImageSaver.swift and find saveImageToDisk(_:andToBeer:).

Now that class Beer has been established, replace the class declaration with the following:

class func saveImageToDisk(image: UIImage, andToBeer beer: Beer) -> Bool {

This changes the type of input parameter beer from AnyObject to Beer for some extra type safety.

Just before } else {, add the following:

beer.beerDetails.image = pathName

This stores a path name to the image instead of the image data itself.

Now that you have all the details saved and ready to display, it’s time to look at the list view controller. Open BeerListViewController.swift and add the following property to the class:

var beers: [Beer]!

Next, find fetchAllBeers() and add the following lines to the end of the method:

let sortKey = NSUserDefaults.standardUserDefaults().objectForKey(wbSortKey) as? String
let ascending = (sortKey == sortKeyRating) ? false : true
// Fetch records from Entity Beer using a MagicalRecord method.
beers = Beer.findAllSortedBy(sortKey, ascending: ascending) as! [Beer]

This calls the MagicalRecord method findAllSortedBy(_:ascending:), which fills up beers with Beer objects from the data store.

findAllSortedBy(_:ascending:) is one of many ways to perform a Core Data fetch using MagicalRecord. To see more options, check out NSManagedObject+MagicalFinders.m.

Now that beers has been filled with records, you can display them in the table view. Still in BeerListViewController.swift, find tableView(_:numberOfRowsInSection:). Replace the return statement with the following:

return beers.count

This will set the number of rows to the number of beers returned in the query.

Next, find configureCell(_:atIndex:) and add the following lines to the beginning of the method:

let currentBeer = beers[indexPath.row]
cell.textLabel?.text =

Now the name attribute gets populated into the table view row.

Build and run. It’s time to try adding a beer:

  1. Tap the + button in the top right corner of the list view.
  2. Once the app transitions to the detail view, add a name for a new beer in the Beer Name textbox.
  3. Tap Done.

The app transitions back to the list view and (drumroll) there it is! A new beer has been added to the list.


Fixing Anomalies

If something doesn’t look quite right, try cleaning the project by selecting from the Xcode file menu Product\Clean. Also, the app can be deleted from the iOS simulator by:

  1. Pressing and holding on the app icon until all icons begin to shake.
  2. Clicking the delete button that appears in the upper left corner of the icon.
  3. Confirming the deletion by clicking the delete button in the alert view.
  4. Choosing Hardware\Home from the iOS simulator menu.

This may be necessary if blank rows continue to appear with the disclosure indicator (right arrow) even after tableView:numberOfRowsInSection has been changed from returning ten rows to returning beers.count rows.

Cancel That Beer

Speaking of blank rows, what if the user starts to add a beer, but then decides not to? Restart the app if it is not already running.

  1. Tap the + button in the top right corner of the list view.
  2. Once the app transitions to the detail view, tap Cancel.

The app transitions back to the list view, but this time a blank beer has been added to the list. A disclosure indicator appears in the list with no beer name.


You’ll need MagicalRecord to come to the rescue to stop this blank record!

Open BeerDetailViewController.swift and find cancelAdd() – this is the method that gets called when you tap on the “Cancel” button. Add the following line to the beginning of the method:


When the detail view controller is loaded, viewDidLoad() creates a new Beer object if one isn’t being edited. If the user taps Cancel, then this object needs to be deleted from the data store. A call to MagicalRecord’s deleteEntity() will take care of the task.

Finding a Method’s Origin
For those who might be curious about finding a method’s origin:

  1. Highlight the method name.
  2. Right-click on the name.
  3. Select Jump to Definition.

Build and run. Repeat the steps to cancel adding a beer.

  1. Tap the + button in the top right corner of the list view.
  2. Once the app transitions to the detail view, tap Cancel.

This time no extra beer is added to the list. Great work – unless you were looking forward to extra beer. ;]

Delete a Beer

Time to clean up the bar area and delete the blank beer (or any beer) from the list. First, you’ll need to enable rows to be deleted from the table view.

Open BeerListViewController.swift and find tableView(_:commitEditingStyle:forRowAtIndexPath:). Add the following code inside the if statement:

let indexPaths = [indexPath]
tableView.deleteRowsAtIndexPaths(indexPaths, withRowAnimation: .Automatic)

Reviewing the code above:

  1. Remove the Beer object from the beers array and call MagicalRecord’s deleteEntity to prepare to remove it from the data store. Call saveContext to have MagicalRecord do the actual work of removing it from the data store.
  2. Prepare to delete the corresponding row from the table view by making a temporary array, indexPaths, with only one index-path object corresponding to the path pointing to the table view cell displaying the object to be deleted.
  3. Tell the table view to remove the row(s) in the temporary array with an animation.
  4. Reload the table view to reflect the changes made by the deletion.

Notice the call to saveContext(). Code must be added to save changes to the data store. You’ve already done this once. Can you figure out how to make it work? Ready? Go!

Solution Inside SelectShow

Build and run. Swipe left on the row with the empty beer and click Delete.


That gets rid of the empties. Try adding and deleting a few more – but not too many if you are driving.

Edit a Beer

Now that a beer can be added, cancelled, or deleted, try editing it. Click on the row in the table displaying the new beer name. What happens? The app transitions to an empty detail view. Did someone drink the beer? Actually, the list view needs to send the beer object to the detail view. Let’s fix that next.

Open BeerListViewController.swift and find prepareForSegue(_:sender:). This is the point where the list will transition by segue over to the detail view.

Add the following code right after if segue.identifier == "editBeer" {:

let indexPath = tableView.indexPathForSelectedRow()
let beerSelected = beers[indexPath!.row]
controller!.currentBeer = beerSelected
controller!.currentBeer.beerDetails = beerSelected.beerDetails

This code uses the indexPath for the row the user tapped as an index into the beers array to access the selected Beer object. The currentBeer property of the destination view controller (BeersDetailViewController) is set to the selected Beer object. beerDetails is set up as a child of currentBeer.

Build and run. Tap on the row in the table displaying the new beer name. There it is in the detail view. Excellent work!


Try editing the name and clicking Done. The app transitions to the list view and the new name is displayed. Perfect!

Finishing Touches

There are a few more things the app can do for users: perform searches and pre-populate a few beers so there’s some starter data.


Now with multiple beers in the system, test the search capabilities. The starter app already included a Search Bar. Scroll to the top of the table to see it. Code needs to be added to search the list of beers.

Earlier, you used a MagicalRecord helper method to fetch and return all beers. Now, only those beers matching a specific search term are required.

For this, you’ll use an NSPredicate . Can you guess how to implement the search? The logic should live inside of performSearch() in BeerListViewController.swift.

Solution Inside SelectShow

Run the app again and drag the beer list down until the search bar is revealed. Search for one of the beers in the list, then search for one that isn’t in the list. Do you get the expected behavior?


Demo Data

It might be nice to give users some initial data to quickly demonstrate how to keep track of their favorite beers. In the final version of the project in AppDelegate.swift, there are two options for preloading beer data:

  1. To preload beer data only once, no matter how many times the app runs, uncomment populateBeers under OPTION 1.
  2. To force preload of beer data, regardless if data has already been preloaded, uncomment populateBeers under OPTION 2. This might come in handy if one or more preloaded beers gets deleted and a fresh, full set is desired.

A Magical Debugger

When the app starts, MagicalRecord logged four things during the Core Data stack setup. It is showing the Core Data setup process happened and a defaultContext was created. This is the same defaultContext referenced in saveContext.

Also, when modifying or adding a new beer, after selecting Done, MagicalRecord performs a save and reports information to the log, such as:

  1. The defaultContext saved to the Main Thread.
  2. Any parents of the context will be saved, designated with flag 1.
  3. The save will be synchronous, designated with flag 1.
  4. Two objects (Beer and BeerDetails) were inserted into the context.
  5. The saved finished.

MagicalRecord will also log the fact that it is not saving a record due to no changes. Try this:

  1. Build and run the project.
  2. Make sure the console is visible. If not, click the square icon in the bottom right corner of the Xcode window.
  3. Select an existing beer from the list.
  4. Once in the detail view, go back to the list view by clicking Wender Beer.


Upon leaving the detail view, viewWillDisappear(_:) calls saveContext() which, in turn, saves the default context. Since no changes were made, MagicalRecord recognizes there is no need to perform a save operation and skips the process. There is no need to think about the need to save. Perform the save and let MagicalRecord figure it out.

Pay attention to the MagicalRecord log reports. They can provide very useful information.

Where To Go From Here?

You can download the finished project, which could be helpful if you get stuck somewhere.

Hopefully, this MagicalRecord tutorial demonstrated how easy it is to get up and running with MagicalRecord. It really helps cut down the boilerplate code! The fundamentals explored in this tutorial can assist in developing all kinds of apps that help users keep track of things they like with pictures, notes, and ratings. Enjoy!

If you want to develop the BeerTracker project further, here are a few ideas to get you started:

  • Add a “no beers created yet” message to the BeerListViewController – lookup the hasAtLeastOneEntity method in MagicalRecord and use it.
  • Add a message to indicate how many beers match the search pattern in the search bar. Use the countOfEntitiesWithPredicate method.
  • Implement a database reset function. Lookup the truncateAll method from MagicalRecord.
  • For fun open up MagicalRecordShorthand.h in the Pods target under Pods/MagicalRecord/Shorthand and read through the method names. Most of them are self explanatory. This header file should provide more insight into using MagicalRecord.

If you have any questions or comments about this tutorial, then please join the forum discussion below! If I’m late in responding, then I might have taken the BeerTracker on a tour of Vermont’s craft breweries. :]

Ed Sasena

Ed Sasena is an independent iOS developer focusing on Core Data applications for the iPhone. His quest is to organize all of the world's information into iPhone apps - which is likely to keep him busy and out of trouble. Whenever he is not immersed in code, you can find Ed biking, running, skiing, hiking, swimming, kayaking, boot camping, drumming, learning Italian, or enjoying wine and cheese.

Other Items of Interest

Save time.
Learn more with our video courses. Weekly

Sign up to receive the latest tutorials from each week, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

PragmaConf 2016 Come check out Alt U

Our Books

Our Team

Video Team

... 19 total!

Swift Team

... 15 total!

iOS Team

... 33 total!

Android Team

... 15 total!

macOS Team

... 10 total!

Apple Game Frameworks Team

... 11 total!

Unity Team

... 11 total!

Articles Team

... 12 total!

Resident Authors Team

... 15 total!