This tutorial explains how to create a new Today Widget for iOS in Swift 3 using App Extensions in Xcode.  It covers how to import pods, set up entitlements for inter-app communication via user defaults and custom url schemes.  It also explains how to remove the existing storyboard and implement the today widget view programmatically instead of using Auto-Layout.

Create the Widget

First, Select your project and then create a new target via File, New, Target…

From the iOS tab, choose the “Today Extension” template. Press Next.

Fill in the Product Name and select the appropriate “Embed in Application” target.

From the Extension’s Target General Tab you may want to set your Version and Build to match your main iOS project in order to avoid the following warning when you submit your build to iTunes Connect: CFBundleVersion Mismatch and CFBundleShortVersionString Mismatch.

Remove Storyboards

If you don’t want to use storyboards then delete the “MainInterface.storyboard” file.  From Info.plist, navigate to the NSExtension dictionary and delete the NSExtensionMainStoryboard key.  Add a new key to the NSExtension dictionary titled “NSExtensionPrincipalClass” and for the value choose your main “ViewController.swift” that conforms to the NCWidgetProviding protocol.

Also add the @objc(ViewController) tag to the top of your View Controller class to avoid the error “Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘*** setObjectForKey: object cannot be nil”.

Import Pods

If you would like to use cocoa pods with your widget, then add the new widget target to the Podfile with a new target.  The below example will add the SnapKit pod to the “TodayExtension” widget.

target 'TodayExtension' do
    platform :ios, '9.0'
    use_frameworks!
    pod 'SnapKit', '~> 3.2.0'
end

Create the View

You can use auto layout to create your today widget view.  The width of the widget is always fixed. By default the today widget is in a compact size and has a fixed height of 110pts.  However, the user can press “Show More” on the widget to expand it larger and “Show Less” to compact it. When in the “Show More” or .expanded state the widget can have a variable height up to the size of the screen.

To know when the user has changed the display mode implement func widgetActiveDisplayModeDidChange(_ activeDisplayMode: NCWidgetDisplayMode, withMaximumSize maxSize: CGSize)
You can determine the active display mode and retrieve the max width and heights available via the below.  If you are in expanded NCWidgetDisplayMode then use .expanded instead of .compact

if self.extensionContext!.widgetActiveDisplayMode == .compact {
    let todayWidth = self.extensionContext!.widgetMaximumSize(for: .compact).width
    let todayHeight = self.extensionContext!.widgetMaximumSize(for: .compact).height
}

Entitlements

If you would like to share data between the today widget and your container/host application you will need to add the “App Groups” entitlement.  From your project, go to your widget’s Target and select the Capabilities tab.  Turn on App Groups and add a new App Group with a unique name such as “group.com.domain.app”.  Go to your container/host application’s Capabilities  and turn on App Groups also, select same app group you previously created.

You can now share data between the two apps using UserDefaults suite name.  For example: let sharedDefaults = UserDefaults(suiteName: "group.com.domain.app")!

 

Custom URL

If you would like to communicate a press or tap on your Today Widget to open your container/host application you will need to set up a custom url scheme.  Follow the first step on how to register a custom URL scheme here How to open an iOS app with custom URL.

From your today widget you can now use the below sample code to open a custom url:

    self.extensionContext?.open(url, completionHandler: { (Bool) in
        //Handle callback
    })

From your container/host application, implement the following function in AppDelegate.swift to handle the custom url:

func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
    //do something
    return true
}

 

Sources:

https://developer.apple.com/library/content/documentation/IDEs/Conceptual/AppDistributionGuide/AddingCapabilities/AddingCapabilities.html

https://developer.apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html#//apple_ref/doc/uid/TP40014214-CH21-SW1

https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/Inter-AppCommunication/Inter-AppCommunication.html#//apple_ref/doc/uid/TP40007072-CH6-SW10

 

How to create a Today Widget for your iOS App in Swift
Translate »