What are widgets/today extensions?

There are extension that show in the Notification’s center Today view, and whose purpose is to display a short amount of relevant information. When we add a Today extension to our projects we aim to allow the user to obtain information in a quick manner (a single look), without forcing him to open the App, and navigate through various screens to obtain that information.

Adding a Today Widget to our project

To illustrate how to add a Today widget to an existing project I will use as a starting point the project I used some post ago to show how to do add left swipe actions to an UITable.

In that project we were using Alamofire to fetch data (a list of todos) from a rest service, and display them as list. If we have an App that display a list of Todos, we can improve the functionality by adding a Today Extensions that list the top most important pending todos, or the todos due to this day, or this week.

The name Today Extension/ Today Widget is a bit misleading: we are not coerced into displaying data for “today”: the information that we will consider valuable to display in the today View will vary from one application to another.

In this example, to make things easier I will be simply displaying the same Todos that I display in the main App, limited to the 2 top ones.

Steps to add a today widget

1. Select File/New/Add Target, and select TodayExtension.
Create new extension ...

2. Give a name to your extension, and add it to your project.

Choose target

XCode will show a warning offering to activate the extension scheme. Since this will allow us to debug the extension, we will agree to Activate the scheme.

Activate extension schema

4. Notice that in our project has appeared now a new folder with the name we had given to the extension (TodoExtension), and that there are already some files inside: a storyboard, a controller, and the Info.plist file.

Today extension folder ...

5. We will select the storyboard, and start to design the screen for our extension. For starters, I’ll change the size to work more comfortably.

Extension view controller

6. Add a table view controller to display our todos, and set the constraints so that the table view takes the whole of the view controller.

7. Next we should select the table view, and add a protoype cell.

8. We will assign an identifier to the cell: todoTodayCell

9. Lets take a look at our TodayViewController: this is the default code created automatically when we add the extension:

class TodayViewController: UIViewController, NCWidgetProviding {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view from its nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
func widgetPerformUpdate(completionHandler: (@escaping (NCUpdateResult) -> Void)) {
        // Perform any setup necessary in order to update the view
        // If an error is encountered, use NCUpdateResult.Failed
        // If there's no update required, use NCUpdateResult.NoData
        // If there's an update, use NCUpdateResult.NewData
        completionHandler(NCUpdateResult.newData)
    }

}

The function widgetPerformUpdate it is called to update the widget, so we will put the code to fetch the Todos to display here. But before that we are going to finish to set up the Table View.

Lets create a data source:

 var items : [Todo] = []

Add and IBOutlet for the Table View:

dd_2_outlet

And implement the datasource, and delegate extensions:

extension TodayViewController : UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }

    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: "todoTodayCell", for: indexPath)
        cell.textLabel?.text = items[indexPath.row].title
        return cell
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        /* So far empty, but we could implement this function so that we open the App and jump to the detail, when clicked */
    }

}

10. We will reuse the service we use in the main App to fetch the Todos.

TodoService.sharedInstance.getTodos().then { todos -> Void in
            self.items = Array(todos.prefix(2))
                self.reloadTable()
            }.catch { error in
              print("Error: could not load todos")
        }

In this example I am using the TodoService I had defined in the main App to fetch the data, and filtering the 2 top results. In a real application I might have in my TodoService a specific function to fetch the specific data relevant to the Today window.

We need to remember also to update the Target Membership of the files from our main project that we want to use in our extension to include our TodoExtension.

All in all our TodayViewController will look like this:

class TodayViewController: UIViewController, NCWidgetProviding {

    var items : [Todo] = []

    @IBOutlet weak var tableView: UITableView!
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    func reloadTable()
    {
        self.tableView.reloadData()
    }

    func performUpdate() {
        TodoService.sharedInstance.getTodos().then { todos -> Void in
            self.items = Array(todos.prefix(2))
                self.reloadTable()
            }.catch { error in
              print("Error: could not load todos")
        }
    }

    func widgetPerformUpdate(completionHandler: (@escaping (NCUpdateResult) -> Void)) {
        performUpdate()
        completionHandler(NCUpdateResult.newData)
    }
}

Before deploying the extension we can set the name it will display in the TodayView by modifying the attribute Bundle display name int the Info.plist of our extension folder.

dd_bundle_name

And with that we have a basic Today Extension for our todos ready to use :-).

today widget

If you found this post useful you can subscribe to my blog (click at the follow button at the bottom ) so you can be notified of new posts.

Advertisements