【Swift UI】WidgetにRealmのデータを表示させる方法!
この記事からわかること
- Swift UIでWidgetにRealmのデータを表示させる方法
- App Groupsで共有コンテナー内にRealmデータベースを構築するには?
- Widget追加後にCocoa Podsを使用してライブラリを追加する方法
- App Groups使用時の@ObservedResultsの注意点
- FileManager.default.containerURLメソッドの使い方
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
iOS14から実装されたWidget(ウィジェット)機能にRealmで保存しているデータを表示させる方法をまとめていきます。Realmの導入方法やWidgetの作成方法は下記記事を参考にしてください。
【SwiftUI】Realm Swiftとは?導入方法とCRUD処理のやり方
【Swift UI】Widget(ウィジェット)の実装方法!TimelineProviderとは?
WidgetにRealmのデータを表示させる方法
WidgetにRealmのデータを表示させるにはRealmのデータベースをApp Groupsを使った共有コンテナー内に構築する必要があります。
実装の流れ
- 新規プロジェクトを生成
- Widgetの追加
- Realmの導入
- App Groupsで新規コンテナーの作成
- コンテナーメンバーシップにアプリとWidgetを追加
- 共有コンテナーのURLを取得
- Realmの保存先URLに取得したURLを設定
今回はライブラリ管理ツールであるCocoa Podsを使用している場合の実装方法をまとめていきます。
まずは新規でプロジェクトを立ち上げ、さらにプロジェクト内に新しくWidgetを追加(「File」>「 New 」>「Target...」を選択後「Widget Extension」)しておきます。
これでWidget機能を持ったアプリプロジェクトファイルができました。
Widgetがある場合のCocoa Podsのインストール
次にCocoa Podsを使用してRealmを導入していきます。pod init
を実行するとWidgetを追加している場合はPodFileの中身がtargetごとに分かれます。
ライブラリはターゲットごとに導入する必要があるので以下のように2ヶ所に記述してpod install
を実行します。
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'
target 'TestWidgetExtension' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
# Pods for TestWidgetExtension
pod 'RealmSwift'
end
target 'WidgetTest' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
# Pods for WidgetTest
pod 'RealmSwift'
end
App Groupsと紐づける
立ち上げたアプリからApp Groupsを追加し新規コンテナーを作成しておきます。
- アプリ側の「Signing & Capabilities」タブを開く
- 「+」ボタンから「App Groups」を追加
- コンテナーID名を決める(例:group.com.ame.dev.WidgetTest)
- Widget側の「Signing & Capabilities」タブを開く
- 「+」ボタンから「App Groups」を追加
- 先ほど追加したコンテナーにチェックを入れて有効化
作成したらWidget側からも作成したコンテナーにアクセスできるようにチェックを打っておきます。
Realmの保存先を共有コンテナーURLに変更する
続いてアプリ内にRealmデータベースを操作するためのコードを記述していきます。
まずはデータベースに保存するテーブルクラスとRealmインスタンスを操作するクラスを作成しておきます。プロジェクト内では保存先を変更したインスタンスを使用したいのでクラス内に定義した共有となるRealmインスタンスを使用することで記述が冗長にならないようにします。
import UIKit
import RealmSwift
class User: Object,ObjectKeyIdentifiable{
@Persisted(primaryKey: true) var id = UUID()
@Persisted var name:String = ""
@Persisted var age:Int = 0
}
class RealmManager {
var realm:Realm {
var config = Realm.Configuration()
config.fileURL = fileUrl
return try! Realm(configuration: config)
}
var fileUrl: URL {
let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.ame.dev.WidgetTest")!
return url.appendingPathComponent("db.realm")
}
}
Realmをインスタンス化する際に設定を指定できるのでその際に保存先URLを作成したコンテナーのURLに変更します。
containerURL
メソッドを使って任意のコンテナーURLを取得し、appendingPathComponent
を使ってその中にRealm用のパスを構築しています。
続いて作成したUserRealmModels.swiftをWidget側からも参照できるようにインスペクタ(右側)のTarget MemberShipのWidgetにチェックを入れておきます。
アプリ内でデータの登録処理を記述
アプリ内からデータを登録できるようにビューを構築していきます。ここでのポイントは以下の2つです。
- 共有コンテナーの場合の@ObservedResults
- アプリ側からWidgetを更新する
import SwiftUI
import RealmSwift
import WidgetKit
struct ContentView: View {
let manager = RealmManager()
@ObservedResults(User.self,configuration: Realm.Configuration(fileURL:RealmManager().fileUrl)) var users
@State var text:String = ""
func entryName(_ name:String){
let obj = User()
obj.name = name
obj.age = 26
let realm = manager.realm
try! realm.write{
realm.add(obj,update:.modified)
}
text = ""
WidgetCenter.shared.reloadAllTimelines()
}
var body: some View {
VStack{
TextField("name", text: $text).padding()
Button(action: {
entryName(text)
}, label: {
Text("Entry")
})
List(users) { user in
Text(user.name)
}.listStyle(GroupedListStyle())
}
}
}
App Groups使用時の@ObservedResults
App Groupsを使用して共有コンテナーにRealmデータベースを保存する場合は@ObservedResults
を使用する際に注意が必要です。
// ×
@ObservedResults(User.self) var users
// ◯
@ObservedResults(User.self,configuration: Realm.Configuration(fileURL:RealmManager().fileUrl)) var users
保存しているファイルURLを変更しているのでその変更を明示的に指定する必要があります。ここではRealm.Configuration
を使って保存先URLを渡しています。
WidgetCenter.shared.reloadAllTimelines()
ボタンをクリックされたときにWidgetを更新するには以下のように記述します。WidgetKit
をimportするのを忘れないようにしてください。
WidgetCenter.shared.reloadAllTimelines()
Widget側からRealmを取得
最後にWidget側にRealmからデータを取得して反映させるように記述すれば完成です。ここで追記するのは以下のポイントです。
- TimelineEntryにnameプロパティを追加
- Widgetのビューの調整
- getTimelineメソッド内からRealmのデータを参照
import WidgetKit
import SwiftUI
import RealmSwift
struct Provider: TimelineProvider {
// 省略
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
let realm = RealmManager().realm
let obj = realm.objects(User.self).last!
var entry = SimpleEntry(date: Date())
entry.name = obj.name
entries.append(entry)
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
var name:String = "NoUser"
}
struct TestWidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
VStack{
Text("最後の登録者")
Text(entry.name)
}
}
}
// 省略
これで以下のような最後に登録されたユーザー名が表示されるWidgetが完成しました。
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。