【iOS14】Realmで保存したデータをWidgetに表示してみた
IT技術
Realmで保存したデータをWidgetに表示する方法をまとめてみた!
(株)ライトコードの小林(こばやし)です!
前回の記事では、iOS 14から追加された WidgetKit を使い、Widget 実装を実践してみました。
今回は Realm を使って、アプリ内で保存したデータを、Widget に表示する方法をまとめていきます!
開発環境
開発環境は以下のとおりです。
MacOS | 10.15.4 |
Swift | 5.3 |
Xcode | 12.0.1 |
Realm | 5.5.0 |
では早速、始めていきましょう!
前回の記事
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 を入力します。
「Up to Next Major」で、バージョンを5.0.0に指定し、次に進みます。
最後に、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」を検索します。
ProductName などを入力して、Finish を押します。
次に、プロジェクト配下に新しく、Widget 用ディレクトリを追加していきましょう。
続いて下記画像のように、Memo.swift と ContentViewModel の Target Membership に追加します。
また、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 を追加します。
次に、下記のように名前をつけて、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 に表示する方法をまとめていきました。
前回は静的なデータを表示するのみでしたが、アプリ内のデータが共有できるようになると、表現できる幅も広がります。
また、 Timeline の機能を使うと、「時間になったら DB の値を更新する」といったこともできます。
ぜひ試してみてくださいね!
こちらの記事もオススメ!
2020.07.17ライトコード的「やってみた!」シリーズ「やってみた!」を集めました!(株)ライトコードが今まで作ってきた「やってみた!」記事を集めてみました!※作成日が新し...
2020.08.14スマホ技術 特集Android開発Android開発をJavaからKotlinへ変えていくためのお勉強DelegatedPropert...
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ
「好きを仕事にするエンジニア集団」の(株)ライトコードです! ライトコードは、福岡、東京、大阪、名古屋の4拠点で事業展開するIT企業です。 現在は、国内を代表する大手IT企業を取引先にもち、ITシステムの受託事業が中心。 いずれも直取引で、月間PV数1億を超えるWebサービスのシステム開発・運営、インフラの構築・運用に携わっています。 システム開発依頼・お見積もり大歓迎! また、現在「WEBエンジニア」「モバイルエンジニア」「営業」「WEBデザイナー」を積極採用中です! インターンや新卒採用も行っております。 以下よりご応募をお待ちしております! https://rightcode.co.jp/recruit
おすすめ記事
immichを知ってほしい
2024.10.31