Today Extensions in iOS 10 and Swift 3
The widgets that are found in the Today view of our iOS devices are called extensions. Not only are they a useful way to present information to our users, but they are easy to implement and keep our users engaged with our app, even when they are not open.
My interest in widgets came from an app that I am currently working on. Ignite - Be Inspired sends an inspirational message at the time of your choice to ignite your day every day. Creating an extension for this app is powerful because it allows users to keep their motivating message on display without having to open the app.
To create our today extension we will have to open up Xcode and go to File > New > Target > Application Extension > Today Extension.
Once this file has been created you will notice that there is now a designated target with it’s corresponding files, including a storyboard.
In the Storyboard file I created two labels that will display the text I’d like to include in the today extension. After doing so I hooked up an IBOutlet for the second label and named it quote
since this will be the only label who’s text we will change.
In the viewDidLoad()
method from the TodayViewController
I added this line of code to give the quote
label a default message.
quote.text = "Open your app to read your quote of the day."
Upon opening the widget, the today extension will tell the user to open up the app to receive their quote of the day.
Since the purpose of using this widget is to display information from the app, we have to figure out a way to access the data in the app and make it available in the extension. To do this I used UserDefaults
to share information and access them across the different targets.
Before we touch UserDefaults
we have to create an App Group so that we can store the information that we want to share between the widget and our app. To do this we will have to go to our project and click on our app’s target. Then we will go to Capabilities > App Groups > ON. From here we will click on the + sign and create an app group like the one I created above. *Make sure to copy the name of this group since we will be creating another app group for our extension target.
Next we will go to our widget’s target and repeat the same exact steps using the same app group name.
From here we can begin using UserDefaults
to share data. In our app I’ve added this line of code to store the information I’d like to use in my widget.
UserDefaults.init(suiteName: "group.com.joycematos.ignite")?.setValue("If you are not willing to risk the usual, you will have to settle for the ordinary.", forKey: "testQuote")
Here I have included the name of the app group and setting a value and key pair that can be used whenever this app group is invoked.
Next I will move on to my extension target and write a few lines of code describing the type of actions I’d like to take in the viewDidLoad()
method.
if let quoteFromApp = UserDefaults.init(suiteName: "group.com.joycematos.ignite")?.value(forKey: "testQuote") {
quote.text = quoteFromApp as? String
}
Here I am unwrapping the value that is stored inUserDefaults
and setting the quote
label to display the value that we just unwrapped. When our widget loads, it will display the quote that we stored in our app.
Notice that our TodayViewController
has a method called widgetPerformUpdate(completionHandler:)
. This is where our widget gets notified of any updates about our data.
In this method we will add these lines of code:
if let quoteFromApp = UserDefaults.init(suiteName: "group.com.joycematos.ignite")?.value(forKey: "testQuote") {
if quoteFromApp as? String != quote.text {
quote.text = quoteFromApp as? String
completionHandler(NCUpdateResult.newData)
} else {
completionHandler(NCUpdateResult.noData)
} else {
quote.text = "Open your app to read your quote of the day."
completionHandler(NCUpdateResult.newData)
}
This may seem like a lot but I’ll explain it step by step. First we will unwrap the value from our UserDefaults
if let quoteFromApp = UserDefaults.init(suiteName: "group.com.joycematos.ignite")?.value(forKey: "testQuote") {// Do Something} else {
quote.text = "Open your app to read your quote of the day."
completionHandler(NCUpdateResult.newData)}
If we are able to successfully unwrap the value in our UserDefaults
, we will perform some type of action. If we are not able to unwrap this value, it’s because nothing has been stored to UserDefaults
and we have to open our app to retrieve that data.
If there is a value in UserDefaults
that we can unwrap, we will do this
if quoteFromApp as? String != quote.text {
quote.text = quoteFromApp as? String
completionHandler(NCUpdateResult.newData)
} else {
completionHandler(NCUpdateResult.noData)
}
We check to see if the value stored in our UserDefaults
is the same as the text being displayed in our widget. If these values are not equal, this means that our app has updated and we will call on the completion to notify our widget of this update. However, if these values are equal, we will call on the completion and let our widget know that there is no update to be made.
And that’s it! That’s how you create a today extension and update it based on the information we receive from our app.
Thanks for reading and happy coding :)