🔨

Swift - RealmのデータをWidgetと共有する方法

2022/05/14に公開約5,100字

作るもの

今回は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を再起動すると治る場合があります。

完成コード

ViewController.swift
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 = ""
}
widget.swift
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.")
    }
}
SceneDelegate.swift
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のデータを保存する先を指定しています。
ここはRealmを使うときに毎回記述することになるので、Structにまとめておくと便利です。
ここで保存先を最初に作ったAppGroupにしているので、Widgetもデータを取り出せるようになります。

group.com.yourname.appnameの部分はAppGroupのIDを入力してください。

Realmに保存するオブジェクト

ViewController.swift
class Item: Object {
    @objc dynamic var text = ""
}

今回はtextというプロパティをもつだけのItemという名前のオブジェクトをRealmデータに保存しています。
この箇所を記述しているファイルは、右側にあるTarget MenberShipからwidgetExtensionを選択してください。

Widgetの更新

SceneDelegate.swift
func sceneDidEnterBackground(_ scene: UIScene) {
    WidgetCenter.shared.reloadAllTimelines()
}

この箇所でアプリを閉じる時にWidgetを更新するようにしています。

参考記事

https://qiita.com/oidy/items/3bcb26d67a1c4c9d90c7

Discussion

ログインするとコメントできます