【Swift UI】WidgetにRealmのデータを表示させる方法!

この記事からわかること

  • Swift UIWidgetRealmデータ表示させる方法
  • 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を使った共有コンテナー内に構築する必要があります。

実装の流れ

  1. 新規プロジェクトを生成
  2. Widgetの追加
  3. Realmの導入
  4. App Groupsで新規コンテナーの作成
  5. コンテナーメンバーシップにアプリとWidgetを追加
  6. 共有コンテナーのURLを取得
  7. Realmの保存先URLに取得したURLを設定

今回はライブラリ管理ツールであるCocoa Podsを使用している場合の実装方法をまとめていきます。

まずは新規でプロジェクトを立ち上げ、さらにプロジェクト内に新しくWidgetを追加(「File」>「 New 」>「Target...」を選択後「Widget Extension」)しておきます。

Xcodeの新規プロジェクト作成画面 【Swift UI】Widget(ウィジェット)の実装方法!

これで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を追加し新規コンテナーを作成しておきます。

  1. アプリ側の「Signing & Capabilities」タブを開く
  2. 「+」ボタンから「App Groups」を追加
  3. コンテナーID名を決める(例:group.com.ame.dev.WidgetTest)
  4. Widget側の「Signing & Capabilities」タブを開く
  5. 「+」ボタンから「App Groups」を追加
  6. 先ほど追加したコンテナーにチェックを入れて有効化
【Swift UI】App GroupsでWidgetとアプリのデータを共有する方法!

作成したらWidget側からも作成したコンテナーにアクセスできるようにチェックを打っておきます。

【Swift UI】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にチェックを入れておきます。

【Swift UI】WidgetにRealmのデータを表示させる方法!

アプリ内でデータの登録処理を記述

アプリ内からデータを登録できるようにビューを構築していきます。ここでのポイントは以下の2つです。


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からデータを取得して反映させるように記述すれば完成です。ここで追記するのは以下のポイントです。

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が完成しました。

【Swift UI】WidgetにRealmのデータを表示させる方法!

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index