Quantcast
Channel: Kodeco | High quality programming tutorials: iOS, Android, Swift, Kotlin, Unity, and more
Viewing all articles
Browse latest Browse all 4370

MagicalRecord Tutorial for iOS

$
0
0
Space game

Beer ahead!
Photo: Krzysztof Szkurlatowski

For many years, Core Data has been an integral part of many OS X and iOS apps, supporting the persistence and querying of user data. Apple constantly tinkers with Core Data API, in an effort to make it easier for developers to use and integrate into apps.

That said, the truth is that Core Data remains a challenging API to master. Even when you know how to use Core Data, mundane, everyday tasks can feel clunky and cumbersome. Good thing there is MagicalRecord, a third-party library for Core Data created by MagicalPanda – and good that this MagicalRecord tutorial is focused on bringing you up to speed with MagicalRecord quickly and easily :]

MagicalRecord is easy to use, well developed and popular. As described by the project’s author, the point of MagicalRecord is to “clean up” Core Data code and enable simple one-line fetches of data, while still allowing custom performance optimizations. How does it do this? It provides convenience methods, which wrap common boilerplate for Core Data setup, query and update. Its design also features influences from Ruby on Rails’ ActiveRecord persistence system (an instance of the venerable Active Record design pattern).

But enough theory! Just follow along this MagicalRecord tutorial to see how MagicalRecord works its, well, magic.. In this tutorial, you’ll create an app that will keep track of your favorite beers. It will allow you to:

  1. Add a beer of your liking
  2. Rate the beer
  3. Make notes about the beer
  4. Take a photo of the beer – in case you’ve had one too many to make sense of your notes

Getting Started

To follow this tutorial, you should already have a basic understanding of Core Data, at the level provided by a basic Core Data tutorial. Beyond that, you don’t need any prior experience with Core Data.

If you lack even a basic experience with Core Data it’d be great if you checked first this introductory Core Data tutorial and then come back here.

To begin, start by downloading this Starter Project. Once you have it, go ahead and run the application so you can check it out.
BeerTracker_FirstRun

It has a basic navigation controller with an Add button, table view, search bar (if you pull down on the table) and a segmented control for sorting alphabetically or by rating. If you click the Add button you’ll go to the UI, where you’ll enter and view beer information. If you try to enter anything, it won’t save – yet.
Now take a look around the code. In the Project Navigator you’ll see:

  • All of the view controllers that you use to make the application work
  • An ImageSaver utility class
  • Images that you’ll use later for populating initial data
  • An Images.xcassets library that has a few images that are used by the UI
  • AMRating – an easy to use third-party library for a rating control
  • And finally, MagicalRecord

While you’re poking around, you may notice there is no Core Data model, nor does AppDelegate.m contain any code to set it up. This is the perfect scenario for any project you started without Core Data, only to realize later that Core Data is the way to go.

Exploring MagicalRecord

In the Project Navigator, go ahead and expand the MagicalRecord group. Inside, you’ll find groups for Categories and Core, as well as CoreData+MagicalRecord.h. Expand the Categories group, and open the file named NSManagedObjectModel+MagicalRecord.h.
You’ll notice that the methods in the header are all prefixed MR_. In fact, if you go through any of the files in the Categories group, you’ll notice all of the methods are prefixed with MR_.
I don’t know about you, but I find it rather confusing to begin every method with the same two letters. Good thing MagicalRecord makes it easy to change them up with Shorthand Support.

Again, in the Project Navigator, expand the Supporting Files group, and open BeerTracker-Prefix.pch. This is the precompiled header for the project. The starter project has added two lines to this file:

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

These two lines enable MagicalRecord for your project:

  1. MR_SHORTHAND tells MagicalRecord that you do not want to have to type MR_ before any MagicalRecord method. You can look in MagicalRecord+ShorthandSupport.m to find out more how this works. As it goes beyond the scope of this tutorial, it will not be discussed here.
  2. By importing CoreData+MagicalRecord.h, you can now access any of the MagicalRecord APIs in any of your project files without having to import them in every file you need them.

Note:If you want to add MagicalRecord to your own project, there are a few tips on their GitHub page. You can also add MagicalRecord the same way it I added it to this project:

  1. Download the MagicalRecord project from GitHub
  2. Drag the MagicalRecord directory (that contains CoreData+MagicalRecord.h, Categories, and Core) into your Xcode project
  3. Make sure CoreData.framework is added to your project settings under Build Phases\Link Binary With Libraries
  4. In your precompiled header, add the following within #ifdef __OBJC__:
    #define MR_SHORTHAND
    #import “CoreData+MagicalRecord.h”

Beer Model

To start keeping track of your favorite beers, you’re going to need a model, because you can’t expect yourself to remember them, right? From the Xcode menu, select File\New\File…. From the list on the left hand side, select Core Data and choose Data Model from the options.
Create Data Model

Name the file BeerModel.xcdatamodeld and put it in the BeerTracker group.

In the project navigator, select the new bundle BeerModel.xcdatamodeld to start editing it. Add an entity named Beer. Add an attribute named name with a type of String.
Add Beer Entity

Add another Entity named BeerDetails. This entity will track of the details for a beer, such as user rating, notes and where to find the image. Add the following attributes to BeerDetails, with the corresponding types:

  • Attribute: image Type: String
  • Attribute: note Type: String
  • Attribute: rating Type: Integer 16

Add BeerDetails Entity

Next, you’re going to create a relationship between the two, so the Beer entity knows which BeerDetails belong to it. Under BeerDetails, create a new relationship and name it “beer” with the destination Beer. By selecting the destination beer, you’ve established the first part of the relationship between the entities.
Add Beer Relationship

Finish setting up the relationship by selecting the Beer entity. Add a relationship named beerDetails, with the destination of BeerDetails, and the inverse of Beer. Now your Beer entity will have a BeerDetails entity to call its own.
Add BeerDetails Relationship

The next step is to create data classes to represent the entities. You’ll use Xcode for this, but you need to be careful because of some quirks in Xcode’s behavior.

First, edit the Core Data model by selecting it in the Xcode project navigator; ensure that you highlight the Beer entity in the “ENTITIES” pane — and NOT the BeerDetails entity.

Next, in the Xcode menu, go to Editor\Create NSManagedObject Subclass…. Check BeerModel, then select Next. Under Entities to Manage, select the checkboxes for both the Beer and BeerDetails, if not selected by default, and double-check that Beer is highlighted. Press Next and then Create. This will generate new classes of Beer and BeerDetails corresponding to the entities with those names. Take a moment to look at the new classes, and you’ll see that each class has properties corresponding to the entity attributes you defined.

In particular, check the properties that represent the relationship between entities. You should see that the Beer class holds a property beerDetails of type BeerDetails * object. But, you’ll also see that the BeerDetails class holds a property beer of type NSManagedObject*. Why doesn’t the property have a type of Beer *? This is due to a limitation in Xcode’s “Create NSManagedObject Subclass” command. It has no effect on this project, so feel free to ignore it.

Note: Tortured by curiosity? Wondering why Xcode does not create a Beer * property? It appears to be the result of a limitation in Xcode’s CreateNSManagedObjectSubclass command. Since Xcode can see the Core Data Model, the command should logically be able to infer that each class’s property should have the type of the other class in the mutual relationship.

However, that command is not only generating the property types of the classes. It is also generating the class names used to define those types, and the command is not clever enough to do both things fully. One workaround is to simply tweak the generated code and set the property to the precise subclass type (adding forward declarations as needed). Another workaround is just to give Xcode less to do. If you explicitly define the class name for each entity in the Data Model Inspector before generating the classes themselves, then Xcode will generate correct property types.

If you’re curious about more elaborate tools for generating classes from Xcode data models, one option is mogenerator.

Now that you’ve created the data object classes, it’s time to initialize the Core Data stack. Open AppDelegate.m, and in application:didFinishLaunchingWithOptions: add the following lines of code just before the return statement:

// Setup CoreData with MagicalRecord
// Step 1. Setup Core Data Stack with Magical Record
// Step 2. Relax. Why not have a beer? Surely all this talk of beer is making you thirsty…
[MagicalRecord setupCoreDataStackWithStoreNamed:@"BeerModel"];

If you’ve ever worked with an Xcode project created with Core Data enabled, you’ve likely seen how much code it takes to setup Core Data initialize it within the AppDelegate file. With MagicalRecord, all you need is that one line!

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

  • Your type of backing store
  • On whether you want auto-migration
  • And on the name of your Core Data Model file

If your model file has the same base name as your project (e.g., a model file BeerTracker.xcdatamodeld, within a project named BeerTracker), then you can use one of MagicalRecord’s first three convenience methods–setupCoreDataStack, setupCoreDataStackWithInMemoryStore, or setupAutoMigratingCoreDataStack.
But, since your model file is named differently, then you have to use one of the other two setup methods, setupCoreDataStackWithStoreNamed: or setupCoreDataStackWithAutoMigratingSqliteStoreNamed:.

With the setup methods described as AutoMigrating, you can change your model, and if it is possible to auto migrate your store, MagicalRecord will handle it for you. Normally, Core Data requires you to add some code to handle small changes you make to your model.

Brewing a Beer (entity)

Now that your model and Core Data stack are set up, you can start adding beers to your list. Open BeerViewController.h, and after @class AMRatingControl, add:

@class Beer;

Also, add a Beer property after @interface:

@property (nonatomic, strong) Beer *beer;

Now switch to BeerViewController.m, and import Beer.h and BeerDetails.h at the top:

#import "Beer.h"
#import "BeerDetails.h"

In viewDidLoad, add the following block of code:

- (void)viewDidLoad {
    // 1. If there is no beer, create new Beer
    if (!self.beer) {
        self.beer = [Beer createEntity];
    }
    // 2. If there are no beer details, create new BeerDetails
    if (!self.beer.beerDetails) {
        self.beer.beerDetails = [BeerDetails createEntity];
    }
    // View setup
    // 3. Set the title, name, note field and rating of the beer
    self.title = self.beer.name ? self.beer.name : @"New Beer";
    self.beerNameField.text = self.beer.name;
    self.beerNotesView.text = self.beer.beerDetails.note;
    self.ratingControl.rating = [self.beer.beerDetails.rating integerValue];
    [self.cellOne addSubview:self.ratingControl];
 
    // 4. If there is an image path in the details, show it.
    if ([self.beer.beerDetails.image length] > 0) {
        // Image setup
        NSData *imgData = [NSData dataWithContentsOfFile:[NSHomeDirectory() stringByAppendingPathComponent:self.beer.beerDetails.image]];
         [self setImageForBeer:[UIImage imageWithData:imgData]];
     }
}

Troubleshooting: If you have errors after copying the preceding code into your project, clean your project by pressing Shift+Command+K, or by going to Product\Clean.

When BeerViewController loads, it will be because you have:

  • Either selected a beer, or…
  • Selected the Add button from the MasterViewController

Now, when the view loads you will want to do the following:

  1. Check if a beer object loaded. If not, this means you are adding a new Beer.
  2. If the beer doesn’t have any BeerDetails, create a BeerDetails object.
  3. To setup the view, grab the beer’s name, rating and note content. If there is no name for the beer (in the instance of a new beer, it will give it the name “New Beer”).
  4. If the beer contains a path to an image, it will load the image into the UIImageView.

There are a few more things you need to set up so that you can edit and add new beers. First, you need to be able to add or edit the name. Edit textFieldDidEndEditing: so it appears as below:

- (void)textFieldDidEndEditing:(UITextField *)textField {
    if ([textField.text length] > 0) {
        self.title = textField.text;
        self.beer.name = textField.text;
    }
}

Now, when you finish adding the name textField, it will set the beer’s name to whatever the contents of the textField are, so long the field is not empty.

To save the note content to the beer’s note value, find textViewDidEndEditing:, and edit it so it appears as below:

- (void)textViewDidEndEditing:(UITextView *)textView {
    [textView resignFirstResponder];
    if ([textView.text length] > 0) {
        self.beer.beerDetails.note = textView.text;
    }
}

Next, make sure the rating for the beer updates when the user changes it in the View Controller. Find updateRating, and add the following:

- (void)updateRating {
    self.beer.beerDetails.rating = @(self.ratingControl.rating);
}

When the user taps the UIImageView on the details page, it allows them to add or edit a photo. A UIActionSheet displays, which allows the user to pick an image from their Camera Roll, or to snap a new picture. If the user wants to take a picture, you’ll need to make sure the image also saves to the disk. Instead of saving the image to Core Data (which can cause performance issues) you’ll want to save the image to the user’s documents directory, with all their other pictures, and just use the image path for Core Data.

Manage interaction between the camera and the photo library by implementing methods of the UIImagePickerControllerDelegate protocol. You need to make the BeerViewController the delegate for the UIImagePickerController, since it’s the controller handling this storyboard scene. Find imagePickerController:didFinishPickingMediaWithinfo:, and add the following:

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
    // 1. Grab image and save to disk
    UIImage *image = info[UIImagePickerControllerOriginalImage];	
    // 2. Remove old image if present
    if (self.beer.beerDetails.image) {
        [ImageSaver deleteImageAtPath:self.beer.beerDetails.image];
    }
    // 3. Save the image
    if ([ImageSaver saveImageToDisk:image andToBeer:self.beer]) {
        [self setImageForBeer:image];
    }
    [picker dismissViewControllerAnimated:YES completion:nil];
}

Here’s what’s happening in the code shown above:

  1. imagePickerController:didFinishPickingMediaWithInfo: indirectly passes a reference to the user’s image of choice, where the image itself is in the info dictionary, under the UIImagePickerControllerOriginalImage key
  2. If the Beer object already contains an image, the app will delete it from the disk, so you don’t fill up the user’s storage unnecessarily.
  3. The new image then saves to the disk, and the path is added to the BeerDetails image attribute. Open ImageSaver and find saveImageToDisk:andToBeer to see how this looks. Once the image successfully saves, it shows in the UIImageView.
  4. The picker is dismissed.

You’ll need to modify ImageSaver just a little to get it to work. Now that you’ve got your Beer classes created, you can uncomment the import lines, and the line that sets the image’s path in the entity. Open ImageSaver.m and modify the import statements to this:

#import "ImageSaver.h"
#import "Beer.h"
#import "BeerDetails.h"

Now you’ll need to uncomment the line found within the IF statement:

if ([imgData writeToFile:jpgPath atomically:YES]) {
        beer.beerDetails.image = path;
}

The ImageSaver class is now fully ready to accept an image, and save it to the phone’s documents directory, and the path to the BeerDetails object.

From here, the user has two options: Cancel or Done, which saves the new beer. When you created the view, a Beer entity was created, and inserted in the managedObjectContext. Canceling should delete the Beer object. Find cancelAdd, and add the following:

- (void)cancelAdd {
    [self.beer deleteEntity];
    [self.navigationController popViewControllerAnimated:YES];
}

MagicalRecord provides a nice method for deleting an entity, which automatically removes the entity from the managedObjectContext. After deleting, the user will return to the main list of beers.

If the user chooses Done, it will save the beer and return to the main list. Find addNewBeer. It simply pops the view controller, going back to the list. When the view disappears, it will call viewWillDisapper:. This in turn calls saveContext.

Right now saveContext is empty, so you’ll need to add some code to save your entity. Add the following to saveContext:

- (void)saveContext {
    [[NSManagedObjectContext defaultContext] saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) {
        if (success) {
            NSLog(@"You successfully saved your context.");
        } else if (error) {
            NSLog(@"Error saving context: %@", error.description);
        }
    }];
}

There’s a lot going on here in just a few lines of code! In AppDelegate.m, you set up the Core Data stack with MagicalRecord. This created a default managedObjectContext that the entire app can access. When you created the Beer and BeerDetails entities, they were inserted into this defaultContext . MagicalRecord allows you to save any managedObjectContext you may have using saveToPersistentStoreWithCompletion:. The completion block gives you access to an NSError object, if the save failed. Here, you’ve added a simple if/else block that logs what happens after you try to save the defaultContext.

Are you ready to test it out? Go ahead and run your app! Select the + button, fill out any information you want. Then select done.
Working Details Page

When you do, you’ll notice you don’t see your new beer show up in the list. Don’t fret, it’s not broken and you’ll learn how to fix this in just a bit. You also might notice your debugger has a whole bunch of information in it. Contrary to what your logic might say, this is really good for you!
Debugger

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:

  1. The defaultContext saved to the Main Thread.
  2. Any parents of the context will be saved, designated with the flag 1.
  3. The save will be not be synchronous, designated with the flag 0.
  4. 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:

 #define MR_ENABLE_ACTIVE_RECORD_LOGGING 1

to:

 #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:

  1. Retrieved the sort key stored in NSUserDefaults
  2. If the sort key is “rating,” the ascending variable is set to NO. If alphabetically, it is set to YES.
  3. Perform a fetch.

Yep, that’s really all there is to it!
awesomesmile

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!
Working Main List

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!

Solution Inside SelectShow>

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!

Demo Data

It might be nice to give your users some initial data so they can see how easily they can keep track of their favorite beers. In AppDelegate.m, import Beer.h, BeerDetails.h. Then, just after you setup the Core Data stack, add the following:

// Setup App with prefilled Beer items.
if (![[NSUserDefaults standardUserDefaults] objectForKey:@"MR_HasPrefilledBeers"]) {
    // Create Blond Ale
    Beer *blondAle = [Beer createEntity];
    blondAle.name  = @"Blond Ale";
    blondAle.beerDetails = [BeerDetails createEntity];
    blondAle.beerDetails.rating = @4;
    [ImageSaver saveImageToDisk:[UIImage imageNamed:@"blond.jpg"] andToBeer:blondAle];
 
    // Create Wheat Beer
    Beer *wheatBeer = [Beer createEntity];
    wheatBeer.name  = @"Wheat Beer";
    wheatBeer.beerDetails = [BeerDetails createEntity];
    wheatBeer.beerDetails.rating = @2;
    [ImageSaver saveImageToDisk:[UIImage imageNamed:@"wheat.jpg"] andToBeer:wheatBeer];
 
    // Create Pale Lager
    Beer *paleLager = [Beer createEntity];
    paleLager.name  = @"Pale Lager";
    paleLager.beerDetails = [BeerDetails createEntity];
    paleLager.beerDetails.rating = @3;
    [ImageSaver saveImageToDisk:[UIImage imageNamed:@"pale.jpg"] andToBeer:paleLager];
 
    // Create Stout
    Beer *stout = [Beer createEntity];
    stout.name  = @"Stout Lager";
    stout.beerDetails = [BeerDetails createEntity];
    stout.beerDetails.rating = @5;
    [ImageSaver saveImageToDisk:[UIImage imageNamed:@"stout.jpg"] andToBeer:stout];
 
    // Save Managed Object Context
    [[NSManagedObjectContext defaultContext] saveToPersistentStoreWithCompletion:nil];
 
    // Set User Default to prevent another preload of data on startup.
    [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"MR_HasPrefilledBeers"];
    [[NSUserDefaults standardUserDefaults] synchronize];
}

The starter app you downloaded at the beginning of this tutorial includes four frosty beer images. Here, you just create four different beers, and save them. Storing a flag in NSUserDefaults makes sure the app pre-fills the data model only once when it launches for the very fist time.

Run the app again, so you can see all the new beers. Try deleting one and re-launching the app; it won’t come back. If you want to see all the sample beers again, delete the app from the simulator or device, and re-launch.
Demo Data

Searching

Now that you have more than 1 beer, test the search capabilities. The starter app already included a Search Bar, – scroll to the top of the table to see it. The only thing you need to add is the logic to search the list of beers.

Earlier, you used a MagicalRecord helper method to do a fetch of all beers, and it simply returned all of the beers. Now, you want to retrieve all the beers that match a specific search term.

For this, you’ll need to use an NSPredicate. Earlier, this tutorial explained a method for fetching with an NSPredicate – can you guess how to do the search? The logic should live inside of doSearch in the MasterViewController.m file.

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?
Search

Where to go from here

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

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

If you want to develop further the BeerTracker project here follow few ideas to get you started:

  • Add a “no beers created yet” message to the MasterViewController – lookup the hasAtLeastOneEntity method in MagicalRecord and use it.
  • Add a message to indicate how many beers match the search patter to the search controller – use the countOfEntitiesWithPredicate: method.
  • Implement a database reset function – lookup the truncateAll method from MagicalRecord
  • For fun open up MagicalRecordShorthand.h and read through the method names – most of them are pretty self explanatory, this header file should give you an even better idea how to use MagicalRecord

If you have any questions or comments about this tutorial, please join the forum discussion below!

MagicalRecord Tutorial for iOS is a post from: Ray Wenderlich

The post MagicalRecord Tutorial for iOS appeared first on Ray Wenderlich.


Viewing all articles
Browse latest Browse all 4370

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>