Why do we love Realm?
For many Core Data is a synonym for an iOS database. Mobile Core Data appeared in 2009 and for five years was the one and only solution. Nowadays Core Data in iOS 9 looks almost the same as the first from iOS 3 SDK. That makes it a little bit out-of-date, especially in Swift app. Fortunately since 2014 there is Realm – new king in the database kingdom.
Core Data was never an easy and nice framework. That’s why GitHub is full of wrapper frameworks like MagicalRecord, AlecrimCoreData or CoreDataStack. But it’s still the same old and not-so-fast Core Data.
According to the benchmarks Realm is from 5 to 30 times faster than Core Data. In addition it is much simpler than CD. Still not convinced? It’s open source and has dedicated version for iOS (Objective-C and Swift) and Android (Java). That means you can share databases and use the same high-level models across platforms.
Atlassian, Starbucks, McDonald’s, Alibaba and Virgin Mobile have trusted Realm and use it in their mobile apps. I believe it’s about time to abandon Core Data and switch to Realm. Don’t waste time and make a simple to-do app with Realm.
If you want to start from scratch with me, clone this GitHub repository, checkout to scratch
branch and do pod install
. Remember that final version is available in master
branch.
That’s how the app is going to look like.
Right now you should have an almost empty project similar to default Master-Detail Application template. That’s a right place to make a new class which will store to-do task’s details. Make this simple and store only title, optional description and current status (do or done). Take a look at my Task
class.
1 2 3 4 5 6 7 8 9 | import RealmSwift class Task: Object { dynamic var taskTitle = "" dynamic var taskDescription: String? = nil dynamic var done = false dynamic var date = NSDate() } |
First of all, remember to import RealmSwift
framework. Then notice that the Task
is an Object
subclass – Realm data model class.
All variables here are dynamic
. That’s a Realm requirement for almost all (except List
and RealmOptional
) properties that can be stored in Realm. Well, that’s the best time for a few words about properties’ types used in Realm.
You can store String
s, Bool
s, all numbers (like Double
, Float
, Int
and all its variations – in addition there is no need to typecast from/to a NSNumber
like in Core Data), NSDate
s, NSData
s, other Object
s (to-one relationship) and List
s of Object
s (to-many relationship).
Elementary types’ optional properties can look weird at first glance, but they’re just fine. Just take a look at the cheatsheet and remember you can access a RealmOptional
‘s value through a value
property.
Back to the code. Probably you have noticed that I added date
to Task
class. That’s because we’re going to sort all tasks by date – recent tasks should be first.
All right, we’ve got a Task
class. Now we should find a way to retrieve all tasks from a database. With Realm it’s really easy. Although let’s make some helper to make the code well-organised. That’s how my RealmHelper
looks.
1 2 3 4 5 6 7 8 | class RealmHelper { static func objects<T: Object>(type: T.Type) -> Results<T>? { let realm = try? Realm() return realm?.objects(type) } } |
I made it generic, so new Object
subclass won’t be a problem. Don’t be afraid of Results
– it’s like an Array
, but let you do for free some great things like using notifications.
Notice that according to Realm documentation you should never use Realm
instance as a singleton – that can occur in many hard-to-solve thread issues. Now you see why I prefer helper like this – every single task for objects creates Realm
instance and then returns what you want.
Don’t worry about performance or issues about objects from two or more returns – there is plenty of magic here and Object
s are auto-updating. Please take a look at the example in the documentation.
We can retrieve tasks, but still we can’t save, update or delete them. Let’s write an extension for Object
, but still remember about Realm instances logic.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import RealmSwift extension Object { func add() { let realm = try? Realm() try! realm?.write { realm?.add(self) } } func update(updateBlock: () -> ()) { let realm = try? Realm() try! realm?.write(updateBlock) } func delete() { let realm = try? Realm() try! realm?.write { realm?.delete(self) } } } |
We’ve got all database business ready. Before check it out let’s talk about UI logic behind the app.
I decided that MasterViewController
‘s table view should show all tasks – unfinished recent first. A cell selection should open a DetailViewController
which allows edit task’s details. Add button should push a DetailViewController
. Cell’s swipe should allow to remove cell’s task. Let’s do it one by one.
Tasks in table view
We should have all tasks in MasterViewController
, so add this at the top of the class.
1 | var objects = RealmHelper.objects(Task) |
That retrieves all objects, but as I said, I prefer unfinished recent tasks first. Realm loves chaining, but don’t try sorted(_:).sorted(_:)
– it won’t work as expected. Instead try something like this:
1 | var objects = RealmHelper.objects(Task)?.sorted(["done", SortDescriptor(property: "date", ascending: false)]) |
First sort by done
and then by date
property. You’ve got to be careful here – properties’ names are just strings.
Don’t forget about UITableViewController
‘s delegate methods. These set number of rows and section right.
1 2 3 4 5 6 7 | override func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1 } override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return objects?.count ?? 0 } |
Cells should look different for done and to do tasks. Strikethrough and gray text color for done should be all right. We can achieve this by attributedText
and textColor
properties.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) let object = objects![indexPath.row] let attributedText = NSMutableAttributedString(string: object.taskTitle) if object.done { attributedText.addAttribute(NSStrikethroughStyleAttributeName, value: 1, range: NSMakeRange(0, attributedText.length)) } cell.textLabel?.attributedText = attributedText cell.textLabel?.textColor = object.done ? .grayColor() : .blackColor() return cell } |
DetailViewController
is going to let each task be changed. We’ve got to cover this in MasterViewController
. Each task update should be visible in the main view controller. The easiest way to do that is using Realm notifications.
First, we need a property that stores NotificationToken
. Then we should add notification block for objects
we have. It should look like this:
1 2 3 4 5 6 7 8 9 10 11 | var token: NotificationToken? override func viewDidLoad() { super.viewDidLoad() token = objects?.addNotificationBlock { _ in self.tableView.reloadData() } ... } |
The notification stays active as long as a reference is held to the returned notification token. You should hold onto a strong reference to this token on the class registering for updates, as notifications are automatically un-registered when the notification token is deallocated.
Removing tasks
It’s quite easy and can be done through these two delegate methods.
1 2 3 4 5 6 7 8 9 10 | override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool { return true } override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) { if editingStyle == .Delete { objects![indexPath.row].delete() tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade) } } |
Add button
In this case insertNewbutton(_:)
always executes a segue. So we’ve got to override prepeareForSegue(_:sender:)
method. If there is any selected row then we should pass it to the detail view controller. First, we should add a task
property to the DetailViewController
:
1 | var task: Task? |
Then go back to MasterViewController
and be prepared for the segue.
1 2 3 4 5 6 7 8 9 | override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == "showDetail" { if let indexPath = tableView.indexPathForSelectedRow { let object = objects![indexPath.row] let controller = segue.destinationViewController as! DetailViewController controller.task = object } } } |
Task details
We should show all details in a DetailsViewController
. viewDidLoad()
seems to be a right place.
Add this at the bottom of the func’s body.
1 2 3 4 5 | if let task = task { titleTextField.text = task.taskTitle descriptionTextView.text = task.taskDescription ?? "" doneSwitch.on = task.done } |
Last, but not least. We should save all the changes to the task
object when Save button is pressed. If we’re adding new task, then should execute add()
on it. When updating an elder one, we should do it in an update block. Then return to the previous view controller. Take a look at my approach of this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | func saveObject() { if let task = task { updateTask(task) } else { task = Task() task!.add() updateTask(task!) } navigationController?.popViewControllerAnimated(true) } func updateTask(task: Task) { task.update { task.taskTitle = self.titleTextField.text ?? "" task.taskDescription = self.descriptionTextView.text task.done = self.doneSwitch.on task.date = NSDate() } } |
A saveObject()
is a saveButton
‘s action. If there is a task, it just executes updateTask(_:)
on it. If there is no task, then before updating, it creates and saves one. Of course all properties can be fulfilled before saving the task, but that makes part of the code reusable.
That’s all!
It wasn’t so hard, was it? 🙂 I hope you’re convinced to Realm now. It’s efficient in little projects like this, but also in bigger ones.
Thank you for reading and remember that you can find all the source code on our GitHub repository mentioned at the beginning.
About the author
Ready to take your business to the next level with a digital product?
We'll be with you every step of the way, from idea to launch and beyond!