Home Accessing your App's User Defaults from Widgets Extension in Swift iOS
Post
Cancel

Accessing your App's User Defaults from Widgets Extension in Swift iOS

iOS Widgets offer a friendly and effortless way for users to get quick information and perform actions without navigating through the main app. However, when creating widgets we often face the common challenge of how to access user defaults settings that is stored within our main app from our widget. But don’t worry, I’m here to show you a solution using a combo of app groups and user defaults. With this approach, we we will be able to fetch and show personalized settings in our widgets that was set in our main app.

User Defaults

Before we dive into accessing user defaults from widgets, let’s briefly discuss what user defaults are. User defaults are a simple way for us to persist small amounts of data in the form of key-value pairs. They are commonly used to store user preferences, settings, or any other small data that needs to be preserved between app launches. User defaults are stored in a property list file and are automatically saved to disk by the system.

Step 1: Create a Shared App Group

To be able to access and store data to the same userdefaults as our main app from our widget, we will need to setup an AppGroup. If you don’t know what an AppGroup is, it’s exactly what the name denotes.

Here’s how apple explained AppGroup according to their documentation:

App groups allow multiple apps produced by a single development team to access shared containers and communicate using interprocess communication (IPC). Apps may belong to one or more app groups. Apps within an app group share access to a group container.

TLDR: App Group allows us to share data between different apps built by the same team using a common container.

With a combination of AppGroup with UserDefault suits we can achieve our goal. Which if you don’t know UserDefault suits allow us to create different UserDefaults for different sections of our app, but it also allows us to share content between multiple apps and extensions that use the same App Group.

To create an App Group, follow these steps:

  1. Open your project in Xcode.
  2. In the Project Navigator, select your app’s target.
  3. Go to the “Signing & Capabilities” tab.
  4. Click the “+ Capability” button.
  5. Choose “App Groups” from the list and click the “Enable” button.
  6. Create a new App Group or select an existing one.

Setting up User Defaults

Now that we have set up the App Group, we can now access the shared user defaults from our widget extension. Here’s how we can do it:

Open Xcode project and locate the widget extension target under the “Targets” section.

In the widget extension, we’ll need to use the shared App Group container to access the user defaults. To do this, use the following code to get the App Group container URL:

Storing Data

1
2
3
let defaults = UserDefaults(suiteName: "group.com.yourapp.widget") // Replace with your App Group identifier
defaults?.setValue("JohnDoe", forKey: "username")
defaults?.setValue("light", forKey: "theme")

Retrieving Data

1
2
3
let defaults = UserDefaults(suiteName: "group.com.yourapp.widget") // Replace with your App Group identifier
let username = defaults?.value(forKey: "username") as! String
let theme = defaults?.value(forKey: "theme") as! String

Here, we create a User Defaults instance using a shared App Group identifier. Make sure to replace “group.com.yourapp.widget” with your own App Group identifier. We also register default values for “username” and “theme” keys, which will be accessible from our widget.

Step 2: Creating a Widget Extension

In Xcode, go to File -> New -> Target and select “Widget Extension.” Choose the widget size you prefer, and click Finish. This will create a new target for the widget extension.

Step 3: Building the Widget UI

Open the file YourWidget.swift within the newly created widget extension target. Replace the default code with the following:

Make sure you use your app group identifier as the user defaults suitname. Again this is what will allow us to update and retrieve data between the app and the widget.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import SwiftUI
import WidgetKit

struct YourWidget: Widget {
    let kind: String = "YourWidget"

    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: Provider()) { entry in
            YourWidgetView(entry: entry)
        }
        .configurationDisplayName("Your Widget")
        .description("This is an example widget.")
    }
}

struct YourWidgetEntry: TimelineEntry {
    let date: Date
    let username: String
    let theme: String
}

struct Provider: TimelineProvider {
    func placeholder(in context: Context) -> YourWidgetEntry {
        YourWidgetEntry(date: Date(), username: "JohnDoe", theme: "light")
    }

    func getSnapshot(in context: Context, completion: @escaping (YourWidgetEntry) -> ()) {
        let entry = YourWidgetEntry(date: Date(), username: "JohnDoe", theme: "light")
        completion(entry)
    }

    func getTimeline(in context: Context, completion: @escaping (Timeline<YourWidgetEntry>) -> ()) {
        let currentDate = Date()
        let entry = YourWidgetEntry(date: currentDate, username: getUsername(), theme: getTheme())

        let timeline = Timeline(entries: [entry], policy: .never)
        completion(timeline)
    }

    // The secret sauce here:
    func getUsername() -> String {
        // make sure you use your app group identifier as the suitname
        guard let defaults = UserDefaults(suiteName: "group.com.yourapp.widget") else {
            return "JohnDoe"
        }
        return defaults.string(forKey: "username") ?? "JohnDoe"
    }

     // The secret sauce here:
    func getTheme() -> String {
        guard let defaults = UserDefaults(suiteName: "group.com.yourapp.widget") else {
            return "light"
        }
        return defaults.string(forKey: "theme") ?? "light"
    }
}

struct YourWidgetView: View {
    var entry: Provider.Entry

    var body: some View {
        VStack {
            Text("Welcome, \(entry.username)!")
                .font(.title)
            Text("Theme: \(entry.theme)")
                .font(.subheadline)
        }
    }
}

@main
struct YourWidgetBundle: WidgetBundle {
    @WidgetBundleBuilder
    var body: some Widget {
        YourWidget()
    }
}

In this code, we define the widget’s UI by creating a YourWidget struct that conforms to the Widget protocol. We also define a YourWidgetEntry struct that conforms to the TimelineEntry protocol, which represents the data to be displayed in the widget. The Provider struct implements the TimelineProvider protocol, responsible for providing the widget’s content and handling data updates. We retrieve the username and theme values from User Defaults within the Provider methods.

The YourWidgetView struct represents the actual widget’s view, displaying the retrieved data. Customize it further based on your requirements.

Conclusion:

By leveraging User Defaults, we can store user-specific data and provide personalized experiences to our users through widgets. With the provided example code, we should now be able to retrieve data from User Defaults and display it in our widget’s UI. Feel free to customize and enhance the widget according to your app’s requirements. Hope this was helpful.

This post is licensed under CC BY 4.0 by the author.