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

この記事からわかること

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

index

[open]

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

みんなの誕生日

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

posted withアプリーチ

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

App Groupsとは?

公式リファレンス:App Groups

App Groupsとは異なるアプリ同士でデータを共有できる機能のことです。

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

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

新規コンテナーの作成

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

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

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

データの保存と共有方法

コンテナー内に保存されるデータは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にデータの保存と取得をできるように記述していきます。


import SwiftUI

struct ContentView: View {
    @State  var name:String = "NoName"
    
    func setName(){
        guard let userName = UserDefaults(suiteName: "group.com.ame.dev.WidgetTest")?.object(forKey: "name")else{
            return
        }
        self.name = userName as! String
    }
    
    func entryName(_ name:String){
        UserDefaults(suiteName: "group.com.ame.dev.WidgetTest")?.set(name, forKey: "name")
        setName()
    }
    
    var body: some View {
        VStack{
            Button(action: {
                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()
    
    // Generate a timeline consisting of five entries an hour apart, starting from the current date.
    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()
        
        // Generate a timeline consisting of five entries an hour apart, starting from the current date.
        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")!

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

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

searchbox

スポンサー

ProFile

ame

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

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

New Article

index