【Swift UI】App GroupsでWidgetやアプリ間でデータを共有する方法!
この記事からわかること
- Swift UIでWidgetやアプリ間でデータを共有する方法
- App Groupsの使い方と実装方法
- 新規コンテナの追加方法とUserDefaultsの使い方
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
アプリ内に保存しているデータをWidget(ウィジェット)にも共有するためには「App Groups」を使用する必要があります。今回はApp Groupsを使ったデータの共有方法をまとめていきます。Widgetの実装方法は下記記事を参考にしてください。
App Groupsとは?
App Groupsとは異なるアプリ同士でデータを共有できる機能のことです。
内部的に共有コンテナーをコンテナーIDをつけて作成し、メンバーシップに登録してあるアプリからコンテナー内のデータを操作することができます。メンバーシップに登録できるのは自分が開発したアプリに限られますがApp Groups使うことで関連アプリを作ったり、Widgetを表示させることができるようになります。
作成できる共有コンテナーは最大1000個までです。
新規コンテナーの作成
App Groupsを使用できるようにするためには前準備が必要です。以下の流れで新しくコンテナーを作成します。コンテナーを作成したらメンバーに対象のアプリを追加する必要があります。
- アプリ側の「Signing & Capabilities」タブを開く
- 「+」ボタンから「App Groups」を追加
- コンテナーID名を決める(例:group.com.ame.dev.WidgetTest)
- Widget側の「Signing & Capabilities」タブを開く
- 「+」ボタンから「App Groups」を追加
- 先ほど追加したコンテナーにチェックを入れて有効化
コンテナーのメンバーは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)
}
全体のコード
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を取得する方法
作成した共有コンテナーのURLを取得するにはFileManager
クラスのcontainerURL
メソッドを使用します。引数にはURLを取得したいコンテナーIDの文字列を渡します。無能な文字列を渡した場合はnil
が返ります。
FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.ame.dev.WidgetTest")!
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。