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.
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
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 Specimen
s 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:
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:
- First you create a predicate which searches for
name
s that start withsearchString
. The[c]
that followsBEGINSWITH
indicates a case insensitive search. - You then grab a reference to the currently selected scope index from the search bar
- If the first segmented button is selected, sort the results by name ascending.
- If the second button is selected, sort the results by created date ascending.
- 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!
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.