16
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

posted at

updated at

App Extension(Today Extension)でRealmを使う

本記事では、iOSアプリ本体とToday Extensionとの間でRealm Mobile Databaseのファイルを共有する方法を紹介します。
その他のApp Extensionでも同様の方法が使えます。

環境

  • Xcode 8.2
  • Swift 3
  • Realm Swift 2.4.4
  • iOS 10.2

Today Extensionとは

Today Extensionとは、iOS 8から導入されたウィジェットを実装するためのApp Extensionの一つです。
App Extensionには他にもAction ExtensionやShare Extensionなど色々とありますが、これらを実装する場合でも本記事で紹介する方法が使えます。

App Extensionでは、アプリ本体で使用されているデータやソースコードをそのまま共有して使うことができません。
別のアプリを新たに作成しているような扱いとなります。

そこで、アプリ本体とApp Extensionの間でデータを共有するためにはApp Groupsという仕組みを利用します。
ソースコードを共有するためにはEmbedded Frameworkという仕組みを利用します(本記事ではこちらについての説明は省略します)。

本記事で作成するアプリ

本記事では、アプリ本体の画面に入力した文字列をそのままウィジェット上に表示するようなシンプルなアプリを作成していきます。

app_overview.png

手順

Step 1. プロジェクトを作成する

Xcodeで普通にプロジェクトを作成します。
Create a new Xcode project → iOS → Single View Application

Product NameはここではMyAppとします。

step1.png

Step 2. Today Extensionを追加する

Xcodeメニューの File → New → Target を選択すると、Targetを追加するダイアログが現れます。
Application Extensionの中のToday Extensionを選択します。

step2.png

ここではToday ExtensionのProduct NameをMyExtensionとします。

step2.1.png

Activate “MyExtension” Scheme?と聞かれるのでActivateしておきます。

Step 3. App Groupを作成する

この時点で、アプリにはMyAppとMyExtensionという2つのTargetsが作成されていることになります。
これらを同一のApp Groupに含めることで、データを共有することができます。

まず、Targetsの中からMyAppを選択し、Capabilitiesのタブを開きます。
App GroupsのスイッチがOFFになっているので、これをONにします。

step3.png

次に、+ボタンをクリックしてApp GroupのIDを入力します。
ここにはgroup.に続いてアプリのBundle Identifierを入力します。

これでMyAppがApp Groupに追加されました。
MyExtensionもMyAppと同一のApp Groupに追加しましょう。

Targetsの中からMyExtensionを選択し、先ほどと同様にApp GroupsのスイッチをONにします。
次に、先ほど追加したApp GroupのIDにチェックを入れるとMy ExtensionがApp Groupに追加されます。

Step 4. Realmを導入する

Realmの詳しい導入方法は公式ドキュメントを参照してください。
ここではCocoaPodsを使用してプロジェクトにRealmを導入する方法を紹介します。

まず、Xcodeを一旦閉じてCocoaPodsをインストールします。インストール方法の説明は省略します。
CocoaPodsをインストール後、プロジェクトディレクトリ直下でpod initを実行すると、同じ場所にPodfileが生成されます。
これを以下のように編集します。

Podfile
target 'MyApp' do
  use_frameworks!
  pod 'RealmSwift'
end

target 'MyExtension' do
  use_frameworks!
  pod 'RealmSwift'
end

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['SWIFT_VERSION'] = ‘3.0’
    end
  end
end

Podfileを保存してpod installを実行すると、MyAppとMyExtensionの両方にRealmが導入されます。

これ以降はMyApp.xcodeprojではなくMyApp.xcworkspaceを開いて作業を行いましょう。

Step 5. 画面レイアウトを作成する

MyApp.xcworkspaceを開いて、MyAppのMain.storyboardを編集します。
ここでは、文字列をRealm DBに保存するためにText FieldとButtonを適当な場所に配置しておきます。

step5.1.png

次に、ウィジェットの画面レイアウトを作成します。
MyExtensionの中に生成されているMainInterface.storyboardを編集します。
既にHello WorldというLabelが入っているはずなので、これをそのまま使っていきます。無かったら追加してください。
文字の色が白くて見えにくいので、黒に変更しておきます。

step5.2.png

Step 6. コードを書く

このアプリで実現したいことは以下の3つです。

  1. RealmのDBファイルを作成する
  2. MyAppのText Fieldに入力された文字列をRealm DBに保存する
  3. Realm DBに保存した文字列をMyExtensionのLabelに表示する

Step 6.1. RealmのDBファイルを作成する

まず、文字列を保存するためのモデルクラスを作成します。

MyAppディレクトリの中にMyModel.swiftというファイルを生成します。
New File → Swift File → Create

MyModel.swiftを以下のように編集します。

MyModel.swift
import RealmSwift

class MyModel: Object {
    dynamic var id = 0
    dynamic var text = ""

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

次に、これとまったく同じ内容のクラスファイルをMyExtensionディレクトリの中にも作成します。
同じ内容のソースコードを別々の場所に置くのはバグの原因になるので望ましくはありませんが、これでも一応動作します。
ソースコードをTarget間で共有したい場合はEmbedded Frameworkを使用してください。

Embedded Frameworkについては以下のページに説明があります。
App Extension Programming Guide: Handling Common Scenarios

Step 6.2. MyAppのText Fieldに入力された文字列をRealm DBに保存する

MyAppのMain.storyboardとViewController.swiftを開き、controlキーを押しながらMain.storyboard上のText FieldからViewController.swiftに向かってドラッグします。

step6.2.1.png

Outletを追加するダイアログが現れるので、textFieldという名前で追加します。

次に、controlキーを押しながらMain.storyboard上のButtonからViewController.swiftに向かってドラッグします。

step6.2.2.png

今回はOutletではなくActionを追加します。名前はbuttonClickedとしておきます。

step6.2.3.png

次に、「保存ボタンをタップしたときにテキストフィールド内の文字列をRealm DBに書き込む」という処理ができるようにViewController.swiftを書き換えていきます。

最終的なコードは以下のようになります。

ViewController.swift
import UIKit
import RealmSwift

class ViewController: UIViewController {

    @IBOutlet weak var textField: UITextField!

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

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    @IBAction func buttonClicked(_ sender: Any) {
        var config = Realm.Configuration()
        let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.example.MyApp")!
        config.fileURL = url.appendingPathComponent("db.realm")
        let realm = try! Realm(configuration: config)

        try! realm.write {
            realm.create(MyModel.self, value: ["id": 0, "text": textField.text!], update: true)
        }
    }    
}

注意すべきはFileManager.default.containerURL()の引数です。
ここにはStep 3.で設定したApp GroupのIDを入力してください。

他の場所からもRealmを参照したい場合は、Realm ConfigurationのfileURLを必ず上記のように設定してからRealmを参照してください。

Realm ConfigurationをdefaultConfigurationとして設定しておくこともできます。詳しくは公式ドキュメントを参照してください。

Step 6.3. Realm DBに保存した文字列をMyExtensionのLabelに表示する

MyExtensionのMainInterface.storyboardとTodayViewController.swiftを開き、controlキーを押しながらMainInterface.storyboard上のLabelからTodayViewController.swiftに向かってドラッグします。

step6.3.1.png

Outletを追加するダイアログが現れるので、labelという名前で追加します。

次に、「Realm DBに保存された文字列をウィジェットの画面が更新されるタイミングで読み込んでラベルに表示する」という処理ができるようにTodayViewController.swiftを書き換えていきます。

最終的なコードは以下のようになります。

TodayViewController.swift
import UIKit
import NotificationCenter
import RealmSwift

class TodayViewController: UIViewController, NCWidgetProviding {

    @IBOutlet weak var label: UILabel!

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

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    func widgetPerformUpdate(completionHandler: (@escaping (NCUpdateResult) -> Void)) {
        var config = Realm.Configuration()
        let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.example.MyApp")!
        config.fileURL = url.appendingPathComponent("db.realm")
        let realm = try! Realm(configuration: config)
        let result = realm.object(ofType: MyModel.self, forPrimaryKey: 0)
        label.text = result?.text

        completionHandler(NCUpdateResult.newData)
    }
}

ここでもRealm ConfigurationのfileURLをMyAppのViewControllerと同様に設定しています。
これによって、同一のApp Groupに含まれているMyAppとMyExtensionの両方が同じRealmのDBファイルを参照することができます。

Step 7. 実行する

MyAppを実行し、テキストフィールドに適当な文字列を入力して保存ボタンを押します。
通知センターなどからMyExtensionのウィジェットを表示するように設定すると、保存した文字列がウィジェット上に表示されます。

参考

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
16
Help us understand the problem. What are the problem?