チュートリアル: iOSアプリをイチから作ってみる

 

初めてのRealm Mobile Platformアプリ開発

このチュートリアルは、Realm Swiftを用いて、RealmTasksと同等のアプリをイチから作ります。

macOS版のRealm Mobile PlatformとRealmTasksで入力したデータが必要ですので、まだインストールしていなければ「macOS版Realm Mobile Platformのインストール」をご覧になり、macOS版をセットアップし、macOS版のRealmTasksアプリを一度起動してください。

1. 新規Xcodeプロジェクトを作成する

  1. Xcode 8を起動します。
  2. “Create a new Xcode project”をクリックします。
  3. “iOS”を選択し、”Application”内の”Single View Application”を選択して”Next”をクリックします。
  4. “Product Name”フィールドに”RealmTasksTutorial”と入力します。
  5. “Language”ドロップダウンメニューから”Swift”を選択します。
  6. “Devices”ドロップダウンメニューから”iPhone”を選択します。
  7. “Team”(必要に応じてXcodeのPreferencesからログインしてください)を選択し、任意の”Organization Name”を入力します。
  8. “Next”をクリックし、プロジェクトの保存場所を選択して、”Create”をクリックします。

2. 自動的に作成されるファイルを削除する

  1. Main.storyboardファイルをXcodeのプロジェクトナビゲータから削除します。ダイアログでは”Move To Trash”をクリックします。
  2. プロジェクトファイルを選択し、RealmTasksTutorialターゲットの”General”タブに移動します。”Deployment Info”セクションの”Main Interface”に入力されているテキストを空にします。
  3. “General”タブの”Team Name”から自分のアカウントのチームを選択します。(必要に応じてXcodeのPreferencesのAccountペインから自分のデベロッパーアカウントを使ってログインしてください。)
  4. “Capabilities”タブに切り替えて”Keychain Sharing”をONにしてください。

ViewController.swiftの内容をすべて削除し、代わりに下記のコードに置き換えてください。

import UIKit

class ViewController: UITableViewController {
}

同様にAppDelegate.swiftの内容も下記のコードに置き換えます。

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

必要に応じてここまでの進捗をGitにコミットしてください。

ここまでの手順でプロジェクトをビルドして起動することができます。まだUIも何もないので真っ黒な画面が表示されます。特にエラーがなく起動できればOKです。

3. Realm Swiftのインポート、モデルを作成する

ダウンロードしたmacOS版のRealm Mobile Platformを展開すると、SDKs/realm-cocoa_*.*.*ディレクトリがあります。(..*にはバージョンの数字が入ります。)その中のiosディレクトリを開き、(Xcode 8.3.*を使っていれば)swift-3.1ディレクトリを、(Xcode 8.1または8.2を使っていれば)swift-3.0.1またはswift-3.0ディレクトリのいずれかを開きます。

RealmTasksTutorialターゲットの”General”タブにある”Embedded Binaries”セクションに、Realm.frameworkRealmSwift.frameworkをドラッグ&ドロップします。

確認ダイアログが表示されますので、”Copy Items if Needed”にチェックをし、”Finish”をクリックします。

ViewController.swiftの先頭に下記のコードを記述します。(import RealmSwift 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
}

ここまでで、RealmSwiftをアプリにインポートし、データモデル(TaskTaskList)を定義しました。データをRealmTasksアプリと同期する準備が整ったことになります。

確認のために一度ビルドして実行してみましょう。何も表示されませんが、エラーが起こらなければOKです。

4. ビューのタイトルを設定し、テーブルビューに使うセルクラスを登録する

ViewControllerクラスの中に、下記のコードを記述します。

override func viewDidLoad() {
    super.viewDidLoad()
    setupUI()
}

func setupUI() {
    title = "My Tasks"
    tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}

5. RealmのListクラスを使ってテーブルビューにタスクを表示する

ViewControllerに下記のプロパティを追加します。クラスの宣言のすぐ下に書いてください。

var items = List<Task>()

viewDidLoad()メソッドの最後に初期データとしてダミーデータを作成するコードを記述します。

override func viewDidLoad() {
    // ... existing function ...
    items.append(Task(value: ["text": "My First Task"]))
}

ViewControllerクラスの最後に下記のコードを記述します。

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

ここまででビルドして実行してみましょう。タスクが1つ、テーブルビューに表示されるはずです。先ほど書いたコードには、完了済み(’completed’)のアイテムは少し明るく表示する処理が含まれていますが、まだアイテムを完了済みにはできないので今は確認できません。後ほど、コードを追加して確認します。

6. 新しいタスクを追加できるようにする

viewDidLoad()に先ほど書いたダミーデータを作成するコードを削除します。

override func viewDidLoad() {
    // -- 次のコードを削除してください。 --
    items.append(Task(value: ["text": "My First Task"]))
}

ViewControllerの最後に下記のメソッドを追加します。

// 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)
}

setupUI()メソッドの最後い下記の1行を追加します。

func setupUI() {
    // ... existing function ...
    navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(add))
}

7. Realmによって同期されたデータを使用する

ViewControllerクラスのitemsプロパティのすぐ下に、下記のプロパティを追加します。

var notificationToken: NotificationToken!
var realm: Realm!

ここで追加したnotificationTokenは同期によるRealmの変更を監視するために必要です。


setupUI()メソッドの最後に下記のコードを追加します。

func setupRealm() {
    // Log in existing user with username and password
    let username = "test"  // <--- 設定したユーザー名に変更してください
    let password = "test"  // <--- 設定したパスワードに変更してください
}

deinit {
    notificationToken.stop()
}

上記のコードについて、usernamepasswordは”はじめに”のステップでRealmTasksアプリで登録した値に変更してください。

setupRealm()メソッドの最後に下記のコードを記述します。

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

viewDidLoad()の最後で、このsetupRealm()メソッドを呼び出します。

override func viewDidLoad() {
    // ... existing function ...
    setupRealm()
}

add()メソッドを次のように変更します。

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

変更内容は、guardブロックの下にあるself.で始まる2行をletで始まる1行ととtry!ブロックに変えたことです。この変更により、新しいタスクオブジェクトがRealmに保存されるようになりました。

最後に、ローカルの同期サーバーと通信できるようにするために非TLSの通信を許可する設定をします。

Info.plistファイルを右クリックし、”Open as… Source Code”を選択します。<dict>セクションの中に下記のコードを追加します。

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

アプリをビルドして実行してみましょう。オブジェクトサーバーに接続し、以前にRealmTasksアプリに追加したタスクが同期されて表示されるはずです。

アプリの”Add”ボタンを押してタスクを追加できます。追加したタスクは即座に同期されて、RealmTasksアプリにも反映されます。

*おめでとうございます!初めてのRealmを使ったアプリが完成しました!

この後、さらにいくつかの機能を追加して、タスク管理アプリをブラッシュアップしていきます。Realmを使うと本当に簡単です。

8. タスクの移動と削除を実装する

setupUI()メソッドに下記のコードを追加します。 swift func setupUI() { // ... existing function ... navigationItem.leftBarButtonItem = editButtonItem }

ViewControllerクラスに書かれているtableView関連のメソッドのすぐ下に下記のコードを追加します。

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. タスクをタップすると完了済み(’completed’)にマークする

ViewControllerクラスのtableView関連メソッドの下に下記のコードを追加します。

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. 完成!

これで、簡単ですが複数デバイスでデータがリアルタイム同期するタスク管理アプリの完成です。

もっとRealmについて学びたい場合はScannerデモアプリのチュートリアルもご覧ください。