Using MagicalRecord when developing iOS applications
- Transfer
- Tutorial
Habralyudi, good afternoon!
Today I want to bring to your attention another translation, do not judge strictly :) I hope you find this material useful in your work.
For many years, Core Data has been an integral part of many OS X and iOS applications, providing storage and retrieval of user data. Apple is working hard to make the Core Data API easier to use and make it easier for developers to integrate into applications.
This fact indicates that Core Data is a highly modifiable project. Even if you know how to use Data Core, performing simple, daily tasks may seem complicated and voluminous. Good thing there is MagicalRecord, an independent library for Core Data created by MagicalPanda. And this tutorial will teach you how to speed up work with MagicalRecord quickly and easily.
MagicalRecord - easy to use, well designed and popular. The authors of the project said that the main task of MagicalRecord is to clear the code that you need to write to use Core Data and use one simple line of code to select the data, while allowing the user to optimize performance. “How is this possible?” You will think. This is possible thanks to convenient technology that uses the same template to configure, query and update Core Data. A design feature is the influence of the Ruby on Rails' ActiveRecord storage system.
Enough theory! Follow the instructions and you will see how MagicalRecord works. This article talks about how to create an application that will track which beer your favorite. This will allow you to:
Add beer to taste.
Let's start.
For a complete understanding of this article, you should already have a basic understanding of CoreData, at the core tutorial level for CoreData. In addition, you do not need any prior experience with CoreData.
If you don’t even have a basic Core Data experience, I recommend that you first read this Core Data tutorial and then come back here.
To get started, download this starter project. Once it loads, unzip the archive and run the application to check it.
The application has a basic navigation controller with the “Add” button, also a table, a search bar (if you pull down an empty table) and a segment selection for sorting alphabetically or by rating. If you click the “Add” button, you will go to the interface where you will need to enter the name of the beer and view information about it. If you try to enter something, it will not be saved for now.
Now inspect the code. In Project Navigator you will see:
Looking through, you may notice that there is no CoreData model, and AppDelegate.m also does not contain installation code. This is the perfect scenario for any project that you started without Core Data. But the understanding that without Core Data is nowhere will come only later.
Introducing MagicalRecord
In Project Navigator, select and expand the MagicalRecord group. Inside you will see the Categories and Core groups, as well as CoreData + MagicalRecord.h. Expand the Categories group and open a file called NSManagedObjectModel + MagicalRecord.h.
You will notice that, all names begin with the prefix MR_. In fact, if you go through any files to the Categories group, you will notice that all names starting with the MR_ prefix are exactly.
I don’t know about you, but it seems rather strange to me to start each name with the same letters. The good thing is that MagicalRecord makes it easy to change them using Shorthand Support.
Again, in Project Navigator, expand the Supporting Files group, and open the BeerTracker-Prefix.pch file. This is the precompiled title for the project. Add two lines of code to the “start-up project” in this file:
These two lines activate MagicalRecord for your project:
Note: if you want to add MagicalRecord to your project, you can take advantage of additional tips on their GitHub page.
You can also add MagicalRecord in the same way as I added it to the project:
The Beer Model
To start tracking your favorite varieties, you will need to create a model for this, because you are unlikely to be able to remember them all, right? In the Xcode menu, select File \ New \ File ... from the list on the left, select Core Data and select - Data Model.
Name the file BeerModel.xcdatamodeld and move it to the BeerTracker group.
In the project navigator, select the new BeerModel.xcdatamodeld kit and start editing it. Add an object and name it “Beer”. Add property - name of type String.
Add another object called BeerDetails. This object will follow indicators such as: user rating, notes and where to find the image. Add the following BeerDetails properties of the appropriate types:
Next, create a connection between the two objects, so that the beer object must know that BeerDetails belongs to it. In BeerDetails, create a new relationship and name it “beer” with the target beer. By choosing a beer destination, you created the first part of the relationship between the subjects.
Complete the relationship setup by selecting the Beer object. Add a relationship with the name beerDetails with the destination BeerDetails and invert the Beer. Your Beer object will now have a BeerDetails object to distinguish between the same varieties.
The next step is to classify the data to represent the objects. Use Xcode for this, but you have to be careful because Xcode is sometimes buggy.
First, modify the CoreData model by selecting it in the Xcode project navigator; make sure you select the Beer object in the “objects” panel and not among the BeerDetails objects.
Next, in the Xcode menu, go to Editor \ Create NSManagedObject Subclass ... check BeerModel, then click Next. Under the controls, check the boxes for Beer and BeerDetails if they are not set by default, and double-check that Beer is highlighted. Click “Next” and then “Create”. This way you will create new classes, Beer and BeerDetails with the corresponding objects and names. Look at the new classes and you will see that each class has properties that correspond to the properties of the objects that you defined.
Check again the properties that represent the relationship between the entities. You should see that the Beer class contains a beerDetails property of type BeerDetails * object. But, you will also see that the BeerDetails class contains the NSManagedObject * beer property. Why properties do not have beer type *? This is due to limitations in the “Xcode Create NSManagedObject Subclass” command. It does not affect this project, so just ignore it.
Note: Curious? I wonder why Xcode does not create cdjqcndf * Beer? Apparently, this is the result of restrictions in the Xcode CreateNSManagedObjectSubclass command. Since Xcode recognizes the CoreData Model, it is logical to assume that the team should be able to conclude that the properties of each class should be interconnected with each other.
However, this command not only generates properties of different types of classes. It also generates class names used to define these types, and the command is not correct enough to fully complete both tasks. First, you can simply generate the code and set the exact property of the type and subclass (adding a description as necessary). Another way is to reduce the number of tasks performed for Xcode. If you clearly defined the class name for each object in the Data Model Inspector before creating the classes themselves, then Xcode will create the correct type properties.
If you are interested, more sophisticated tools for creating classes from Xcode data models, one of them is mogenerator .
Now that you have created the classes for the data objects, it's time to initialize the Core Data stack. Open the AppDelegate.m file, and in the delegate method application: didFinishLaunchingWithOptions: add the following lines of code before the return statement:
If you used to work with the Xcode project created by Core Data, you probably saw how many codes are required to connect Core Data, and its initialization in the AppDelegate file. With MagicalRecord, all you need is just one line of code!
MagicalRecord provides several alternative ways to configure the Core Data stack, depending on the following:
If your Model file has the same base name as the project name (for example, the BeerTracker.xcdatamodeld model file within the framework of a project called BeerTracker), then you can use MagicalRecord in one of three convenient ways -setupCoreDataStack, setupCoreDataStackWithInMemoryStore, or setupAutoMataStackMarketackack.
But, since your models are called differently, you should use one of two other types of installation: setupCoreDataStackWithStoreNamed: or setupCoreDataStackWithAutoMigratingsqlitestorenamed:
Using the AutoMigrating configuration method, you can change your model, and if it is possible to automatically transfer your storage, MagicalRecord will do it for you. As a general rule, Core Data requires you to add code to handle small changes to the model.
Brewing (entity)
Now, your model and stack of Core Data - are tuned. You can start adding beer to your list. Open the BeerViewController.h file, and after class AMRatingControl, add:
You can also add beer properties after interface :
Then go to the BeerViewController.m file and import Beer.h and BeerDetails.h:
In viewDidLoad, add the following code block:
Troubleshooting: if you encounter errors after copying the previous block of code into your project and do Clean for your project, press Shift + Command + K, or go to Product \ Clean.
BeerViewController loads because:
Now that the view has loaded you will need to do the following:
There are several things that should be set so that you can edit and add new beers. First, you should be able to add or change the name. Edit textFieldDidEndEditing: so that it looks like the one shown below:
Now, when you finish adding with the name textField, it will set the name of the beer and all the contents of textField until the field is filled.
To save the contents of the note, you need to evaluate the beer, find textViewDidEndEditing :, and change the contents of the method to make it look like the following:
Next, make sure that the beer rating is updated after the user changes it in the menu: View Controller. Find the updateRating method, and add the following:
When a user clicks on the UIImageView on the "details" page, this allows him to add or modify a photo. In UIActionSheet displays, which allows the user to select an image taken by his camera, or take a new picture. If the user wants to take a picture, he must make sure that the image is also saved on disk. Instead of saving the image in CoreData (which can cause problems with computer performance), save the image in the user's documents with the rest of the photos, and this path is used only for Core Data.
The interaction between the camera and the library of photographs is controlled by implementing the protocol methods UIImagePickerControllerDelegate. You need to make BeerViewController a delegate for the UIImagePickerController, as it is a storyboard processing controller. Find the imagePickerController: didFinishPickingMediaWithinfo: method, and add the following:
Here is what happens in the code above:
You will need to modify ImageSaver a bit to get started. Now that the Beer class is created, you can not comment on the import line, and the line that sets the path to the image object. Open the ImageSaver.m file and change the import instructions:
Now you need to uncomment the line with the if statement.
The ImageSaver class is now fully prepared to accept the image and save it to your phone, documents, directories, and the path to the BeerDetails object.
Based on this, the user has two options: cancel or add a new beer. When creating the view, a Beer object was created, and inserted into managedObjectContext. Cancel should delete the Beer object. Find the cancelAdd method, and add the following:
MagicalRecord provides a good way to delete an object that automatically removes objects from the ManagedObjectContext. After deletion, the user will return to the main beer list.
If the user clicks the Done button, this will save the beer and return to the main list. Find the addNewBeer method. It simply displays the view controller and returns to the list. When view closes, the viewWillDisapper: method is called. This in turn will make a callssaveContext call.
The saveContext method is empty right now, so you need to add some code to save the object. Add the following code to the saveContext method:
Here are just a few lines of code! In AppDelegate.m, you configure the basic data of the WithMagicalRecord stack. This created access to the default application with managedObjectContext. When creating Beer and BeerDetails objects, they were added to defaultContext. MagicalRecord allows you to save managedObjectContext using saveToPersistentStoreWithCompletion :. The completion of the block gives you access to the NSError object, in case you could not save it. Here, you added a simple if / else block that registers what happens after you save the defaultContext.
Are you ready to test your creation? Now, launch your application! Press the "+" button, fill in any information you want. Then click Finish.
After you do this, you will notice that the new beer is not displayed on the list. Do not worry, it is not broken, and you will learn how to fix it all this a bit later. You may also have noticed that the debugger has a whole bunch of information. Contrary to what your logic might say, this is really good.
The Magic Debugger
When the application is launched, MagicalRecord introduces four things when entering the Core Data installation stack. This indicates that a Core Data setup has occurred, and that a defaultContext has been created. The same defaultContext that was mentioned above when you saved the Beer object.
After you click the Done button and save the MagicalRecord, a few more protocols appear. In the process of saving, the following protocols were produced:
MagicalRecord logs a large amount of information. If you have problems, or something does not work properly, you should check your protocols, because they carry a lot of useful information.
Note:
Although I do not recommend it, but if you really do not want to see what happens when the MagicalRecord protocols go into MagicalRecord.h and replace line 17:
on the
Another beer please!
If this application is worth all the effort, you can view all the beers that you have added. Open MasterViewController.m, and import Beer.h, and BeerDetails.h.
In order to get all beers stored in CoreData, you will need to make a selection. Add a selection To viewWillAppear: before calling the reloadData method.
At the first viewing of downloads, or when returning after viewing or adding Beer, it is worth loading all types of beer into the table.
Find the fetchAllBeers method, and add the following:
MasterViewController allows the user to sort the beer by rating with a rating of 5 to 1 per beer, or in alphabetical order (AZ). The first time the application starts, it creates an NSUserDefault (sort value by rating) and sets it by default. In this case, you:
Yes, that’s really all there is to it!
Once again, you are using MagicalRecord as a way to interact with Core Data. findAllSortedBy: Ascending is just one of many ways to fetch the master data of entities using MagicalRecord. Some others include (note - you will have to use one of them later):
There is much more that you can take advantage of - just check NSManagedObject + MagicalFinders.m.
To display the beer name and rating in the cell, follow configureCell: atIndex :, and add the following:
Now find the prepareForSegue: sender: method, and in the if which checks if the transition is the identifier of “editBeer,” add:
This is to pass the Beer object to the BeerViewController so that it displays information about the beer, allowing you to edit it.
Try to run the project again.
Now you will see the beer that was added earlier, with its rating. Also, you can select beer and edit information. When you return, the table will be updated! Very well!
Try to view the Beer object separately, and without editing the information, return to the main list. Take a look at the magazine. You should see:
When you finish viewing the details, the code you wrote will be saved in the default context in viewWillDisappear :. However, since changes have been made, MagicalRecord recognizes that there is no need to perform a save operation, and therefore it skips the process. Fortunately, there is no need for this, to think about whether you need to save - just try and save, and let MagicalRecord do it for you.
Final touches
There are several functions that you probably want to add to your application, for example, the ability to remove beer by pre-filling the list of your favorite beers and performing a search.
Removal
In MasterViewController.m, find the delegate method tableView: commitEditingStyle: forRowAtIndexPath :, and add the following code:
Please note there is a special saveContext call. You need to add the code and make sure the deletion is completed. If you have already done this once - can you figure out how to make it work? Ready......? Forward!
Add the following saveContext method:
Since you technically do not need to know when it will end, you can use one of MagicalRecord's capabilities to save managedObjectContext.
Launch the application and remove the beer (traditionally using cells). If you restart the application, but there is no beer there, you have done something very necessary! You saved the changes, which means that you used MagicalRecord correctly.
Pat yourself!
Demo Data
It would be nice to give users some basic data so that they can see how easily they can track their favorite varieties. In the AppDelegate.m file, import the Beer.h, BeerDetails.h files. Then, immediately after installing the Core Data stack, add the following:
The starter app you downloaded at the beginning of this article includes four images of cold beer. Here you can not only create four different types of beer, but also save them. Save the checkboxes to NSUserDefaults allowing the application to pre-populate the DataModel only once when it is launched for the first time.
Launch the app again so that you can see all new beers. Try uninstalling one and restarting the application; it will not return. If you want to see all beer samples again, uninstall the application in the simulator or device and run it again.
Search
Now that you have more than one beer, test your search capabilities. The starter application is already included in the search bar - scroll to the top of the table to see it. The only thing you need to add logic is to search the beer list.
Previously, you used MagicalRecord as a helper method to sample all sorts of beer, and simply return all the beer. So, if you want to get all types of beer that match a specific search term.
For this you will need to use NSPredicate. Previously, the tutorial talked about the sampling method with NSPredicate. Can you guess how to do a search? The logic should be inside doSearch in MasterViewController.m file.
Add the following code to the doSearch method:
For other methods to extract the results, check the MagicalRecord header files.
Launch the application again and scroll down the beer list until the search bar appears. Look for one of the beers on the list, then find one that is not on the list. Do you manage to achieve the desired result?
What's next?
I hope this MagicalRecord article showed you how easy it is to work with MagicalRecord. It really helps reduce the amount of code written! The basics that you learned in this lesson will help you develop all kinds of applications that help users keep track of things, as it allows you to use pictures, notes, and ratings. Enjoy it!
You can download the finished project here. It can come in handy if difficulties arise at some stages.
If you want to develop the BeerTracker project, here are a few ideas to help you develop this application:
Today I want to bring to your attention another translation, do not judge strictly :) I hope you find this material useful in your work.
For many years, Core Data has been an integral part of many OS X and iOS applications, providing storage and retrieval of user data. Apple is working hard to make the Core Data API easier to use and make it easier for developers to integrate into applications.
This fact indicates that Core Data is a highly modifiable project. Even if you know how to use Data Core, performing simple, daily tasks may seem complicated and voluminous. Good thing there is MagicalRecord, an independent library for Core Data created by MagicalPanda. And this tutorial will teach you how to speed up work with MagicalRecord quickly and easily.
MagicalRecord - easy to use, well designed and popular. The authors of the project said that the main task of MagicalRecord is to clear the code that you need to write to use Core Data and use one simple line of code to select the data, while allowing the user to optimize performance. “How is this possible?” You will think. This is possible thanks to convenient technology that uses the same template to configure, query and update Core Data. A design feature is the influence of the Ruby on Rails' ActiveRecord storage system.
Enough theory! Follow the instructions and you will see how MagicalRecord works. This article talks about how to create an application that will track which beer your favorite. This will allow you to:
Add beer to taste.
- Rate the beer.
- Make notes about beer.
- Take a photo of beer (just in case you went over and you need to make a note)
Let's start.
For a complete understanding of this article, you should already have a basic understanding of CoreData, at the core tutorial level for CoreData. In addition, you do not need any prior experience with CoreData.
If you don’t even have a basic Core Data experience, I recommend that you first read this Core Data tutorial and then come back here.
To get started, download this starter project. Once it loads, unzip the archive and run the application to check it.
The application has a basic navigation controller with the “Add” button, also a table, a search bar (if you pull down an empty table) and a segment selection for sorting alphabetically or by rating. If you click the “Add” button, you will go to the interface where you will need to enter the name of the beer and view information about it. If you try to enter something, it will not be saved for now.
Now inspect the code. In Project Navigator you will see:
- All controllers that you use to run the application.
- ImageSaver Utility Class
- Images that will be useful to you in future to fill in the source data
- Images.xcassets library, which already has several images used by the UI
- AMRating - convenient for rating control in other libraries
Looking through, you may notice that there is no CoreData model, and AppDelegate.m also does not contain installation code. This is the perfect scenario for any project that you started without Core Data. But the understanding that without Core Data is nowhere will come only later.
Introducing MagicalRecord
In Project Navigator, select and expand the MagicalRecord group. Inside you will see the Categories and Core groups, as well as CoreData + MagicalRecord.h. Expand the Categories group and open a file called NSManagedObjectModel + MagicalRecord.h.
You will notice that, all names begin with the prefix MR_. In fact, if you go through any files to the Categories group, you will notice that all names starting with the MR_ prefix are exactly.
I don’t know about you, but it seems rather strange to me to start each name with the same letters. The good thing is that MagicalRecord makes it easy to change them using Shorthand Support.
Again, in Project Navigator, expand the Supporting Files group, and open the BeerTracker-Prefix.pch file. This is the precompiled title for the project. Add two lines of code to the “start-up project” in this file:
#define MR_SHORTHAND
#import “CoreData+MagicalRecord.h”
These two lines activate MagicalRecord for your project:
- MR_SHORTHAND tells MagicalRecord that you do not want to enter MR_ before each method with MagicalRecord. By going to MagicalRecord + ShorthandSupport.m you can find out more about how this works. But this is beyond the scope of this article, and therefore will not be discussed here :)
- By importing CoreData + MagicalRecord.h, you can now access any of the MagicalRecord APIs in project files, and you no longer have to import them again and again into each file.
Note: if you want to add MagicalRecord to your project, you can take advantage of additional tips on their GitHub page.
You can also add MagicalRecord in the same way as I added it to the project:
- Download MagicalRecord Project from GitHub
- Drag and drop a MagicalRecord (containing CoreData + MagicalRecord.h, Categories, and Core) into your Xcode project
- Make sure that the CoreData database is added to the project settings in Build Phases \ Link Binary with Libraries
- In your precompiled header, add the following #ifdef __OBJC__:
#define MR_SHORTHAND
#import “CoreData+MagicalRecord.h”
The Beer Model
To start tracking your favorite varieties, you will need to create a model for this, because you are unlikely to be able to remember them all, right? In the Xcode menu, select File \ New \ File ... from the list on the left, select Core Data and select - Data Model.
Name the file BeerModel.xcdatamodeld and move it to the BeerTracker group.
In the project navigator, select the new BeerModel.xcdatamodeld kit and start editing it. Add an object and name it “Beer”. Add property - name of type String.
Add another object called BeerDetails. This object will follow indicators such as: user rating, notes and where to find the image. Add the following BeerDetails properties of the appropriate types:
- Property: Image. type: String
- Property: Note. type: String
- Property: Rating. type: Integer 16
Next, create a connection between the two objects, so that the beer object must know that BeerDetails belongs to it. In BeerDetails, create a new relationship and name it “beer” with the target beer. By choosing a beer destination, you created the first part of the relationship between the subjects.
Complete the relationship setup by selecting the Beer object. Add a relationship with the name beerDetails with the destination BeerDetails and invert the Beer. Your Beer object will now have a BeerDetails object to distinguish between the same varieties.
The next step is to classify the data to represent the objects. Use Xcode for this, but you have to be careful because Xcode is sometimes buggy.
First, modify the CoreData model by selecting it in the Xcode project navigator; make sure you select the Beer object in the “objects” panel and not among the BeerDetails objects.
Next, in the Xcode menu, go to Editor \ Create NSManagedObject Subclass ... check BeerModel, then click Next. Under the controls, check the boxes for Beer and BeerDetails if they are not set by default, and double-check that Beer is highlighted. Click “Next” and then “Create”. This way you will create new classes, Beer and BeerDetails with the corresponding objects and names. Look at the new classes and you will see that each class has properties that correspond to the properties of the objects that you defined.
Check again the properties that represent the relationship between the entities. You should see that the Beer class contains a beerDetails property of type BeerDetails * object. But, you will also see that the BeerDetails class contains the NSManagedObject * beer property. Why properties do not have beer type *? This is due to limitations in the “Xcode Create NSManagedObject Subclass” command. It does not affect this project, so just ignore it.
Note: Curious? I wonder why Xcode does not create cdjqcndf * Beer? Apparently, this is the result of restrictions in the Xcode CreateNSManagedObjectSubclass command. Since Xcode recognizes the CoreData Model, it is logical to assume that the team should be able to conclude that the properties of each class should be interconnected with each other.
However, this command not only generates properties of different types of classes. It also generates class names used to define these types, and the command is not correct enough to fully complete both tasks. First, you can simply generate the code and set the exact property of the type and subclass (adding a description as necessary). Another way is to reduce the number of tasks performed for Xcode. If you clearly defined the class name for each object in the Data Model Inspector before creating the classes themselves, then Xcode will create the correct type properties.
If you are interested, more sophisticated tools for creating classes from Xcode data models, one of them is mogenerator .
Now that you have created the classes for the data objects, it's time to initialize the Core Data stack. Open the AppDelegate.m file, and in the delegate method application: didFinishLaunchingWithOptions: add the following lines of code 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 used to work with the Xcode project created by Core Data, you probably saw how many codes are required to connect Core Data, and its initialization in the AppDelegate file. With MagicalRecord, all you need is just one line of code!
MagicalRecord provides several alternative ways to configure the Core Data stack, depending on the following:
- Backup Type
- Do you want to use auto-migration
- And the name of your Core Data Model file
If your Model file has the same base name as the project name (for example, the BeerTracker.xcdatamodeld model file within the framework of a project called BeerTracker), then you can use MagicalRecord in one of three convenient ways -setupCoreDataStack, setupCoreDataStackWithInMemoryStore, or setupAutoMataStackMarketackack.
But, since your models are called differently, you should use one of two other types of installation: setupCoreDataStackWithStoreNamed: or setupCoreDataStackWithAutoMigratingsqlitestorenamed:
Using the AutoMigrating configuration method, you can change your model, and if it is possible to automatically transfer your storage, MagicalRecord will do it for you. As a general rule, Core Data requires you to add code to handle small changes to the model.
Brewing (entity)
Now, your model and stack of Core Data - are tuned. You can start adding beer to your list. Open the BeerViewController.h file, and after class AMRatingControl, add:
@class Beer;
You can also add beer properties after interface :
@property (nonatomic, strong) Beer *beer;
Then go to the BeerViewController.m file and import Beer.h and BeerDetails.h:
#import "Beer.h"
#import "BeerDetails.h"
In viewDidLoad, add the following code block:
- (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 encounter errors after copying the previous block of code into your project and do Clean for your project, press Shift + Command + K, or go to Product \ Clean.
BeerViewController loads because:
- Or beer is chosen, or ...
- Clicked Add from MasterViewController
Now that the view has loaded you will need to do the following:
- Check if the Beer object is loaded. If not, it means that you need to add a new beer.
- If the Beer does not have any BeerDetails, create a BeerDetails object.
- To configure viewing, enter the name of the beer, its rating and pay attention to the content. If this beer does not yet have a name (in the case of a new beer being introduced, the project will give it the name “new beer”).
- If the beer contains the path to the image, it will load the images into the UIImageView.
There are several things that should be set so that you can edit and add new beers. First, you should be able to add or change the name. Edit textFieldDidEndEditing: so that it looks like the one shown below:
- (void)textFieldDidEndEditing:(UITextField *)textField {
if ([textField.text length] > 0) {
self.title = textField.text;
self.beer.name = textField.text;
}
}
Now, when you finish adding with the name textField, it will set the name of the beer and all the contents of textField until the field is filled.
To save the contents of the note, you need to evaluate the beer, find textViewDidEndEditing :, and change the contents of the method to make it look like the following:
- (void)textViewDidEndEditing:(UITextView *)textView {
[textView resignFirstResponder];
if ([textView.text length] > 0) {
self.beer.beerDetails.note = textView.text;
}
}
Next, make sure that the beer rating is updated after the user changes it in the menu: View Controller. Find the updateRating method, and add the following:
- (void)updateRating {
self.beer.beerDetails.rating = @(self.ratingControl.rating);
}
When a user clicks on the UIImageView on the "details" page, this allows him to add or modify a photo. In UIActionSheet displays, which allows the user to select an image taken by his camera, or take a new picture. If the user wants to take a picture, he must make sure that the image is also saved on disk. Instead of saving the image in CoreData (which can cause problems with computer performance), save the image in the user's documents with the rest of the photos, and this path is used only for Core Data.
The interaction between the camera and the library of photographs is controlled by implementing the protocol methods UIImagePickerControllerDelegate. You need to make BeerViewController a delegate for the UIImagePickerController, as it is a storyboard processing controller. Find the imagePickerController: didFinishPickingMediaWithinfo: method, 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 is what happens in the code above:
- imagePickerController: didFinishPickingMediaWithInfo: indirectly passes the link to the image selected by the user, where the image itself is already in the info dictionary, under the UIImagePickerControllerOriginalImage key ...
- If the Beer object already contains an image, the application will delete it from the disk, so that the User’s storage is not full without need.
- And then the new image is saved to disk and its path is added to BeerDetails in the image properties. Open ImageSaver and find saveImageToDisk: andToBeer to see how it looks. After the image is successfully saved, it is displayed in the UIImageView.
- After which the picker is hiding.
You will need to modify ImageSaver a bit to get started. Now that the Beer class is created, you can not comment on the import line, and the line that sets the path to the image object. Open the ImageSaver.m file and change the import instructions:
#import "ImageSaver.h"
#import "Beer.h"
#import "BeerDetails.h"
Now you need to uncomment the line with the if statement.
if ([imgData writeToFile:jpgPath atomically:YES]) {
beer.beerDetails.image = path;
}
The ImageSaver class is now fully prepared to accept the image and save it to your phone, documents, directories, and the path to the BeerDetails object.
Based on this, the user has two options: cancel or add a new beer. When creating the view, a Beer object was created, and inserted into managedObjectContext. Cancel should delete the Beer object. Find the cancelAdd method, and add the following:
- (void)cancelAdd {
[self.beer deleteEntity];
[self.navigationController popViewControllerAnimated:YES];
}
MagicalRecord provides a good way to delete an object that automatically removes objects from the ManagedObjectContext. After deletion, the user will return to the main beer list.
If the user clicks the Done button, this will save the beer and return to the main list. Find the addNewBeer method. It simply displays the view controller and returns to the list. When view closes, the viewWillDisapper: method is called. This in turn will make a callssaveContext call.
The saveContext method is empty right now, so you need to add some code to save the object. Add the following code to the saveContext method:
- (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);
}
}];
}
Here are just a few lines of code! In AppDelegate.m, you configure the basic data of the WithMagicalRecord stack. This created access to the default application with managedObjectContext. When creating Beer and BeerDetails objects, they were added to defaultContext. MagicalRecord allows you to save managedObjectContext using saveToPersistentStoreWithCompletion :. The completion of the block gives you access to the NSError object, in case you could not save it. Here, you added a simple if / else block that registers what happens after you save the defaultContext.
Are you ready to test your creation? Now, launch your application! Press the "+" button, fill in any information you want. Then click Finish.
After you do this, you will notice that the new beer is not displayed on the list. Do not worry, it is not broken, and you will learn how to fix it all this a bit later. You may also have noticed that the debugger has a whole bunch of information. Contrary to what your logic might say, this is really good.
The Magic Debugger
When the application is launched, MagicalRecord introduces four things when entering the Core Data installation stack. This indicates that a Core Data setup has occurred, and that a defaultContext has been created. The same defaultContext that was mentioned above when you saved the Beer object.
After you click the Done button and save the MagicalRecord, a few more protocols appear. In the process of saving, the following protocols were produced:
- The main thread is saved in defaultContext.
- All source materials will be saved and flagged 1.
- Synchronization will not take place simultaneously and all saved files will be marked with a 0 flag.
- The final protocols show that MagicalRecord knows about the two objects that need to be saved (Beer and BeerDetails objects), and that they are successfully saved.
MagicalRecord logs a large amount of information. If you have problems, or something does not work properly, you should check your protocols, because they carry a lot of useful information.
Note:
Although I do not recommend it, but if you really do not want to see what happens when the MagicalRecord protocols go into MagicalRecord.h and replace line 17:
#define MR_ENABLE_ACTIVE_RECORD_LOGGING 1
on the
#define MR_ENABLE_ACTIVE_RECORD_LOGGING 0
Another beer please!
If this application is worth all the effort, you can view all the beers that you have added. Open MasterViewController.m, and import Beer.h, and BeerDetails.h.
In order to get all beers stored in CoreData, you will need to make a selection. Add a selection To viewWillAppear: before calling the reloadData method.
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// Check if the user's sort preference has been saved.
...
[self fetchAllBeers];
[self.tableView reloadData];
}
At the first viewing of downloads, or when returning after viewing or adding Beer, it is worth loading all types of beer into the table.
Find the fetchAllBeers method, 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];
}
MasterViewController allows the user to sort the beer by rating with a rating of 5 to 1 per beer, or in alphabetical order (AZ). The first time the application starts, it creates an NSUserDefault (sort value by rating) and sets it by default. In this case, you:
- The recovered sort key is stored in NSUserDefaults
- If the sort key is “rating”, then the ascending variable should be marked as “no.” If alphabetically, then the value is yes.
- Run fetch.
Yes, that’s really all there is to it!
Once again, you are using MagicalRecord as a way to interact with Core Data. findAllSortedBy: Ascending is just one of many ways to fetch the master data of entities using MagicalRecord. Some others include (note - you will have to use one of them later):
- indAllInContext: - find all objects of the type in context, provided
- findAll - find all objects in the context of the current thread
- findAllSortedBy: ascending: inContext: - similar to the one that was used earlier, but limited to their ha, context
- findAllWithPredicate: - allows you to go to NSPredicate to search for objects.
- findAllSortedBy: ascending: withPredicate: inContext: - allows you to sort done, flagged, in a specific context. It also allows you to pass to NSPredicate for filtering.
There is much more that you can take advantage of - just check NSManagedObject + MagicalFinders.m.
To display the beer name and rating in the cell, follow 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 the prepareForSegue: sender: method, and in the if which checks if the transition is the identifier of “editBeer,” add:
if ([[segue identifier] isEqualToString:@"editBeer"]) {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
Beer *beer = self.beers[indexPath.row];
upcoming.beer = beer;
}
This is to pass the Beer object to the BeerViewController so that it displays information about the beer, allowing you to edit it.
Try to run the project again.
Now you will see the beer that was added earlier, with its rating. Also, you can select beer and edit information. When you return, the table will be updated! Very well!
Try to view the Beer object separately, and without editing the information, return to the main list. Take a look at the magazine. You should see:
-[NSManagedObjectContext(MagicalSaves) MR_saveWithOptions:completion:](0x8b6bfa0) NO CHANGES IN ** DEFAULT ** CONTEXT - NOT SAVING
When you finish viewing the details, the code you wrote will be saved in the default context in viewWillDisappear :. However, since changes have been made, MagicalRecord recognizes that there is no need to perform a save operation, and therefore it skips the process. Fortunately, there is no need for this, to think about whether you need to save - just try and save, and let MagicalRecord do it for you.
Final touches
There are several functions that you probably want to add to your application, for example, the ability to remove beer by pre-filling the list of your favorite beers and performing a search.
Removal
In MasterViewController.m, find the delegate method 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];
}
}
Please note there is a special saveContext call. You need to add the code and make sure the deletion is completed. If you have already done this once - can you figure out how to make it work? Ready......? Forward!
Add the following saveContext method:
// Save ManagedObjectContext using MagicalRecord
[[NSManagedObjectContext defaultContext] saveToPersistentStoreAndWait];
Since you technically do not need to know when it will end, you can use one of MagicalRecord's capabilities to save managedObjectContext.
Launch the application and remove the beer (traditionally using cells). If you restart the application, but there is no beer there, you have done something very necessary! You saved the changes, which means that you used MagicalRecord correctly.
Pat yourself!
Demo Data
It would be nice to give users some basic data so that they can see how easily they can track their favorite varieties. In the AppDelegate.m file, import the Beer.h, BeerDetails.h files. Then, immediately after installing 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 article includes four images of cold beer. Here you can not only create four different types of beer, but also save them. Save the checkboxes to NSUserDefaults allowing the application to pre-populate the DataModel only once when it is launched for the first time.
Launch the app again so that you can see all new beers. Try uninstalling one and restarting the application; it will not return. If you want to see all beer samples again, uninstall the application in the simulator or device and run it again.
Search
Now that you have more than one beer, test your search capabilities. The starter application is already included in the search bar - scroll to the top of the table to see it. The only thing you need to add logic is to search the beer list.
Previously, you used MagicalRecord as a helper method to sample all sorts of beer, and simply return all the beer. So, if you want to get all types of beer that match a specific search term.
For this you will need to use NSPredicate. Previously, the tutorial talked about the sampling method with NSPredicate. Can you guess how to do a search? The logic should be inside doSearch in MasterViewController.m file.
Add the following code to the doSearch method:
- (void)doSearch {
// 1. Get the text from the search bar.
NSString *searchText = self.searchBar.text;
// 2. Do a fetch on the beers that match Predicate criteria.
// In this case, if the name contains the string
self.beers = [[Beer findAllSortedBy:SORT_KEY_NAME ascending:YES withPredicate:[NSPredicate predicateWithFormat:@"name contains[c] %@", searchText] inContext:[NSManagedObjectContext defaultContext]] mutableCopy];
// 3. Reload the table to show the query results.
[self.tableView reloadData];
}
For other methods to extract the results, check the MagicalRecord header files.
Launch the application again and scroll down the beer list until the search bar appears. Look for one of the beers on the list, then find one that is not on the list. Do you manage to achieve the desired result?
What's next?
I hope this MagicalRecord article showed you how easy it is to work with MagicalRecord. It really helps reduce the amount of code written! The basics that you learned in this lesson will help you develop all kinds of applications that help users keep track of things, as it allows you to use pictures, notes, and ratings. Enjoy it!
You can download the finished project here. It can come in handy if difficulties arise at some stages.
If you want to develop the BeerTracker project, here are a few ideas to help you develop this application:
- Add the message “no beer created yet” on the MasterViewController - using the hasAtLeastOneEntity search method in MagicalRecord.
- Add a message indicating how much beer to search for. for the search controller - use the countOfEntitiesWithPredicate: method.
- Database implementations reset function - truncateAll search method from MagicalRecord
- For fun, open the MagicalRecordShorthand.h file and read the names of the methods - most of them are pretty clear, this header file should give you even more ideas on how to use MagicalRecord