Getting Started with MagicalRecord
Get started with MagicalRecord, an active record-style library to make your Core Data code cleaner and simpler! By Ed Sasena.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
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? = currentBeer.name
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()
cellNameRatingImage.addSubview(theRatingControl)
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 {
showImage(bImage)
}
}
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 currentBeer.name == "" {
title = "New Beer"
} else {
title = currentBeer.name
}
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()
:
NSManagedObjectContext.defaultContext().saveToPersistentStoreAndWait()
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:
currentBeer.name = 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 name
, note
, and rating
. image
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 {
ImageSaver.deleteImageAtPath(imageToDelete)
}
// 2
if ImageSaver.saveImageToDisk(image!, andToBeer: currentBeer) {
showImage(image!)
}
This code does the following:
- Do some clean-up work in getting rid of the original image if the user did a move and scale operation on it.
- 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 = currentBeer.name
Now the name
attribute gets populated into the table view row.
Build and run. It’s time to try adding a beer:
- Tap the + button in the top right corner of the list view.
- Once the app transitions to the detail view, add a name for a new beer in the Beer Name textbox.
- Tap Done.
The app transitions back to the list view and (drumroll) there it is! A new beer has been added to the list.
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:
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.
- Pressing and holding on the app icon until all icons begin to shake.
- Clicking the delete button that appears in the upper left corner of the icon.
- Confirming the deletion by clicking the delete button in the alert view.
- Choosing Hardware\Home from the iOS simulator menu.
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:
- Pressing and holding on the app icon until all icons begin to shake.
- Clicking the delete button that appears in the upper left corner of the icon.
- Confirming the deletion by clicking the delete button in the alert view.
- 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.