Realm Tutorial: Getting Started

In this tutorial, you’ll learn how to use the Realm cross-platform mobile database solution by building an app that keeps track of wild animals. By Felipe Laso-Marsetti.

4.4 (23) · 1 Review

Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Adding Specimens

Still in AddNewEntryController.swift, add one more property to the class:

var specimen: Specimen!

This property stores the new specimen object.

Next, add this helper method to the class:

func addNewSpecimen() {
  let realm = try! Realm() // 1
    
  try! realm.write { // 2
    let newSpecimen = Specimen() // 3
      
    newSpecimen.name = nameTextField.text! // 4
    newSpecimen.category = selectedCategory
    newSpecimen.specimenDescription = descriptionTextField.text
    newSpecimen.latitude = selectedAnnotation.coordinate.latitude
    newSpecimen.longitude = selectedAnnotation.coordinate.longitude
      
    realm.add(newSpecimen) // 5
    specimen = newSpecimen // 6
  }
}

Here’s what the code above does:

  1. First, get a Realm instance, like before.
  2. Start the write transaction to add your new Specimen.
  3. Create a new Specimen instance.
  4. Assign the Specimen values. The values come from the input text fields in the user interface, the selected categories and the coordinates from the map annotation.
  5. Add the new Specimen to the realm.
  6. Assign the new Specimen to your specimen property.

You’ll need some sort of validator to make sure all of the fields are populated in your Specimen. validateFields() in AddNewEntryViewController exists to check for a specimen name and description. Since you’ve added the ability to assign a category to a specimen, you’ll check for that field too.

Find the line in validateFields() that looks like this:

if nameTextField.text!.isEmpty || descriptionTextField.text!.isEmpty {

Change that line to this:

if 
  nameTextField.text!.isEmpty || 
  descriptionTextField.text!.isEmpty || 
  selectedCategory == nil {

This verifies that all of the fields are populated and that you’ve selected a category.

Next, add the following method to the class:

override func shouldPerformSegue(
  withIdentifier identifier: String, 
  sender: Any?
  ) -> Bool {
    if validateFields() {
      addNewSpecimen()
        
      return true
    } else {
      return false
    }
}

In the code above, you call the method to validate the fields. If everything is filled in, you add the new specimen and return `true`; otherwise, you return `false`.

Build and run. Tap the + button to create a new specimen. Fill in the name and description, select a category, and then tap Confirm to add your Specimen to the database.

Add Specimen

The view controller dismisses, but nothing appears to happen. What’s the deal?

You posted the record to your realm, but you haven’t populated the map with your new specimen.

Retrieving Records

You added a specimen to the database that you want to show on the map.

Start by taking another look at the updated database in the Realm Browser:

Realm Specimen entry

You’ll see a single specimen with its fields populated, along with the latitude and longitude from the MKAnnotation. You’ll also see the link to your specimen’s category; this means your one-to-many Category relationship is working as expected.

Click the Category in your Specimen record to view the Category record itself.

Next, you’ll populate the map in the app.

Open SpecimenAnnotation.swift and add a property to the class:

var specimen: Specimen?

This holds the Specimen for the annotation.

Next, replace the initializer with the following:

init(
  coordinate: CLLocationCoordinate2D, 
  title: String, 
  subtitle: String, 
  specimen: Specimen? = nil
  ) {
    self.coordinate = coordinate
    self.title = title
    self.subtitle = subtitle
    self.specimen = specimen
}

The change here is to add an option to pass in a Specimen. The specimen has a default value of nil meaning you can omit that argument if you like. The rest of the app can still call the initializer with the first three arguments if there’s no specimen.

Open MapViewController.swift and add a new property to the class:

var specimens = try! Realm().objects(Specimen.self)

Since you want to store a collection of specimens in this property, you ask a Realm instance for all objects of type Specimen.

Now, add the following method to the class:

func populateMap() {
  mapView.removeAnnotations(mapView.annotations) // 1

  specimens = try! Realm().objects(Specimen.self) // 2

  // Create annotations for each one
  for specimen in specimens { // 3
    let coord = CLLocationCoordinate2D(
      latitude: specimen.latitude, 
      longitude: specimen.longitude);
    let specimenAnnotation = SpecimenAnnotation(
      coordinate: coord,
      title: specimen.name,
      subtitle: specimen.category.name,
      specimen: specimen)
    mapView.addAnnotation(specimenAnnotation) // 4
  }
}

Reviewing each numbered comment, you:

  1. Clear out all the existing annotations on the map to start fresh.
  2. Refresh your specimens property.
  3. Loop through specimens and create a SpecimenAnnotation with the coordinates of the specimen, as well as its name and category.
  4. Add each specimenAnnotation to the MKMapView.

You need to call this method from somewhere. Find viewDidLoad() and add this line to the end of its implementation:

populateMap()

That ensures that the map shows the specimens when the view controller loads.

Now you’ll change your annotation to include the specimen name and category. Find unwindFromAddNewEntry(segue:) and replace the method with the following implementation:

@IBAction func unwindFromAddNewEntry(segue: UIStoryboardSegue) {
  let addNewEntryController = segue.source as! AddNewEntryViewController
  let addedSpecimen = addNewEntryController.specimen!
  let addedSpecimenCoordinate = CLLocationCoordinate2D(
    latitude: addedSpecimen.latitude,
    longitude: addedSpecimen.longitude)
    
  if let lastAnnotation = lastAnnotation {
    mapView.removeAnnotation(lastAnnotation)
  } else {
    for annotation in mapView.annotations {
      if let currentAnnotation = annotation as? SpecimenAnnotation {
        if currentAnnotation.coordinate.latitude == addedSpecimenCoordinate.latitude &&
          currentAnnotation.coordinate.longitude == addedSpecimenCoordinate.longitude {
            mapView.removeAnnotation(currentAnnotation)
            break
        }
      }
    }
  }
    
  let annotation = SpecimenAnnotation(
    coordinate: addedSpecimenCoordinate,
    title: addedSpecimen.name,
    subtitle: addedSpecimen.category.name,
    specimen: addedSpecimen)
    
  mapView.addAnnotation(annotation)
  lastAnnotation = nil;
}

The system calls this method once you’ve returned from AddNewEntryController and there’s a new specimen to add to the map. When you add a new specimen to the map, it gets the generic annotation icon. With your category, you want to change that icon to the category-specific icon.

Here, you remove the last annotation added to the map and replace it with one that shows the specimen’s name and category.

Build and run. Create some new specimens of different categories and see how the map updates:

Map view with specimens

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. Next, you’ll populate this table view with some data.

Open LogViewController.swift and import RealmSwift:

import RealmSwift

Then, replace the specimens property with the following:

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

In the code above, you replace the placeholder array with Results that holds Specimens as you did in MapViewController. They’ll be sorted by name.

Next, add the following to tableView(_:cellForRowAt:) before return cell:

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")
}

This method populates 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:

Specimen list