Tutorial: Build iOS App from Scratch
If you prefer to watch a video walkthrough of this tutorial you can do that below. Otherwise, scroll down for the step-by-step text instructions.
Building Your First Realm Platform iOS App
First, install the Realm Object Server if you haven’t yet and follow the instructions about installing and testing RealmTasks. This will get you set up with the Realm Platform, a running server, and get you started using the RealmTasks app.
In this tutorial we will guide you through writing an iOS app using Realm Swift, which is a simpler version of RealmTasks, which can connect to Realm Object Server and synchronize data with the full RealmTasks app.
1. Create a new Xcode project
- Launch Xcode 9.
- Click “Create a new Xcode project”.
- Select “iOS”, then “Application”, then “Single View Application”, then click “Next”.
- Enter “RealmTasksTutorial” in the “Product Name” field.
- Select “Swift” from the “Language” dropdown menu.
- Select “iPhone” from the “Devices” dropdown menu.
- Select your team name (log in via Xcode’s preferences, if necessary) and enter an organization name.
- Click “Next”, then select a location on your Mac to create this project, then click “Create”.
2. Remove parts of the Xcode project to start with a simple base
- Delete the
Main.storyboard
file from the Xcode project navigator, clicking “Move To Trash” when prompted. - On the “General” tab of the
RealmTasksTutorial
target editor, clear the “Main Interface” text field in the “Deployment Info” section. - Also on the “General” tab, select a team name (you may need to log in to your Apple developer account via Xcode’s Preferences/Account pane)
- Switch to the “Capabilities” tab and turn on the “Keychain Sharing” switch.
Now you need to do some cleanup in the automatically generated source code. Start by deleting the contents of the ViewController.swift
file, replacing it with this:
import UIKit
class ViewController: UITableViewController {
}
Next replace the contents of AppDelegate.swift
with the following:
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions:[UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = UINavigationController(rootViewController: ViewController(style: .plain))
window?.makeKeyAndVisible()
return true
}
}
Your app should now build and run—although so far, it will just show a screen with an empty table view.
3. Import Realm Swift and create models
Download the latest release of Realm Swift from GitHub: https://github.com/realm/realm-cocoa/releases. You need the file named like so: realm-swift-3.*.*.zip
(where * will be the latest minor and update versions of Realm).
Unzip the downloaded file, navigate to the ios/swift-4.0 sub-folder, and drag Realm.framework
and RealmSwift.framework
into the “Embedded Binaries” section of your RealmTasksTutorial
target’s “General” tab.
A confirmation dialog will appear, check “Copy Items if Needed” and then click “Finish”.
Next, add the following at the top of ViewController.swift
(adding the new import
statement and code right under the existing import UIKit
):
import RealmSwift
// MARK: Model
final class TaskList: Object {
@objc dynamic var text = ""
@objc dynamic var id = ""
let items = List<Task>()
override static func primaryKey() -> String? {
return "id"
}
}
final class Task: Object {
@objc dynamic var text = ""
@objc dynamic var completed = false
}
At this point, we’ve imported Realm into the app and defined the data models (Task
and TaskList
) that we’ll use to represent our data and sync.
Your app should still build and run.
4. To register a cell class for use with our table view, add the following to the body of your ViewController
class:
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
func setupUI() {
title = "My Tasks"
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
5. Use a Realm List to display Tasks in the table view
Add the following property to your ViewController
class, on a new line, right after the class declaration:
var items = List<Task>()
Add the following line to the end of your viewDidLoad()
function to seed the list with some initial data:
override func viewDidLoad() {
// ... existing function ...
items.append(Task(value: ["text": "My First Task"]))
}
Append the following to the end of your ViewController
class’s body:
// MARK: UITableView
override func tableView(_ tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
return items.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let item = items[indexPath.row]
cell.textLabel?.text = item.text
cell.textLabel?.alpha = item.completed ? 0.5 : 1
return cell
}
Build and run the app, you’ll see your one task being displayed in the table.
There’s also some code in here to show completed items as a little lighter than uncompleted items, but we won’t see that in action until later.
6. Add support for creating new tasks
Delete the line in your viewDidLoad()
function that seeded initial data:
override func viewDidLoad() {
// -- DELETE THE FOLLOWING LINE --
items.append(Task(value: ["text": "My First Task"]))
}
Add the following function to your ViewController
class at its end:
// MARK: Functions
@objc func add() {
let alertController = UIAlertController(title: "New Task", message: "Enter Task Name", preferredStyle: .alert)
var alertTextField: UITextField!
alertController.addTextField { textField in
alertTextField = textField
textField.placeholder = "Task Name"
}
alertController.addAction(UIAlertAction(title: "Add", style: .default) { _ in
guard let text = alertTextField.text , !text.isEmpty else { return }
self.items.append(Task(value: ["text": text]))
self.tableView.reloadData()
})
present(alertController, animated: true, completion: nil)
}
Now add the following line at the end of the setupUI()
function:
func setupUI() {
// ... existing function ...
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(add))
}
7. Back items by a Realm and integrate sync
Now, add the following properties to your ViewController
class, just under the items
property:
var notificationToken: NotificationToken?
var realm: Realm!
The notification token we just added will be needed when we start observing changes from the Realm.
Right after the end of the setupUI()
function, add the following:
func setupRealm() {
// Log in existing user with username and password
let username = "test" // <--- Update this
let password = "test" // <--- Update this
}
deinit {
notificationToken?.invalidate()
}
In the code above, enter the same values for the username
and password
variables as you used when registering a user through the RealmTasks app in the “Getting Started” steps. (Link at the top of this article.)
Then insert the following at the end of the setupRealm()
function (inside the function body):
func setupRealm() {
// ... existing function ...
SyncUser.logIn(with: .usernamePassword(username: username, password: password, register: false), server: URL(string: "http://127.0.0.1:9080")!) { user, error in
guard let user = user else {
fatalError(String(describing: error))
}
DispatchQueue.main.async {
// Open Realm
let configuration = Realm.Configuration(
syncConfiguration: SyncConfiguration(user: user, realmURL: URL(string: "realm://127.0.0.1:9080/~/realmtasks")!)
)
self.realm = try! Realm(configuration: configuration)
// Show initial tasks
func updateList() {
if self.items.realm == nil, let list = self.realm.objects(TaskList.self).first {
self.items = list.items
}
self.tableView.reloadData()
}
updateList()
// Notify us when Realm changes
self.notificationToken = self.realm.observe { _,_ in
updateList()
}
}
}
}
And, call this setup function at the end of the viewDidLoad()
function:
override func viewDidLoad() {
// ... existing function ...
setupRealm()
}
Now edit the add()
function to look like this:
@objc func add() {
let alertController = UIAlertController(title: "New Task", message: "Enter Task Name", preferredStyle: .alert)
var alertTextField: UITextField!
alertController.addTextField { textField in
alertTextField = textField
textField.placeholder = "Task Name"
}
alertController.addAction(UIAlertAction(title: "Add", style: .default) { _ in
guard let text = alertTextField.text , !text.isEmpty else { return }
let items = self.items
try! items.realm?.write {
items.insert(Task(value: ["text": text]), at: items.filter("completed = false").count)
}
})
present(alertController, animated: true, completion: nil)
}
This deletes two lines in the guard
block that begin with self.
and replaces them with a let
and try!
block which will actually write a new task to the Realm.
Lastly, we need to allow non-TLS network requests to talk to our local sync server.
Right-click on the Info.plist
file and select “Open as… Source Code” and paste the following inside the <dict>
section:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
Build and run the app now - it should connect to the object server and display the tasks that were added in RealmTasks earlier.
If you add new tasks by tapping the “Add” button in your app, you should immediately see them reflected in the RealmTasks app too.
Congratulations, you’ve built your first synced Realm app!
Keep going if you’d like to see how easy it is to add more functionality and finish building your task management app.
8. Support moving and deleting tasks
Add the following line to the end of your setupUI()
function:
func setupUI() {
// ... existing function ...
navigationItem.leftBarButtonItem = editButtonItem
}
Now, add these functions to the ViewController
class body, right after the other tableView
functions:
override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
try! items.realm?.write {
items.move(from: sourceIndexPath.row, to: destinationIndexPath.row)
}
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
try! realm.write {
let item = items[indexPath.row]
realm.delete(item)
}
}
}
9. Support toggling the ‘completed’ state of a task by tapping it
After the last tableView
function in the ViewController
class, add the following function override:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let item = items[indexPath.row]
try! item.realm?.write {
item.completed = !item.completed
let destinationIndexPath: IndexPath
if item.completed {
// move cell to bottom
destinationIndexPath = IndexPath(row: items.count - 1, section: 0)
} else {
// move cell just above the first completed item
let completedCount = items.filter("completed = true").count
destinationIndexPath = IndexPath(row: items.count - completedCount - 1, section: 0)
}
items.move(from: indexPath.row, to: destinationIndexPath.row)
}
}
10. You’re done!
Now you’ve completed writing a minimal task management app that syncs its data in real time!
If you want to learn more about Realm, try out our Scanner App Demo