• トップ
  • ブログ一覧
  • 【iOS14】Realmで保存したデータをWidgetに表示してみた
  • 【iOS14】Realmで保存したデータをWidgetに表示してみた

    メディアチームメディアチーム
    2020.12.07

    IT技術

    【iOS14】Realmで保存したデータをWidgetに表示してみた

    Realmで保存したデータをWidgetに表示する方法をまとめてみた!

    小林先生

    (株)ライトコードの小林(こばやし)です!

    前回の記事では、iOS 14から追加された WidgetKit を使い、Widget 実装を実践してみました。

    今回は Realm を使って、アプリ内で保存したデータを、Widget に表示する方法をまとめていきます!

    開発環境

    開発環境は以下のとおりです。

    MacOS10.15.4
    Swift5.3
    Xcode12.0.1
    Realm5.5.0

    では早速、始めていきましょう!

    前回の記事

    【iOS14】WidgetKitを使ってみた!2020.11.24【iOS14】WidgetKitを使ってみた!WidgetKitの実装方法をまとめてみた小林先生(株)ライトコードの小林(こばやし)です!iOS14からホーム画面に...

    Widgetの元となるアプリを実装する

    それでは前回と同様、Widget の元となるアプリを、まずは実装していきます。

    ちなみに今回作るアプリは、テキストフィールドに入力した値を、Realm に保存するのみの機能です。

    構成は MVVM で、Combine を使っていきます。

    実装準備

    まずは、プロジェクトを作成します。

    プロジェクトを作成

    次に、Realm を導入します。

    今回は、SPM(Swift Package Manager)を使って、Realm をインストールしてみましょう。

    メニューバーから、「File > Swift Packages > Add Package Dependency… 」を開いて、Realm のリポジトリ URL を入力します。

    Realm をインストール

    「Up to Next Major」で、バージョンを5.0.0に指定し、次に進みます。

    「Up to Next Major」で、バージョンを5.0.0に指定

    最後に、Realm と RealmSwift にチェックをつけて、インストール開始です。

    Realm と RealmSwift にチェックをつけて、インストール開始

    これで、実装前の準備は完了です。

    アプリ実装

    今回は、Realm を使ってデータを保存するので、まずは Realm に保存する「データ項目」と「処理を定義したクラス」を作ります。

    1import Foundation
    2import RealmSwift
    3
    4class Memo: Object {
    5    @objc dynamic var id: Int = 0
    6    @objc dynamic var text: String = ""
    7
    8    override static func primaryKey() -> String? {
    9        "id"
    10    }
    11}
    12
    13extension Memo {
    14    private static var realm: Realm = try! Realm()
    15
    16    static func all() -> Results<Memo> {
    17        realm.objects(self)
    18    }
    19
    20    static func create(with memo: Memo) {
    21        try! realm.write {
    22            realm.create(Memo.self, value: memo, update: .all)
    23        }
    24    }
    25}

    次に、ViewModel を実装します。

    1import Combine
    2
    3class ContentViewModel :ObservableObject {
    4    @Published var text = Memo.all().first?.text ?? ""
    5    var saveButtonTapped = PassthroughSubject<Void, Never>()
    6
    7    private var cancellables = [AnyCancellable]()
    8
    9    init() {
    10        saveButtonTapped
    11            .sink(
    12                receiveValue: { [weak self] in
    13                    guard let self = self else { return }
    14                    let memo = Memo()
    15                    memo.text = self.text
    16                    Memo.create(with: memo)
    17                }
    18            )
    19            .store(in: &cancellables)
    20    }
    21}

    最後に、画面を実装していきましょう!

    1import SwiftUI
    2
    3struct ContentView: View {
    4    @ObservedObject var viewModel: ContentViewModel
    5
    6    var body: some View {
    7        VStack {
    8            TextEditor(text: $viewModel.text)
    9                .frame(width: UIScreen.main.bounds.width * 0.8, height: 200)
    10                .border(Color(.systemBlue), width: 1)
    11            Button(
    12                action: {
    13                    viewModel.saveButtonTapped.send()
    14                },
    15                label: {
    16                    Text("Save")
    17                }
    18            )
    19        }
    20    }
    21}
    22
    23struct ContentView_Previews: PreviewProvider {
    24    static var previews: some View {
    25        ContentView(viewModel: ContentViewModel())
    26    }
    27}

    「Build & Run」すると、こんな感じになりました!

    皆さんも、同じような感じになりましたか?

    アプリ実装

    Widgetを実装する

    それでは、作ったアプリの Widget も実装してきます。

    Widgetを追加する

    メニューバーから「File > New > Target…」を開いて、「widget」を検索します。

    メニューバーから「File > New > Target…」を開いて、「widget」を検索

    ProductName などを入力して、Finish を押します。

    次に、プロジェクト配下に新しく、Widget 用ディレクトリを追加していきましょう。

    プロジェクト配下に新しく、Widget 用ディレクトリを追加

    続いて下記画像のように、Memo.swift と ContentViewModel の Target Membership に追加します。

    Memo.swift と ContentViewModel の Target Membership に追加

    また、WidgetExtension でも RealmSwift を使用したいので、WidgetExtension の Build Phases で Library を連携します。

    WidgetExtension でも RealmSwift を使用したいので、WidgetExtension の Build Phases で Library を連携

    Widgetを実装する

    では、いよいよ Widget を実装します。

    とりあえずは、「空のデータを表示する Widget 」とします。

    1import WidgetKit
    2import SwiftUI
    3
    4struct Provider: TimelineProvider {
    5    func placeholder(in context: Context) -> SimpleEntry {
    6        let memo = Memo()
    7        return SimpleEntry(date: Date(), memo: memo)
    8    }
    9
    10    func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
    11        let memo = Memo()
    12        let entry = SimpleEntry(date: Date(), memo: memo)
    13        completion(entry)
    14    }
    15
    16    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
    17        var entries: [SimpleEntry] = []
    18        let currentDate = Date()
    19        let memo = Memo()
    20
    21        let entry = SimpleEntry(date: currentDate, memo: memo)
    22        entries.append(entry)
    23
    24        let timeline = Timeline(entries: entries, policy: .atEnd)
    25        completion(timeline)
    26    }
    27}
    28
    29struct SimpleEntry: TimelineEntry {
    30    let date: Date
    31    let memo: Memo
    32}
    33
    34struct WidgetWithRealmWidgetEntryView : View {
    35    var entry: Provider.Entry
    36
    37    var body: some View {
    38        Text(entry.memo.text)
    39    }
    40}
    41
    42@main
    43struct WidgetWithRealmWidget: Widget {
    44    let kind: String = "WidgetWithRealmWidget"
    45
    46    var body: some WidgetConfiguration {
    47        StaticConfiguration(kind: kind, provider: Provider()) { entry in
    48            WidgetWithRealmWidgetEntryView(entry: entry)
    49        }
    50        .configurationDisplayName("メモ")
    51        .description("メモを確認できます。")
    52    }
    53}
    54
    55struct WidgetWithRealmWidget_Previews: PreviewProvider {
    56    static var previews: some View {
    57        WidgetWithRealmWidgetEntryView(entry: SimpleEntry(date: Date(), memo: Memo()))
    58            .previewContext(WidgetPreviewContext(family: .systemSmall))
    59    }
    60}

    Widgetに保存したデータを表示する

    アプリの実装と、Widget 追加が完了したので、Widget にアプリ内で保存したデータを表示するようにします。

    App Groupsを設定する

    現在の状態では、Widget でアプリ内に保存したデータを、取得することができません。

    Widget にデータ共有するには、App Groups という機能が必要になります。

    したがってまずは、App Groups の設定を行っていきます。

    アプリの Target を選択し、Signing & Capabilities タブで、App Groups を追加します。

    アプリの Target を選択し、Signing & Capabilities タブで、App Groups を追加

    次に、下記のように名前をつけて、Group を追加します。

    名前をつけて、Group を追加

    Widget の Extension でも、同様に App Groups を追加して、先ほど作成した Group を選択します。

    これで、データを共有する準備が完了しました!

    Widget内でアプリのデータを取得する

    まず、Realm に保存する「データ項目」と、「処理を定義したクラス」を修正していきます。

    先ほど設定した App GroupsのIdentifier を、Realm.Configuration() に設定します。

    1extension Memo {
    2    // 修正箇所
    3    // private static var realm: Realm = try! Realm()
    4    private static var realm: Realm {
    5            var config = Realm.Configuration()
    6            let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.naipaka.WidgetWithRealm")!
    7            config.fileURL = url.appendingPathComponent("db.realm")
    8            let realm = try! Realm(configuration: config)
    9            return realm
    10        }
    11
    12    (省略)
    13}

    次に、Widget 内で Realm に保存したデータを、取得する処理を実装します。

    1struct Provider: TimelineProvider {
    2    func placeholder(in context: Context) -> SimpleEntry {
    3        // 修正箇所
    4        // let memo = Memo()
    5        let memo = Memo.all().first ?? Memo()
    6        return SimpleEntry(date: Date(), memo: memo)
    7    }
    8
    9    func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
    10        // 修正箇所
    11        // let memo = Memo()
    12        let memo = Memo.all().first ?? Memo()
    13        let entry = SimpleEntry(date: Date(), memo: memo)
    14        completion(entry)
    15    }
    16
    17    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
    18        var entries: [SimpleEntry] = []
    19        let currentDate = Date()
    20        // 修正箇所
    21        // let memo = Memo()
    22        let memo = Memo.all().first ?? Memo()
    23
    24        let entry = SimpleEntry(date: currentDate, memo: memo)
    25        entries.append(entry)
    26
    27        let timeline = Timeline(entries: entries, policy: .atEnd)
    28        completion(timeline)
    29    }
    30}

    続いて、アプリ内でデータを更新したときに Widget も更新させたいので、ContentViewModel.swift に Widget 更新処理を追加します。

    1import Combine
    2// 追加
    3import WidgetKit
    4
    5class ContentViewModel :ObservableObject {
    6    @Published var text = Memo.all().first?.text ?? ""
    7    var saveButtonTapped = PassthroughSubject<Void, Never>()
    8
    9    private var cancellables = [AnyCancellable]()
    10
    11    init() {
    12        saveButtonTapped
    13            .sink(
    14                receiveValue: { [weak self] in
    15                    guard let self = self else { return }
    16                    let memo = Memo()
    17                    memo.text = self.text
    18                    Memo.create(with: memo)
    19                    // 追加
    20                    WidgetCenter.shared.reloadAllTimelines()
    21                }
    22            )
    23            .store(in: &cancellables)
    24    }
    25}

    最後に、「Bulid & Run」して、Widget を追加すると…

    Realm に保存したデータが、Widget に表示

    無事、Realm に保存したデータが、Widget に表示されました!

    さいごに

    今回は、Realm に保存したデータを、Widget に表示する方法をまとめていきました。

    前回は静的なデータを表示するのみでしたが、アプリ内のデータが共有できるようになると、表現できる幅も広がります。

    また、 Timeline の機能を使うと、「時間になったら DB の値を更新する」といったこともできます。

    ぜひ試してみてくださいね!

    こちらの記事もオススメ!

    featureImg2020.07.17ライトコード的「やってみた!」シリーズ「やってみた!」を集めました!(株)ライトコードが今まで作ってきた「やってみた!」記事を集めてみました!※作成日が新し...
    featureImg2020.08.14スマホ技術 特集Android開発Android開発をJavaからKotlinへ変えていくためのお勉強DelegatedPropert...

    ライトコードでは、エンジニアを積極採用中!

    ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。

    採用情報へ

    メディアチーム
    メディアチーム
    Show more...

    おすすめ記事

    エンジニア大募集中!

    ライトコードでは、エンジニアを積極採用中です。

    特に、WEBエンジニアとモバイルエンジニアは是非ご応募お待ちしております!

    また、フリーランスエンジニア様も大募集中です。

    background