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

(株)ライトコードの小林(こばやし)です!
前回の記事では、iOS 14から追加された WidgetKit を使い、Widget 実装を実践してみました。
今回は Realm を使って、アプリ内で保存したデータを、Widget に表示する方法をまとめていきます!
開発環境
開発環境は以下のとおりです。
MacOS | 10.15.4 |
Swift | 5.3 |
Xcode | 12.0.1 |
Realm | 5.5.0 |
では早速、始めていきましょう!
前回の記事
Widgetの元となるアプリを実装する
それでは前回と同様、Widget の元となるアプリを、まずは実装していきます。
ちなみに今回作るアプリは、テキストフィールドに入力した値を、Realm に保存するのみの機能です。
構成は MVVM で、Combine を使っていきます。
実装準備
まずは、プロジェクトを作成します。
次に、Realm を導入します。
今回は、SPM(Swift Package Manager)を使って、Realm をインストールしてみましょう。
メニューバーから、「File > Swift Packages > Add Package Dependency… 」を開いて、Realm のリポジトリ URL を入力します。
「Up to Next Major」で、バージョンを5.0.0に指定し、次に進みます。
最後に、Realm と RealmSwift にチェックをつけて、インストール開始です。
これで、実装前の準備は完了です。
アプリ実装
今回は、Realm を使ってデータを保存するので、まずは Realm に保存する「データ項目」と「処理を定義したクラス」を作ります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | import Foundation import RealmSwift class Memo: Object { @objc dynamic var id: Int = 0 @objc dynamic var text: String = "" override static func primaryKey() -> String? { "id" } } extension Memo { private static var realm: Realm = try! Realm() static func all() -> Results<Memo> { realm.objects(self) } static func create(with memo: Memo) { try! realm.write { realm.create(Memo.self, value: memo, update: .all) } } } |
次に、ViewModel を実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import Combine class ContentViewModel :ObservableObject { @Published var text = Memo.all().first?.text ?? "" var saveButtonTapped = PassthroughSubject<Void, Never>() private var cancellables = [AnyCancellable]() init() { saveButtonTapped .sink( receiveValue: { [weak self] in guard let self = self else { return } let memo = Memo() memo.text = self.text Memo.create(with: memo) } ) .store(in: &cancellables) } } |
最後に、画面を実装していきましょう!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | import SwiftUI struct ContentView: View { @ObservedObject var viewModel: ContentViewModel var body: some View { VStack { TextEditor(text: $viewModel.text) .frame(width: UIScreen.main.bounds.width * 0.8, height: 200) .border(Color(.systemBlue), width: 1) Button( action: { viewModel.saveButtonTapped.send() }, label: { Text("Save") } ) } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView(viewModel: ContentViewModel()) } } |
「Build & Run」すると、こんな感じになりました!
皆さんも、同じような感じになりましたか?

Widgetを実装する
それでは、作ったアプリの Widget も実装してきます。
Widgetを追加する
メニューバーから「File > New > Target…」を開いて、「widget」を検索します。
ProductName などを入力して、Finish を押します。
次に、プロジェクト配下に新しく、Widget 用ディレクトリを追加していきましょう。
続いて下記画像のように、Memo.swift と ContentViewModel の Target Membership に追加します。
また、WidgetExtension でも RealmSwift を使用したいので、WidgetExtension の Build Phases で Library を連携します。
Widgetを実装する
では、いよいよ Widget を実装します。
とりあえずは、「空のデータを表示する Widget 」とします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | import WidgetKit import SwiftUI struct Provider: TimelineProvider { func placeholder(in context: Context) -> SimpleEntry { let memo = Memo() return SimpleEntry(date: Date(), memo: memo) } func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) { let memo = Memo() let entry = SimpleEntry(date: Date(), memo: memo) completion(entry) } func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) { var entries: [SimpleEntry] = [] let currentDate = Date() let memo = Memo() let entry = SimpleEntry(date: currentDate, memo: memo) entries.append(entry) let timeline = Timeline(entries: entries, policy: .atEnd) completion(timeline) } } struct SimpleEntry: TimelineEntry { let date: Date let memo: Memo } struct WidgetWithRealmWidgetEntryView : View { var entry: Provider.Entry var body: some View { Text(entry.memo.text) } } @main struct WidgetWithRealmWidget: Widget { let kind: String = "WidgetWithRealmWidget" var body: some WidgetConfiguration { StaticConfiguration(kind: kind, provider: Provider()) { entry in WidgetWithRealmWidgetEntryView(entry: entry) } .configurationDisplayName("メモ") .description("メモを確認できます。") } } struct WidgetWithRealmWidget_Previews: PreviewProvider { static var previews: some View { WidgetWithRealmWidgetEntryView(entry: SimpleEntry(date: Date(), memo: Memo())) .previewContext(WidgetPreviewContext(family: .systemSmall)) } } |
Widgetに保存したデータを表示する
アプリの実装と、Widget 追加が完了したので、Widget にアプリ内で保存したデータを表示するようにします。
App Groupsを設定する
現在の状態では、Widget でアプリ内に保存したデータを、取得することができません。
Widget にデータ共有するには、App Groups という機能が必要になります。
したがってまずは、App Groups の設定を行っていきます。
アプリの Target を選択し、Signing & Capabilities タブで、App Groups を追加します。
次に、下記のように名前をつけて、Group を追加します。

Widget の Extension でも、同様に App Groups を追加して、先ほど作成した Group を選択します。
これで、データを共有する準備が完了しました!
Widget内でアプリのデータを取得する
まず、Realm に保存する「データ項目」と、「処理を定義したクラス」を修正していきます。
先ほど設定した App GroupsのIdentifier を、 Realm.Configuration() に設定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 | extension Memo { // 修正箇所 // private static var realm: Realm = try! Realm() private static var realm: Realm { var config = Realm.Configuration() let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.naipaka.WidgetWithRealm")! config.fileURL = url.appendingPathComponent("db.realm") let realm = try! Realm(configuration: config) return realm } (省略) } |
次に、Widget 内で Realm に保存したデータを、取得する処理を実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | struct Provider: TimelineProvider { func placeholder(in context: Context) -> SimpleEntry { // 修正箇所 // let memo = Memo() let memo = Memo.all().first ?? Memo() return SimpleEntry(date: Date(), memo: memo) } func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) { // 修正箇所 // let memo = Memo() let memo = Memo.all().first ?? Memo() let entry = SimpleEntry(date: Date(), memo: memo) completion(entry) } func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) { var entries: [SimpleEntry] = [] let currentDate = Date() // 修正箇所 // let memo = Memo() let memo = Memo.all().first ?? Memo() let entry = SimpleEntry(date: currentDate, memo: memo) entries.append(entry) let timeline = Timeline(entries: entries, policy: .atEnd) completion(timeline) } } |
続いて、アプリ内でデータを更新したときに Widget も更新させたいので、ContentViewModel.swift に Widget 更新処理を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | import Combine // 追加 import WidgetKit class ContentViewModel :ObservableObject { @Published var text = Memo.all().first?.text ?? "" var saveButtonTapped = PassthroughSubject<Void, Never>() private var cancellables = [AnyCancellable]() init() { saveButtonTapped .sink( receiveValue: { [weak self] in guard let self = self else { return } let memo = Memo() memo.text = self.text Memo.create(with: memo) // 追加 WidgetCenter.shared.reloadAllTimelines() } ) .store(in: &cancellables) } } |
最後に、「Bulid & Run」して、Widget を追加すると…

無事、Realm に保存したデータが、Widget に表示されました!
さいごに
今回は、Realm に保存したデータを、Widget に表示する方法をまとめていきました。
前回は静的なデータを表示するのみでしたが、アプリ内のデータが共有できるようになると、表現できる幅も広がります。
また、 Timeline の機能を使うと、「時間になったら DB の値を更新する」といったこともできます。
ぜひ試してみてくださいね!
こちらの記事もオススメ!
書いた人はこんな人

- 「好きを仕事にするエンジニア集団」の(株)ライトコードです!
ライトコードは、福岡、東京、大阪の3拠点で事業展開するIT企業です。
現在は、国内を代表する大手IT企業を取引先にもち、ITシステムの受託事業が中心。
いずれも直取引で、月間PV数1億を超えるWebサービスのシステム開発・運営、インフラの構築・運用に携わっています。
システム開発依頼・お見積もり大歓迎!
また、現在「WEBエンジニア」「モバイルエンジニア」「営業」「WEBデザイナー」「WEBディレクター」を積極採用中です!
インターンや新卒採用も行っております。
以下よりご応募をお待ちしております!
https://rightcode.co.jp/recruit
ライトコードの日常12月 1, 2023ライトコードクエスト〜東京オフィス歴史編〜
ITエンタメ10月 13, 2023Netflixの成功はレコメンドエンジン?
ライトコードの日常8月 30, 2023退職者の最終出社日に密着してみた!
ITエンタメ8月 3, 2023世界初の量産型ポータブルコンピュータを開発したのに倒産!?アダム・オズボーン