Swift - RealmのデータをWidgetと共有する方法
作るもの
今回はTextFieldに入力した文字列をRealmに保存して、Widgetに表示するアプリを作っていきます。
最低限の機能で実装します。
完成イメージ
実装準備
1.RealmSwiftを追加
XcodeのメニューバーにあるFile > Add Packages...からRealmSwiftを追加しましょう。
RealmとRealmSwiftどちらも選択します。
WidgetにRealmSwiftが追加されていない場合は、Target > WidgetExtension > General > FrameWork and Librariesから追加してください。
2.AppGroupの追加
アプリのTarget > Signing and CapitabilitiesにAppGroupを追加しましょう。
IDは group.com.yourname.appname と、アプリのBundle IDの先頭にgroupをつけた形式です。
全く同じIDのAppGroupをTarget > WidgetExtension > Signing and Capitabilitiesにも追加しましょう。
ここでAppGroupが赤字になっている時は、xcodeを再起動すると治る場合があります。
完成コード
import UIKit
import RealmSwift
class ViewController: UIViewController {
@IBOutlet weak var textField: UITextField!
@IBOutlet weak var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func buttonTapped(_ sender: UIButton) {
var config = Realm.Configuration()
let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.yourname.appname")!
config.fileURL = url.appendingPathComponent("db.realm")
let realm = try! Realm(configuration: config)
let item = Item()
item.text = textField.text!
realm.beginWrite()
realm.add(item)
try! realm.commitWrite()
}
}
class Item: Object {
@objc dynamic var text = ""
}
import WidgetKit
import SwiftUI
import RealmSwift
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
return SimpleEntry(date: Date(), items: [])
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> Void) {
completion(SimpleEntry(date: Date(), items: []))
}
func getTimeline(in context: Context, completion: @escaping (Timeline<SimpleEntry>) -> Void) {
var config = Realm.Configuration()
let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.yourname.appname")!
config.fileURL = url.appendingPathComponent("db.realm")
let realm = try! Realm(configuration: config)
let items = Array(realm.objects(Item.self))
let entry = SimpleEntry(date: Date(), items: items)
completion(Timeline(entries: [entry], policy: .atEnd))
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
let items: [Item]
}
struct widgetEntryView : View {
var entry: Provider.Entry
var items: [Item]
init(entry: Provider.Entry){
self.entry = entry
items = entry.items
}
var body: some View {
ForEach(items, id: \.self){ item in
Text(item.text)
}
}
}
@main
struct widget: Widget {
let kind: String = "widget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
widgetEntryView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
}
}
import UIKit
import WidgetKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
}
func sceneDidDisconnect(_ scene: UIScene) {
}
func sceneDidBecomeActive(_ scene: UIScene) {
}
func sceneWillResignActive(_ scene: UIScene) {
}
func sceneWillEnterForeground(_ scene: UIScene) {
}
func sceneDidEnterBackground(_ scene: UIScene) {
WidgetCenter.shared.reloadAllTimelines()
}
}
ポイント
Realmの保存先
var config = Realm.Configuration()
let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.yourname.appname")!
config.fileURL = url.appendingPathComponent("db.realm")
let realm = try! Realm(configuration: config)
この箇所でRealmのデータを保存する先を指定しています。
ここで保存先を最初に作ったAppGroupにしているので、Widgetもデータを取り出せるようになります。
group.com.yourname.appnameの部分はAppGroupのIDを入力してください。
Realmに保存するオブジェクト
class Item: Object {
@objc dynamic var text = ""
}
今回はtextというプロパティをもつだけのItemという名前のオブジェクトをRealmデータに保存しています。
この箇所を記述しているファイルは、右側にあるTarget MenberShipからwidgetExtensionを選択してください。
Widgetの更新
func sceneDidEnterBackground(_ scene: UIScene) {
WidgetCenter.shared.reloadAllTimelines()
}
この箇所でアプリを閉じる時にWidgetを更新するようにしています。
参考記事
Discussion