Sharing data with a Widget

Apple introduced widgets in iOS 14 to allow you to show useful information from your app on the home screen. Widgets have limited functionality, they are not even interactive, but you’re likely want to share some data from your app with the widget. Let’s see how to setup data sharing from your app to a widget.

App Group Entitlements

Widgets may limit what you can do but they work much like other app extensions. To share data between different targets on the same device you add them to an App Group.

Find your main app target in the Xcode project settings, switch to the Signing & Capabilities tab and click on + Capability:

Add Capability to a target

Double-click on App Groups in the list of capabilities to add it to the app target:

App Groups capability

This adds an App Groups setting to the Signing & Capabilities tab. If you have App Groups already created for this development team they will show up. Use the + to create a new group:

App Groups in the settings for a target

The format on iOS is to prefix the identifier with “group”. It’s probably a good idea to use a reverse domain name similar to a bundle identifier for the group name.

Add a new container

If you’re using Xcode to manage signing this will update the provisioning certificate for you. You can create (and delete) the identifier in the Apple developer portal and manually update the certificates if you prefer:

Create App Group identifiers in developer portal

You should end up with an entitlements file added to the project. This is a plist file containing the app group identifier:

Entitlement file

Repeat the process adding each target to the app group. Select the target, add the App Group capability and select the App Group from the list of available groups to enable it:

App Group identifier enabled

Accessing the Container

The App Group entitlement gives us a container directory we can use to share data. To get the URL of the container directory pass the group identifier string to the FileManager instance method containerURL(forSecurityApplicationGroupIdentifier:). I’ve never needed more than one App Group but I like to collect the magic into an enum:

// AppGroup.swift
import Foundation

public enum AppGroup: String {
  case facts = "group.com.useyourloaf.worldfacts"

  public var containerURL: URL {
    switch self {
    case .facts:
      return FileManager.default.containerURL(
      forSecurityApplicationGroupIdentifier: self.rawValue)!
    }
  }
}

For example, if I’m sharing a plist file from my App to a widget I would use the following URL to write the file in the App and then read it from the widget:

let storeURL = AppGroup.facts.containerURL.appendingPathComponent("World.plist")

What about sharing Core Data?

There’s nothing stopping you creating your Core Data database in the App group container. Use the container URL to create a persistent store description:

let storeURL = AppGroup.facts.containerURL.appendingPathComponent("World.sqlite")
let description = NSPersistentStoreDescription(url: storeURL)

let container = NSPersistentContainer(name: "World")
container.persistentStoreDescriptions = [description]
container.loadPersistentStores { ... }

If you need to move a Core Data store to the container use the migratePersistentStore method of NSPersistentStoreCoordinator.

One question I would ask before doing this. Do you need to use Core Data with a widget?

You might be better off keeping it simple. A widget is not interactive and shows a small amount of information. My preference would be to extract the data you need in the main App and share it as a simple plist or JSON file to the widget.