iOS Swift WidgetKit Xcode

WidgetKitでUserDefaultsを使う + タイムラインの更新間隔を計測してみる

2022年2月23日

アプリ側とウィジェット側でUserDefaultsを共有するには設定が必要です。
App Groupsでグルーピングすることで共通のUserDefaultsを利用できるようになります。



今回やりたかったこと

ウィジェットのタイムラインの更新頻度を計測してみたかったので、タイムラインの更新タイミングで時間のログを取って一覧表示してみようと思いました。
ログの保存先としてUserDefaultsを使用します。(ウィジェット側でログを保存、アプリ側でログを取得)



ポイント

  • アプリとウィジェットを同じidentifierでApp Groupsの設定を行う
  • UserDefaults.standardではなくUserDefaults(suiteName: String)を使う



App Groupsの設定

前提としてWidget Extensionは追加済みとします。


アプリ側のTARGETに変更し、+Capabilityを選択します。


App Groupsをダブルクリックします。


App Groupsの項目が追加されるので、+ボタンでグループを追加します。


group.{プロジェクトのBundle Identifier}.xxxxの形式でグループを作成します。


追加した時点では赤文字になっていますが気にしないでください。


Widget Extension側も+CapabilityからApp Groupsを追加します。


追加したグループを選択します。

グループが表示されない場合はリロードボタンを押せば表示されます。


これでUserDefaultsを使う準備は出来ました!





UserDefaultsへのアクセス

UserDefaults.standardではなく以下の方法でUserDefaultsにアクセスします。

UserDefaults(suiteName: "group.com.sample.widget.test.xxxx")


UserDefaults.standardUserDefaults(suiteName: "group.com.sample.widget.test.xxxx")はそれぞれ別の場所で管理されているので、同じキーで値を保存しても別物になります。

///(例)
UserDefaults.standard.setValue(10, forKey: "aaa")
UserDefaults(suiteName: "group.com.sample.widget.test.xxxx")!.setValue(20, forKey: "aaa")

print("standard: \(UserDefaults.standard.integer(forKey: "aaa"))")
print("suiteName: \(UserDefaults(suiteName: "group.tsushima.shohei.widgetkit.demo")!.integer(forKey: "aaa"))")

/// 出力
// standard: 10
// suiteName: 20



(おまけ)ウィジェットの更新タイミングの計測

Widget Extension側のソースコードで、タイムラインの更新時に呼び出されるIntentTimelineProviderのgetTimelineで現在時刻をログとして記録するように実装しました。

(15分ごとにタイムラインが更新されるように設定しています。)

func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
    
    // タイムラインの更新タイミングで時間のログを追加
    let userDefaults = UserDefaults(suiteName: "group.com.sample.widget.test.xxxx")
    if let userDefaults = userDefaults {
        var logDates: [Date] = userDefaults.array(forKey: "logDates") as! [Date]
        logDates.append(Date())
        userDefaults.setValue(logDates, forKey: "logDates")
        userDefaults.synchronize()
    }
    
    // 15分ごとに更新
    let refresh = Calendar.current.date(byAdding: .minute, value: 15, to: Date()) ?? Date()
    let timeline = Timeline(entries: [], policy: .after(refresh))
    completion(timeline)
}


記録したログをアプリ側で取得して、UITableViewに表示して確認してみました。

(共通のUserDefaultsなので取得できてます!)


15分ごとに更新されるように設定していましたが、30分後に更新されたりなどかなりばらつきがありますね...。

やはり時間を指定したとしてもOS側で更新間隔は自動調整されているようですね。

時間帯やウィジェットの表示頻度によっても更新間隔はどんどん最適化されていくようなので、ウィジェットの更新間隔を正確に把握するのは難しそうです...。



  • この記事を書いた人

ツシマ ショウヘイ

フリーランスのiOS/Androidアプリエンジニア。 自作アプリがストアのカテゴリ別ランキングで2位を達成!! 自分用のメモを兼ねてブログを始めました。