Rohit Tomar

How to create widgets in iOS 10

Widgets are something that every trending iOS application is looking forward to. Widgets help users to get some quick and relevant information without even opening the app. They are best suited for applications like Weather apps, To-Do list apps, Calendar apps which might show a day’s schedule. The widget should provide the immediate and relevant information that user must be interested in. The amount and type of information that widget carries, varies according to the requirement of the containing App of the widget.

iOS 10 widgets

We will learn through this blog what are widgets and how to make widgets for our iOS app. We have already made an application called RemindME which is a small To-Do list application.

What we are going to cover 

  • iOS 10 Widget UI
  • Sharing UserDefaults and Files with Widget
  • Updating the content
  • Adjusting Widget’s Height
  • How to open App from Widget
  • When my Widget should Appear?
  • Home Screen Widget

Start-Up Project

We have already created an application that allows user to add some To-Do items with title and description and shows its status as done or not done. This will be containing app for our widget. The source code for the project  can be downloaded here for the reference. Here is how our containing app looks like-

containing app looks

Getting Started

So, enough saying now and let’s get started with the real code. Widgets are nothing but a type of App extensions called Today Extension. So if anybody needs to know about app extensions, you can see this apple documentation. Now, add target by selecting File/New/Target and then under iOS section select Today extension and press next. After that give name to your widget and select the containing app as your project.

get started with the real code

Now we can see in project navigator a new group will be added with our Widget’s name. Change Schema and select target as the widget and run project. You will  see widget in the notification center of our simulator with a message “Hello world” like this.

Widget's name

iOS 10 Widget UI

Let us now design how our widget is going to look like. Open the storyboard of our newly added target and remove the default label. We are going to add a tableview and a button and two labels in our tableview cell. We have added UIImage for the status of the reminder and labels for title and description of the reminder. We have added a class called WidgetTableViewCell. Select the tableview cell and in identity inspector, add class name as WidgetTableViewCell. Add constraints to the views as needed and connects the outlets in the WidgetTableViewCell using Assistant Editor.

Widget UI

We will also add Visual Effect View With Blur  from the object browser in our tableview cell. This is to add vibrancy effect to our widget. This causes a blur background in our Widget’s view. This is to make it look similar to what default iOS widgets look like. Make an outlet of visual effect view in the WidgetTableViewCell and name it as “visualEffectView”.

 @IBOutlet weak var visualEffectView: UIVisualEffectView!

Now add this code in the awakeFromNib of the tableview cell.

  override func awakeFromNib() {
        super.awakeFromNib()
        visualEffectView.effect = UIVibrancyEffect.widgetPrimary()
    }

This ensures that vibrancy is similar to system-defined one for Today Extensions and the UI looks like default iOS 10 widgets.

Sharing UserDefaults and Files with Widget

Widgets are nothing but extensions. Extensions are just like another application so our containing app and widgets cannot communicate directly. There may be situations when our containing app and widget need to share some data. We can do that by sharing UserDefaults between them.

To enable this, select the main App target and choose Capabilities tab and then enable App groups. We require apple developer account for this. Now, create an app group. App group name must start from “group”. We have created an app group with name “group.com.quovantis.RemindMe”.

app group with name

Similarly we will repeat the process by selecting the target for widget. But we don’t have to create a new app group, we can use the one we created before. Now when we will save or retrieve anything from UserDefaults we will use this group.

We have made a class that will manage writing and reading our Reminders from UserDefaults. This code can be used by both our containing app as well as our widget extension. Here is the class.

 class UserDefaultsManager {
    private static let reminderKey:String = "reminderKey"
    
    class func addReminders(reminderDetails: [[String:AnyObject]]) {
        UserDefaults(suiteName: "group.com.quovantis.RemindMe")?.set(reminderDetails, forKey: reminderKey)
    }
    
    class func getReminders() -> [[String:AnyObject]]? {
        let value =  UserDefaults(suiteName: "group.com.quovantis.RemindMe")?.value(forKey: reminderKey)
        return value as? [[String:AnyObject]]
    }
    
}

We have used UserDefaults(suiteName: “group.com.quovantis.RemindMe”) for writing and reading our Reminders. This ensures both our containing app and the Widget extension save and read the data from a shared UserDefault under a common suite.

Now if we have to use the same UserDefaultsManager class in our Widget target also, we will have to enable it explicitly from the File inspector. Select the file UserDefaultsManager.swift and from File inspector check RemindMeWidget. Similarly if we have to import any other file like Assets.xcassests into our widget extension we can do the same.

User Defaults Manager

 

Now we have shared UserDefaults between main app and widget, we can now add content to our widget. We will read data from the UserDefaults and will show the reminders in the table view of the widget. Widget will show the title and description of the Reminder and a button checked or unchecked depending upon the status. We will fetch the reminders from the UserDefaults in the viewWillAppear and will refresh the tableView. You can look the code in the TodayViewController.swift file in the RemindMeWidget folder.

We can also add actions to our widget. We have added an action to the button in our widget. When it is pressed that reminder is marked as done by updating status of the reminder in UserDefaults. Code for this action is written in WidgetTableViewCell class.

Now when we add any reminder from our main App, it will be reflected back to our widget also. Here is how our widget looks like now-

New widget looks

Updating the content

Now we will see how to add support to our widget to update its view when it’s off-screen, by allowing the system to create a snapshot. The system periodically takes snapshot to help our widget stay up to date. That is done through widgetPerformUpdate method.

We need to replace the existing implementation of widgetPerformUpdate with the following

 func widgetPerformUpdate(completionHandler: (@escaping (NCUpdateResult) -> Void)) {
        if let reminders = UserDefaultsManager.getReminders() {
            reminderList = reminders
            completionHandler(.newData)
        }else {
            completionHandler(.failed)
        }
    }

If we get reminders from the UserDefaults successfully, we update the interface and the function calls the system-provided completion block with the .newData enumeration. This informs the system to take the fresh snapshot and replace it with the current.

If we fail to get reminders then we call the completion handler with .failed enumeration. This informs the system that no new data is available and the existing snapshot should be used.

Adjusting Widget’s Height

As we have limited space in our Today View, System provides a fixed height to all the widgets. We have to design our widget UI in accordance with that . But we can also provide the option to increase the height of the widget so as to show more information in the Today view.

Adjust Widget's Height

Our TodayViewController conforms to the NCWidgetProviding that provides the method to do this. There are two modes of the Widget of type NCWidgetDisplayMode, compact and expanded. In viewDidLoad of the TodayViewController write this-

extensionContext?.widgetLargestAvailableDisplayMode = .expanded

This will specify that widget can be expanded and we get a default button to expand and close the widget. Now add this method to adjust the size of the widget.

 func widgetActiveDisplayModeDidChange(_ activeDisplayMode: NCWidgetDisplayMode, withMaximumSize maxSize: CGSize) {
        
        if (activeDisplayMode == NCWidgetDisplayMode.compact) {
            self.preferredContentSize = CGSize()
        }
        else {
            let height = reminderList.count > 0 ? reminderList.count * 59 : 59
            self.preferredContentSize = CGSize(width: 0, height: height);
        }
        
    }

Whenever the widget height is adjusted this method is called where we have specified the height in case of compact and expanded state. In expanded mode we set preferredContentSize to the combined height of the cells of our tableview in Widget and when it is in compact mode we set preferredContentSize as CGSize() which gives the default height to the widget. This is how our expanded view looks like.

preferred Content Size

How to open App from Widget

Users may also want to open the containing app from widgets so as to have a detailed look over the information shown in the widget. For that we will have to allow our widget to open our containing app.

For this we use URL Schemes. We can open any app that supports URL Scheme from our widget but we should do that rarely. As user may get confused regarding the container app of the widget. Generally we should link the containing app of the widget only.

For enabling the URL Schemes in our containing app we need to add the following rows in our info.plist

URL Schemes

After that we use the extensionContext property of type NSExtensionContext in our TodayViewController class to call the openUrl function. We have written this code in didSelectRowAt Index of our tableView

 func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        extensionContext?.open(URL(string: "widgetReminderDemo://")!, completionHandler: nil)
    }

Now when we will tap on our widget, our containing app will open.

When my Widget should Appear?

There can be situations when under certain circumstances, you want your widget to appear in Today view. For example, there is no new or noteworthy information to show and you want to hide your widget until something new information comes up.

You can do this by using setHasContent: forWidgetWithBundleIdentifier: method of class NCWidgetController. Set the flag as true or false to show or hide the widget respectively and specify the bundle identifier of the particular widget which we want to hide or show.

In our case we will hide the widget if there are no reminders in our app. And as soon as the count of the reminders is one or more we will show the widget again.

We can write the code to hide widget from the TodayViewController itself but we must always avoid this. Our containing app should be the one which will decide when to hide or show the widget.

Write this function in the ReminderListViewController-

 func hideShowWidget() {
        if reminderList.count == 0 {
            widgetController.setHasContent(false, forWidgetWithBundleIdentifier: "com.quovantis.rohit.Remind-Me.RemindMeWidget")
            
        }else {
            widgetController.setHasContent(true, forWidgetWithBundleIdentifier: "com.quovantis.rohit.Remind-Me.RemindMeWidget")
        }
    }

Home Screen Widget

We have seen applications show widget along with the quick actions when we Force Touch the app icon. If we have only one widget in our app, it is shown by default. But if our app offers two or more widgets then we will have to specify the bundle identifier of the widget in the info.plist that we want to show when we Force touch our app.

Add UIApplicationShortcutWidget key in the info.plist with the bundle identifier of the widget as its value.

Home Screen Widget

Summary

So, this is all about iOS 10 Widgets for iPhone. Thank you for reading this tutorial and I hope you enjoyed this. You can think some more innovative ideas to explore more possibilities from Today extensions and think about ways to update your existing applications with your own widgets.

Happy Learning 🙂

 

Related Articles

#Tech

NHibernate, Linq and Lazy Collections

For the past few months we have been busy building a services framework for one of our clients so that they can build all of their enterprise web services without ever having to worry about the cross cutting concerns and... Read more
#Tech

Page Redirects using Spring.NET

Who is responsible for page redirects in ASPNET MVP – The View or the Presenter None of the above, it is you :) On a serious note, it is the View as one shouldn’t pollute the Presenter with the page navigation... Read more