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.

Version

  • Swift 4.2, iOS 12, Xcode 10
Update note: Felipe Laso-Marsetti updated this tutorial for Swift 4.2, Xcode 10 and iOS 12. Bill Kastanakis wrote the original and Bradley Johnson completed an earlier update.

Realm is a cross-platform mobile database solution designed for mobile applications that you can integrate with your iOS projects. Unlike wrappers around Core Data, Realm doesn’t rely on Core Data or even an SQLite back end.

This tutorial introduces you to the basic features of Realm on iOS. By the end of this tutorial, you’ll know how to link the Realm framework, create models, perform queries and update records.

Getting Started

Use the Download Materials button at the top or bottom of this tutorial to download the starter project.

Here’s the scenario: You’ve accepted a position as an intern in the National Park Service. Your job is to document the species found in the largest national parks in the United States.

You’ll need an assistant to keep notes of your findings, but since the agency doesn’t have the budget to hire a new one, you decide to create a virtual assistant for yourself: an app named Agents Partner.

Open the starter project in Xcode. Currently, the app contains only the map functionality using MapKit, which is already set up in the project.

Note: If you’re interested in learning more about MapKit, check out the MapKit: Getting Started tutorial.

The starter project is missing Realm, so it’s time to add it.

Note: This tutorial is written against Realm 3.11.1.

An efficient way to install Realm is with CocoaPods.

Note: If you’re not familiar with CocoaPods or need help installing it, check out our CocoaPods tutorial.

In the root directory of the starter project, create a new file named Podfile. Copy the following text and paste it into the newly created file:

platform :ios, '12.0'
use_frameworks!

target 'Agents Partner' do
  pod 'RealmSwift'
end

Save and close the file.

In Terminal and in the root directory of your project, run the following command:

pod install

This tells CocoaPods to scan through your Podfile and install any pods that you have listed in the file. Pretty neat!

It may take a bit for Realm to install, so keep an eye on your terminal. Once it’s complete, you’ll see a line near the bottom that begins with Pod installation complete!.

In Finder, open the root directory of the starter project. Notice the folders that CocoaPods added as well as Agents Partner.xcworkspace.

If you have the starter project open in Xcode, close it now and open .xcworkspace by double-clicking the file. This file is what you need to open when you want to work on the project.

You just set up Realm using CocoaPods. Build and run the project to ensure everything compiles. If everything went as expected, you’ll see this:

Map view screen

Concepts and Classes Overview

To better understand what Realm does, here are some concepts and information about the classes you’ll use in this tutorial:

  • Realm: Realm instances are the heart of the framework. It’s your access point to the underlying database like a Core Data managed object context. You create instances using the Realm() initializer.
  • Object: This is your Realm model. The act of creating a model defines the schema of the database. To create a model, you subclass Object and define the fields you want to persist as properties.
  • Relationships: You create one-to-many relationships between objects by declaring a property of the type of the Object to which you want to refer. You can create many-to-one and many-to-many relationships via a property of type List.
  • Write Transactions: Any operations in the database, like creating, editing or deleting objects, must be performed within writes by calling write(_:) on Realm instances.
  • Queries: To retrieve objects from the database you use queries. The simplest form of a query is calling objects() on a Realm instance and passing in the class of the Object you’re looking for. If your data retrieval needs are more complex, you can make use of predicates, chain your queries and order your results.
  • Results: Results is an auto-updating container type that you get back from object queries. They have many similarities with regular Arrays including the subscript syntax.

With a brief introduction to Realm, it’s time to get your feet wet and build the rest of the project.

Your First Model

Open Specimen.swift from the Models group and add the following implementation:

import Foundation
import RealmSwift

class Specimen: Object {
  @objc dynamic var name = ""
  @objc dynamic var specimenDescription = ""
  @objc dynamic var latitude = 0.0
  @objc dynamic var longitude = 0.0
  @objc dynamic var created = Date()
}

The code above adds a few properties:

name and specimenDescription store the specimen’s name and description. Specific data types in Realm, such as strings, must be initialized with a value. In this case, you initialize them with an empty string.

latitude and longitude store the coordinates for the specimen. Here, you set the type to Double and initialize them with 0.0.

created stores the creation date of the specimen. Date() returns the current date so the property is initialized with that value.

With your first model created in Realm, are you ready to use this knowledge in a small challenge?

Specimens should be separated into different categories. The challenge is to create a Category model by yourself. Name the file Category.swift and give your new model a single String property named name.

If you want to check your work, the solution is below:

[spoiler title=”Category object”]

Category.swift will look like this:

import Foundation
import RealmSwift

class Category: Object {
  @objc dynamic var name = "" 
}

[/spoiler]

You have a Category model that you somehow need to relate to the Specimen model.

Recall the note above that stated you could create relationships between models by declaring a property with the appropriate model to be linked.

Open Specimen.swift and add the following declaration below the other properties:

@objc dynamic var category: Category!

This sets up a one-to-many relationship between Specimen and Category. This means each Specimen can belong to only one Category, but each Category can have many Specimens.

You now have your basic data models in place. It’s time to add some records to your database!

Adding Records

When the user adds a new specimen, they can enter the specimen name and select a category. Open CategoriesTableViewController.swift. This view controller presents the list of categories in a table view so the user can select one.

Before you start writing code to integrate Realm, you need to import the RealmSwift framework. Add the following line to the top of the file, below import UIKit:

import RealmSwift

You’ll populate this table view with some default categories. These Category instances can be stored in an instance of Results.

CategoriesTableViewController has a categories array as a placeholder for now. Find the following code at the top of the class definition:

var categories: [Any] = []

Replace that code with the following:

let realm = try! Realm()
lazy var categories: Results<Category> = { self.realm.objects(Category.self) }()

When you want to fetch objects, you always define the models you want. In the code above, you first create a Realm instance and then populate categories by calling objects(_:) on it, passing in the class name of the model type you want.

Note: To simplify the code required in this tutorial, you’re using try! when calling Realm methods that throw an error. In your own code, you should be using try and do / catch to catch and handle errors.

You want to give your user some default categories to choose from the first time the app runs.

Add the following helper method to the class definition:

private func populateDefaultCategories() {
  if categories.count == 0 { // 1
    try! realm.write() { // 2
      let defaultCategories =
        ["Birds", "Mammals", "Flora", "Reptiles", "Arachnids" ] // 3
      
      for category in defaultCategories { // 4
        let newCategory = Category()
        newCategory.name = category
        
        realm.add(newCategory)
      }
    }
    
    categories = realm.objects(Category.self) // 5
  }
}

Here’s what’s going on in each numbered line:

  1. If count is equal to 0 this means the database has no Category records. This is the case the first time you run the app.
  2. This starts a transaction on realm, and you’re now ready to add some records to the database.
  3. Here, you create the list of default category names and then iterate through them.
  4. For each category name, you create a new instance of Category, populate name and add the object to realm.
  5. You fetch all of the categories you created and store them in categories.

Add the following line to the end of viewDidLoad():

populateDefaultCategories()

This calls the helper method to populate your test categories when the view loads.

Now that you have some data, you’ll update the table view data source methods to show the categories. Find tableView(_:cellForRowAt:) and add the following before return cell:

let category = categories[indexPath.row]
cell.textLabel?.text = category.name

This implementation retrieves a category from categories based on the index path. It then sets the cell’s text label to show the category’s name.

Next, add this property below the other properties you added to CategoriesTableViewController:

var selectedCategory: Category!

You’ll use this property to store the currently selected Category.

Find tableView(_:willSelectRowAtIndexPath:) and add the following before return indexPath:

selectedCategory = categories[indexPath.row]

This stores the user’s selection in the property selectedCategory you declared above.

Build and run your app.

Adding a category

Zoom and pan the map to somewhere interesting and create a new annotation by tapping on the + button in the top-right. Tap on the map pin to select it, and then tap on the annotation data to edit the details. Finally, tap the Category text field to see the list of categories as shown below:

Category overview

You can select a category, but that only saves it to the property and not anywhere else in the database. It’s nice to see the categories show up in the app, but it’s always reassuring to see the records in the database. You can do this via the Realm Browser.

Introducing the Realm Browser

Realm includes the Realm Browser for reading and editing databases. The Realm database format is proprietary and not human-readable.

You can download the Realm Browser here.

Realm Browser

Working With Realm Browser

It’s important to know where your Realm database is stored while developing your app — and there’s a neat trick you can use to find out where it is.

Open MapViewController.swift and add the following line to the top of the file below the existing import statements:

import RealmSwift

Add the following line to viewDidLoad() after the call to super.viewDidLoad():

print(Realm.Configuration.defaultConfiguration.fileURL!)

This line prints the database location to the debug console. It’s a short step to then browse the database using the Realm Browser.

Build and run your app, and you’ll see that it reports the location of the database in the Xcode console.

The easiest way to go to the database location is to open Finder, press Shift-Command-G and paste in the path your app reported.

Once you open the folder in Finder, you might see one or two files. One of them is default.realm, which is your database file. The second file, that may or may not be present, is default.realm.lock. The lock file prevents modification from other apps while the database is in use.

If you haven’t yet downloaded the Realm Browser, download it from the App Store. Double-click default.realm to open it with Realm Browser:

Once the database is open in Realm Browser, you’ll see Category with a 5 next to it. This means that this class contains five records. Click a class to inspect the individual fields contained within.

Realm categories overview

Adding Categories

You can now put in place the logic to set the category of a Specimen.

Open AddNewEntryController.swift and import the RealmSwift framework below the existing import statements:

import RealmSwift

Add the following property to the class:

var selectedCategory: Category!

You’ll use this to store the selected Category.

Next, find unwindFromCategories(segue:) and add the following implementation inside:

if segue.identifier == "CategorySelectedSegue" {
  let categoriesController = segue.source as! CategoriesTableViewController
  selectedCategory = categoriesController.selectedCategory
  categoryTextField.text = selectedCategory.name
}

unwindFromCategories(segue:) is called when the user selects a category from CategoriesTableViewController that you set up in the previous step. Here, you retrieve the selected category, store it in selectedCategory and fill in the text field with the category’s name.

You can continue by creating your first Specimen!

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

Fetches With Predicates

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

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

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

Add this method 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.self)
      .filter(predicate).sorted(byKeyPath: "name", ascending: true) // 3
  case 1:
    searchResults = realm.objects(Specimen.self).filter(predicate)
      .sorted(byKeyPath: "created", ascending: true) // 4
  default:
    searchResults = realm.objects(Specimen.self).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, take them in the order they’re returned from the database.

Now, you’ll actually perform the filtering when the user interacts with the search field. In updateSearchResults(for:), add the following two lines at the beginning of the method:

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

Since the search results table view calls the same data source methods, you need to make a small change to tableView(_:cellForRowAt:) 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 it and replace it with the following:

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

This code checks whether the searchController is active. If so, it retrieves the specimen from searchResults. If not, it retrieves the specimen from specimens instead.

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

Add the following implementation to scopeChanged(sender:):

let scopeBar = sender as! UISegmentedControl
let realm = try! Realm()
  
switch scopeBar.selectedSegmentIndex {
case 1:
  specimens = realm.objects(Specimen.self)
    .sorted(byKeyPath: "created", ascending: true)
default:
  specimens = realm.objects(Specimen.self)
    .sorted(byKeyPath: "name", ascending: true)
}
  
tableView.reloadData()

Here, you check which scope button is pressed (A-Z, or Date Added) and sort. 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.

Specimen search result

Updating Records

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

If you tap in a cell in LogViewController, you’ll segue to AddNewEntryViewController, but with the fields empty. 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 fills 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 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"
}

This code sets the navigation title to indicate whether the user is adding or updating a specimen. If it’s an existing specimen, you also call your helper method to populate the fields.

You need a method to update the specimen record with the user’s changes. Add the following method:

func updateSpecimen() {
  let realm = try! Realm()
    
  try! realm.write {
    specimen.name = nameTextField.text!
    specimen.category = selectedCategory
    specimen.specimenDescription = 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 update the data fields.

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

Next, you’ll call the above method when the user taps Confirm. Find shouldPerformSegue(withIdentifier:sender:) and replace the call to addNewSpecimen() with the following:

if specimen != nil {
  updateSpecimen()
} else {
  addNewSpecimen()
}

This calls your method to update the data when appropriate.

Open LogViewController.swift and add the following implementation for prepare(for:sender:):

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
  if (segue.identifier == "Edit") {
    let controller = segue.destination as! AddNewEntryViewController
    var selectedSpecimen: Specimen!
    let indexPath = tableView.indexPathForSelectedRow
      
    if searchController.isActive {
      let searchResultsController =
        searchController.searchResultsController as! UITableViewController
      let indexPathSearch = searchResultsController.tableView.indexPathForSelectedRow
        
      selectedSpecimen = searchResults[indexPathSearch!.row]
    } else {
      selectedSpecimen = specimens[indexPath!.row]
    }
      
    controller.specimen = selectedSpecimen
  }
}

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

Build and run your app. Open the Log view and tap on a Specimen. You’ll see the details with all of the fields populated and ready for editing.

Edit Specimen

Where to Go From Here?

You can download the final project using the Download materials button at the top or bottom of this tutorial.

In this tutorial, you learned how to create, update and fetch records from a Realm database. You also learned how to use predicates and sort the results by their properties.

Many features of Realm were not covered in this tutorial, but you can learn about those topics and much more in the official documentation or with our book, Realm: Building Modern Swift Apps with Realm Database.

Thank you for taking the time to read this tutorial. I hope you enjoyed it :)

If you have any comments or questions on this tutorial, please join the discussion below!

Contributors

Comments