Tutorial: Build iOS App from Scratch

 

Building Your First Realm Mobile Platform iOS App

This tutorial will guide you through writing an iOS app using Realm Swift to sync with the RealmTasks demo apps.

First, install the MacOS bundle if you haven’t yet. This will get you set up with the Realm Mobile Platform and let you launch the macOS version of RealmTasks.

1. Create a new Xcode project

  1. Launch Xcode 8.
  2. Click “Create a new Xcode project”.
  3. Select “iOS”, then “Application”, then “Single View Application”, then click “Next”.
  4. Enter “RealmTasksTutorial” in the “Product Name” field.
  5. Select “Swift” from the “Language” dropdown menu.
  6. Select “iPhone” from the “Devices” dropdown menu.
  7. Select your team name (log in via Xcode’s preferences, if necessary) and enter an organization name.
  8. 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

  1. Delete the Main.storyboard file from the Xcode project navigator, clicking “Move To Trash” when prompted.
  2. On the “General” tab of the RealmTasksTutorial target editor, clear the “Main Interface” text field in the “Deployment Info” section.
  3. Also on the “General” tab, enter an organization name and select a team name (you may need to log in to your Apple developer account via Xcode’s Preferences/Account pane)
  4. Switch to the “Capabilities” tab and turn on the “Keychain Sharing” switch.

Delete the contents of the ViewController.swift file, replacing it with this:

import UIKit

class ViewController: UITableViewController {
}

Now 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
    }
}

Optionally, commit your progress in source control.

Your app should now build and run—although so far, it will just show a blank screen.

3. Import Realm Swift and create models

Download the latest release of the Realm Mobile Platform for macOS. Unzip the archive, then open the SDKs/realm-cocoa_*.*.* directory. Open ios, then the swift-3.1 (if using Xcode 8.3.*), swift-3.0.1 (if using Xcode 8.1 or Xcode 8.2) or swift-3.0 (if using Xcode 8.0.*) directory.

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 click “Finish”.

Then, 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 {
    dynamic var text = ""
    dynamic var id = ""
    let items = List<Task>()

    override static func primaryKey() -> String? {
        return "id"
    }
}

final class Task: Object {
    dynamic var text = ""
    dynamic var completed = false
}

At this point, we’ve imported RealmSwift into the app and defined the data models (Task and TaskList) that we’ll use to represent our data and sync with the RealmTasks apps.

Your app should still build and run.

4. Add a title and 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
}

If you then 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

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.stop()
}

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.

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.addNotificationBlock { _ 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:

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 in the <dict> section:

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>

If you 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