【Swift UI】App GroupsでWidgetやアプリ間でデータを共有する方法!

【Swift UI】App GroupsでWidgetやアプリ間でデータを共有する方法!

この記事からわかること

  • Swift UIWidgetアプリ間データ共有する方法
  • App Groups使い方実装方法
  • 新規コンテナ追加方法とUserDefaultsの使い方

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

環境

アプリ内に保存しているデータを別のアプリやWidget(ウィジェット)でも共有するためには「App Groups」という仕組みを使用する必要があります。今回はApp Groupsを使ったデータの共有方法をWidgetを例に紹介していきます。Widget自体の実装方法は下記記事を参考にしてください。

App Groupsとは?

公式リファレンス:App Groups

App Groups」とは異なるアプリやWidget同士でデータを共有できる仕組みのことです。端末の内部に共有でデータを保存するためのコンテナーをコンテナーIDをつけて作成し、メンバーシップに登録してあるアプリからであればコンテナー内のデータを操作することができるようになります。メンバーシップに登録できるのは自分が開発したアプリに限られますがApp Groups使うことで関連アプリを作ったり、アプリのデータを表示させるWidgetを表示させることができるようになります。

作成できる共有コンテナーは最大1000個までです。

新規コンテナーの作成

App Groupsを使用できるようにするためには前準備が必要です。以下の流れで新しくコンテナーを作成します。コンテナーを作成したらメンバーに対象のアプリを追加する必要があります。

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

コンテナーのメンバーはTargetごとに識別されるので今回はWidget側にも明示的に追加する必要があるので注意してください。

有効化すると「XXXXXXX.entitlements」というファイルが生成されます。ファイル名にDebugと付与されている場合はDebug側でしか反映されていない可能性があるので注意してください。実際にアプリをリリースする際にコンテナーIDを取得できずにクラッシュすることがあります。Debug / Releaseの両方で「XXXXXXX.entitlements」を指定する必要があるので「Build Settings」>「Code Signing Entitlements」でDebug / Releaseの両方に作成した「XXXXXXX.entitlements」が指定されているか確認してください。

データの保存と共有方法

コンテナー内に保存されるデータはUserDefaultsを使って操作できます。作成したコンテナーのUserDefaultsにアクセスするにはinit(suiteName:)形式のイニシャライザを使用します。

let userName = UserDefaults(suiteName: "group.com.ame.dev.WidgetTest")?.object(forKey: "name")

引数suiteNameに存在しないコンテナーIDを渡すとnilが返り、引数にnilを渡すとUserDefaults.standardと同義になるようです。

あとは通常のUserDefaultsを扱うように値を格納したり、取得したりするだけです。

Widgetへデータを共有する方法

既にコンテナーのメンバーシップへWidgetを追加しているのでWidgetからも共有コンテナー内のUserDefaultsからデータを取得することができます。

まずはアプリ側からUserDefaultsにデータの保存と取得をできるように記述していきます。


struct ContentView: View {
    @State  private var name: String = "NoName"
    
    private func setName() {
        guard let userName = UserDefaults(suiteName: "group.com.ame.dev.WidgetTest")?.object(forKey: "name") else {
            return
        }
        self.name = userName as? String ?? ""
    }
    
    private func entryName(_ name: String) {
        UserDefaults(suiteName: "group.com.ame.dev.WidgetTest")?.set(name, forKey: "name")
        setName()
    }
    
    var body: some View {
        VStack {
            Button {
                entryName("ame")
            } label: {
                Text("Entry")
            }
            Text(name)
                .padding()
        }.onAppear {
            setName()
        }
    }
}

続いてWidget側からUserDefaultsからデータを取得できるように独自の構造体を生成してWidgetに組み込んでいきます。


struct ContainerGroupManager {
    var name: String = "NoName"
    
    mutating func setName() {
        guard let userName = UserDefaults(suiteName: "group.com.ame.dev.WidgetTest")?.object(forKey: "name") else { return }
        self.name = userName as? String ?? ""
    }
}

続いてTimelineEntryとビューを修正します。entry経由でビューからデータを取得できるようにします。


struct SimpleEntry: TimelineEntry {
    let date: Date
    let name: String = "NoName"
}

struct TestWidgetEntryView : View {
    var entry: Provider.Entry
    
    var body: some View {
        Text(entry.name)
    }
}

最後にgetTimelineメソッドを以下のように変更すればWidget側にUserDefaults内に共有されているデータを表示することができました。


func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
    var entries: [SimpleEntry] = []
    var manager = ContainerGroupManager()
    manager.setName()
    
    let entryDate = Date()
    let entry = SimpleEntry(date: entryDate)
    entries.append(entry)

    let timeline = Timeline(entries: entries, policy: .atEnd)
    completion(timeline)
}
【Swift UI】App GroupsでWidgetやアプリ間でデータを共有する方法!

全体のコード


import WidgetKit
import SwiftUI

// MARK: - (1)
struct Provider: TimelineProvider {
    func placeholder(in context: Context) -> SimpleEntry {
        SimpleEntry(date: Date(),name:ContainerGroupManager().name)
    }
    
    func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
        let entry = SimpleEntry(date: Date(),name:ContainerGroupManager().name)
        completion(entry)
    }
    
    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        var entries: [SimpleEntry] = []
        var manager = ContainerGroupManager()
        manager.setName()
        
        let entryDate = Date()
        let entry = SimpleEntry(date: entryDate,name:manager.name)
        entries.append(entry)
    
        let timeline = Timeline(entries: entries, policy: .atEnd)
        completion(timeline)
    }
}

// MARK: - (2)
struct SimpleEntry: TimelineEntry {
    let date: Date
    let name: String
}

// MARK: - (3)
struct TestWidgetEntryView : View {
    var entry: Provider.Entry
    
    var body: some View {
        Text(entry.name)
    }
}

// MARK: - (4)
@main
struct TestWidget: Widget {
    let kind: String = "TestWidget"
    
    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: Provider()) { entry in
            TestWidgetEntryView(entry: entry)
        }
        .configurationDisplayName("My Widget")
        .description("This is an example widget.")
    }
}

// MARK: - (5)
struct TestWidget_Previews: PreviewProvider {
    static var previews: some View {
        TestWidgetEntryView(entry: SimpleEntry(date: Date(),name: ContainerGroupManager().name))
            .previewContext(WidgetPreviewContext(family: .systemSmall))
    }
}

// MARK: - (6) 追加
struct  ContainerGroupManager {
    var name: String = "NoName"
    
    mutating func setName(){
        guard let userName = UserDefaults(suiteName: "group.com.ame.dev.WidgetTest")?.object(forKey: "name") else { return }
        self.name = userName as? String ?? ""
    }
}

共有コンテナーURLを取得する方法

公式リファレンス:containerURL

作成した共有コンテナーのURLを取得するにはFileManagerクラスのcontainerURLメソッドを使用します。引数にはURLを取得したいコンテナーIDの文字列を渡します。無能な文字列を渡した場合はnilが返ります。

FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.ame.dev.WidgetTest")!

これを使用することでRealmなどのライブラリでもApp Groupsを使用することが可能になります。

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

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

Search Box

Sponsor

ProFile

ame

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

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

New Article

index