Store an array on the app group for WidgetKit

991 Views Asked by At

I'm trying to store an struct array into the app group container to use it later on the widget. let's assume, I have an array of string

let = array = ["object1", "object2", "object3"]

and I saw here I can access to the app group container url

let applicationGroupId = "group.com.development.widget"
            
guard let groupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: applicationGroupId) else {
    fatalError("could not get shared app group directory.")
}

Now, I need to know how I can store this array on app group and how I can read from it on the Widgetkit SwiftUI class

Your help will be appreciated.

1

There are 1 best solutions below

0
On

Signing, Capabilities, Entitlements

Right now it should be possible to do all of this from within Xcode (12+). In the app's project settings go to the Signing & Capabilities tab and make sure Automatically manage signing is enabled and the Team has a valid development team (either company or personal account).

Tap on the + Capability button on the top left to enter the App Group Capability (this will also add an entitlements file). Now you will find an App Group section. Hit the + button for App Groups and enter a globally unique identifier (something like group.<appid>). This will create the app group on the developer platform and add it to your app's provisioning profile and entitlements.

You must repeat the same steps and use the same app group for all targets (app target, widget extension target and (optionally) intent targets). If you hit the reload button for the other targets' app group it should automatically show the app group you previously registered and you can check the checkbox. If not, hitting + and using the same id should work as well.

Finally, check all entitlements files and ensure that they have the same String in App Groups (copy and paste just to make sure).

Code

In code you now just have to access the user defaults using the chosen app group as suitName. To test whether everything is set up correctly, I would put the following line in application(_:didFinishLaunchingWithOptions):

UserDefaults(suiteName: "group.id.test")!.setValue(["object1", "object2", "object3"], forKey: "test")

In the Widget just try to put it in a Text view:

struct WidgetTestEntryView : View {
  var entry: Provider.Entry
  var text: String {
      (UserDefaults(suiteName: "group.id.test")!.value(forKey: "test") as? [String])?.joined(separator: " ") ?? "Nothing stored yet"
  }

  var body: some View {
    Text(text)
  }
}

Note: I force unwrap the user defaults on purpose to fail early and check whether the setup of capabilities/signing worked. In production you should guard against a failure.

Alternative Storage

As you pointed out it's also possible to persist data in a shared container by writing to url provided by FileManager.default.containerURL(forSecurityApplicationGroupIdentifier:) but I don't think you need that for your simple example. If you want to share data that is more complex than String, Int, Array ... make sure to convert it to Data since user defaults cannot handle arbitrary data (even if it's Codable).

Manual Signing

If you use manual signing you just have to do the same step in the developer console starting with registering an app group. After that, add the group to all identifiers' capabilities and recreate all provisioning profiles. From there you should be use similar steps to the ones described above.