Realm Tutorial: Getting Started

Learn how to use Realm, a popular cross-platform mobile database that is an alternative to Core Data. By Bradley Johnson.

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

A Different View

You might have noticed the Log button in the top-left of the map view. In addition to the map, the app also has a text-based table view listing of all annotations called the Log View. You will now populate this table view with some data.

Open LogViewController.swift and import RealmSwift again below the other import statements:

import RealmSwift

Then replace the specimens property with the following:

var specimens = try! Realm().objects(Specimen).sorted("name", ascending: true)

In the code above, you replace the placeholder array with a Results which will hold Specimens just as you did in MapViewController. They will be sorted by name.

Next, replace tableView(_:cellForRowAtIndexPath:) with the following implementation:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
  let cell = self.tableView.dequeueReusableCellWithIdentifier("LogCell") as! LogCell

  let specimen = specimens[indexPath.row]

  cell.titleLabel.text = specimen.name
  cell.subtitleLabel.text = specimen.category.name

  switch specimen.category.name {
  case "Uncategorized":
    cell.iconImageView.image = UIImage(named: "IconUncategorized")
  case "Reptiles":
    cell.iconImageView.image = UIImage(named: "IconReptile")
  case "Flora":
    cell.iconImageView.image = UIImage(named: "IconFlora")
  case "Birds":
    cell.iconImageView.image = UIImage(named: "IconBird")
  case "Arachnid":
    cell.iconImageView.image = UIImage(named: "IconArachnid")
  case "Mammals":
    cell.iconImageView.image = UIImage(named: "IconMammal")
  default:
    cell.iconImageView.image = UIImage(named: "IconUncategorized")
  }
  return cell
}

This method will now populate the cell with the specimen’s name and category..

Build and run your app. Tap Log and you’ll see all of your entered specimens in the table view like so:

RW Realm 2016-03-18 at 9.23.37 PM

Fetching With Predicates

You really want your app to rock, so you’ll need a handy search feature. Your starter project contains an instance of UISearchController — you’ll just need to add a few modifications specific to your app in order to make it work with Realm.

In LogViewController.swift, replace the searchResults property with the following:

var searchResults = try! Realm().objects(Specimen)

Now add the method below to the class:

func filterResultsWithSearchString(searchString: String) {
  let predicate = NSPredicate(format: "name BEGINSWITH [c]%@", searchString) // 1
  let scopeIndex = searchController.searchBar.selectedScopeButtonIndex // 2
  let realm = try! Realm()

  switch scopeIndex {
  case 0:
    searchResults = realm.objects(Specimen).filter(predicate).sorted("name", ascending: true) // 3
  case 1:
    searchResults = realm.objects(Specimen).filter(predicate).sorted("created", ascending: true) // 4
  default:
    searchResults = realm.objects(Specimen).filter(predicate) // 5
  }
}

Here’s what the above function does:

  1. First you create a predicate which searches for names that start with searchString. The [c] that follows BEGINSWITH indicates a case insensitive search.
  2. You then grab a reference to the currently selected scope index from the search bar
  3. If the first segmented button is selected, sort the results by name ascending.
  4. If the second button is selected, sort the results by created date ascending.
  5. If none of the buttons are selected, don’t sort the results — just take them in the order they’re returned from the database.

Now you need to actually perform the filtering when the user interacts with the search field. In updateSearchResultsForSearchController(_:) add the following two lines at the beginning of the method:

let searchString = searchController.searchBar.text!
filterResultsWithSearchString(searchString)

Since the search results table view calls the same data source methods, you’ll need a small change to tableView(_:cellForRowAtIndexPath:) to handle both the main log table view and the search results. In that method, find the line that assigns to specimen:

let specimen = specimens[indexPath.row]

Delete that one line and replace it with the following:

let specimen = searchController.active ? searchResults[indexPath.row] : specimens[indexPath.row]

The above code checks whether the searchController is active; if so, it retrieves the specimen from searchResults; if not, then it retrieves the specimen from specimens instead.

Finally you’ll need to add a function to sort the returned results when the user taps a button in the scope bar.

Replace the empty scopeChanged(_:) with the code below:

@IBAction func scopeChanged(sender: AnyObject) {

  let scopeBar = sender as! UISegmentedControl
  let realm = try! Realm()

  switch scopeBar.selectedSegmentIndex {
  case 0:
    specimens = realm.objects(Specimen).sorted("name", ascending: true)
  case 1:
    specimens = realm.objects(Specimen).sorted("created", ascending: true)
  default:
    specimens = realm.objects(Specimen).sorted("name", ascending: true)
  }
  tableView.reloadData()
}

In the code above you check which scope button is pressed — A-Z, or Date Added — and call arraySortedByProperty(_:ascending:) accordingly. By default, the list will sort by name.

Build and run your app; try a few different searches and see what you get for results!

Realm Searches

Updating Records

You’ve covered the addition of records, but what about when you want to update them?

If you tap in a cell in LogViewController you will segue to the AddNewEntryViewController but with the fields empty. Of course the first step to letting the user edit the fields is to show the existing data!

Open AddNewEntryViewController.swift and add the following helper method to the class:

func fillTextFields() {
  nameTextField.text = specimen.name
  categoryTextField.text = specimen.category.name
  descriptionTextField.text = specimen.specimenDescription

  selectedCategory = specimen.category
}

This method will fill in the user interface with the specimen data. Remember, AddNewEntryViewController has up to this point only been used for new specimens, so those fields have always started out empty.

Next, add the following lines to the end of viewDidLoad():

if let specimen = specimen {
  title = "Edit \(specimen.name)"
  fillTextFields()
} else {
  title = "Add New Specimen"
}

The above code sets the navigation bar title to say whether the user is adding a new specimen or updating an existing one. If it’s an existing specimen, you also call your helper method to fill in the fields.

Now you’ll need a method to update the specimen record with the user’s changes. Add the following method to the class:

func updateSpecimen() {
  let realm = try! Realm()
  try! realm.write {
    self.specimen.name = self.nameTextField.text!
    self.specimen.category = self.selectedCategory
    self.specimen.specimenDescription = self.descriptionTextField.text
  }
}

As usual, the method begins with getting a Realm instance and then the rest is wrapped inside a write() transaction. Inside the transaction, you simply update the three data fields.

Six lines of code to update the Specimen record is all it takes! :]

Now you need to call the above method when the user taps Confirm. Find shouldPerformSegueWithIdentifier(_:sender:) and replace it with the following:

override func shouldPerformSegueWithIdentifier(identifier: String?, sender: AnyObject?) -> Bool {
  if validateFields() {
    if specimen != nil {
      updateSpecimen()
    } else {
      addNewSpecimen()
    }
    return true
  } else {
    return false
  }
}

This will call your helper method to update the data when appropriate.

Now open LogViewController.swift and add the following implementation for prepareForSegue(_:sender:):

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
  if (segue.identifier == "Edit") {
    let controller = segue.destinationViewController as! AddNewEntryController
    var selectedSpecimen: Specimen!
    let indexPath = tableView.indexPathForSelectedRow

    if searchController.active {
      let searchResultsController = searchController.searchResultsController as! UITableViewController
      let indexPathSearch = searchResultsController.tableView.indexPathForSelectedRow
      selectedSpecimen = searchResults[indexPathSearch!.row]
    } else {
      selectedSpecimen = specimens[indexPath!.row]
    }
    controller.specimen = selectedSpecimen
  }
}

You need to pass the selected specimen to the AddNewEntryController instance. The complication with the if / else is because getting the selected specimen is slightly different depending on whether the user is looking at search results or not.

Build and run your app; open the Log view and tap on an existing Specimen. You should see the details with all the fields filled in, ready for editing.

Realm Editing Specimen

Bradley Johnson

Contributors

Bradley Johnson

Author

Over 300 content creators. Join our team.