iOS Today Widget

3 minutes, 28 seconds

An app extension is not an app. It implements a specific, well scoped task that adheres to the policies defined by a particular extension point.

1. Create New Project

Create a new normal iOS project.

2. Add App Extension Target

File > New > Target … choose Today Extension. Give a name ‘Today Widget’.


3. Setup UI for Widget

For the widget it’s a UILabel that named as lbl_main.


4. Enable App Groups for both targets

Go to capabilities on each targets: Main App target and Extensions Target. Turn on ‘App Groups’, then hit ‘+’ button, add text like ‘’ or for this project we use ‘’ for both targets.


5. Create a Shared File

Create a new file called SharedFile.swift to share the common string that shared between targets so that we can avoid making mistake on using different key value.

import Foundation

public let K_MAIN_KEY = "main_key"
public let K_GROUP_ID = ""

Select the file and show the File Inspector. Tick both of the targets that available like this.


This step is good to have a cleaner integration where we don’t have to mix and match but rather to point at one place.

6. Setup the Main App

On the main app, there are a text field and a button.

When user tap the button, we will define the UserDefault then save with the value from the text field.


@IBAction func save(_ sender: UIButton) {
  let userDefault = UserDefaults.init(suiteName:K_GROUP_ID)
  userDefault?.set(tf_main.text, forKey:K_MAIN_KEY)

7. Setup the Widget’s View Controller

override func viewDidLoad() {

  NotificationCenter.default.addObserver(self, selector: #selector(self.userDefaultsDidChanged), name: UserDefaults.didChangeNotification, object: nil)


func userDefaultsDidChanged(){

func updateText(){
  let userDefaults = UserDefaults.init(suiteName: K_GROUP_ID)
  let string = userDefaults?.object(forKey: K_MAIN_KEY) as! String

  lbl_main.text = string


For now you can save the state of UserDefaults from the main app, then the widget will listen to changes happened for UserDefault inside view controller’s lifecycle.

If UserDefault changed it will update the widget view.


iOS 10 Expendable View

On iOS 10, there is a button on header right hand-side to ‘show more’ or ‘show less’. It can be supported by adding this code to viewDidLoad().

extensionContext?.widgetLargestAvailableDisplayMode = .expanded

Then we need to handle it properly with the size of canvas of the widget.

func widgetActiveDisplayModeDidChange(_ activeDisplayMode: NCWidgetDisplayMode, withMaximumSize maxSize: CGSize) {
  if (activeDisplayMode == .compact){
    preferredContentSize = CGSize.init(width: 320, height: 40)
  }else if (activeDisplayMode == .expanded){
    preferredContentSize = maxSize

2016-11-02 21_00_39.gif