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.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

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 namenote, and ratingimage is taken care of already in viewDidLoad() which utilizes the ImagePickerControllerDelegate code. There is one more block of code to add to the ImagePickerControllerDelegate.

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

// 1
if let imageToDelete = currentBeer.beerDetails.image {
  ImageSaver.deleteImageAtPath(imageToDelete)
}

// 2
if ImageSaver.saveImageToDisk(image!, andToBeer: currentBeer) {
  showImage(image!)
}

This code does the following:

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

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

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

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

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

Just before } else {, add the following:

beer.beerDetails.image = pathName

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

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

var beers: [Beer]!

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

let sortKey = NSUserDefaults.standardUserDefaults().objectForKey(wbSortKey) as? String
let ascending = (sortKey == sortKeyRating) ? false : true

// Fetch records from Entity Beer using a MagicalRecord method.
beers = Beer.findAllSortedBy(sortKey, ascending: ascending) as! [Beer]

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

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

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

return beers.count

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

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

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

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

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

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

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

AddBeer

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.

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

Contributors

Ed Sasena

Author

Over 300 content creators. Join our team.