Widgets have been around on macOS and iOS for years. Here's how to use Apple's WidgetKit to create your own versions.
What are Widgets?
Apple introduced Widgets years ago first in macOS Tiger and later in iOS 14 in 2020. Simply put, Widgets are small applets that display info to users temporarily without having to launch a full-blown app.
Originally macOS Widgets were part of the macOS Dashboard.
In macOS, originally you dragged Widgets onto the desktop, where they floated. Later Apple moved them all to the macOS sidebar where you access them by clicking on the clock in the upper-right corner of the macOS menubar.
In this fall's macOS Sonoma, Apple is once again allowing you to display widgets on the desktop.
Currently in macOS Ventura, to edit the Widgets in the sidebar, open the sidebar by clicking the menu bar clock, then Control-click on any one Widget in the sidebar:
To edit the list of Widgets displayed in the sidebar, Control-click any one widget and select Edit Widgets from the same popup menu.
This opens the Widget Editor which shows the Widget sidebar on the right, but also displays all available Widgets in a pane on the left. To add a Widget to the sidebar, drag it from the lefthand pane into the Widget sidebar on the right:
Creating your own Widgets
If you still need to create Widgets for earlier systems, check out Beginning Mac OS X Tiger Dashboard Widget Development by Fred Terry from WROX Publishing (ISBN 978-0-471-77825-7).
At WWDC 2020, Apple introduced a brand new framework for developing Widgets called WidgetKit. Apple has full WidgetKit developer documentation on its developer website at Documentation/WidgetKit.
To write a Widget you'll need to know the Swift language, SwiftUI, and Xcode. Keep in mind that in most cases, Widgets are really additions to existing applications.
Full SwiftUI documentation is on Apple's developer site at /Documentation/SwiftUI. Apple also has a SwiftUI Tutorials page at https://developer.apple.com/tutorials/swiftui/
Creating complex Widgets is beyond the scope of this article so we'll use a simple example here. In future articles, we'll delve more in-depth into developing complex and full-featured Widgets.
Widgets live inside an app in the /Contents/Plugins folder inside the app Bundle. Bundles in macOS and iOS are just special folders that package all app code and assets together in one place.
To use your Widget you'll need to create a macOS app first using Xcode, build it, then build your Widget and install it inside your app's /Contents/Plugins folder.
To do so manually, Control-click your app on the macOS desktop and select Show Package Contents from the popup menu:
Navigate to the /Contents/Plugins folder. Widgets have an app extension of .appex.
When you build a Widget in Xcode, it creates the Bundle folder for you, compiles your code, and copies the code binary and any needed resources into the Bundle.
Since Widgets are Bundles themselves, you can also Control-click on them in Finder and again using Show Package Contents from the popup menu to peek inside them.
Inside each Widget Bundle, you'll find a single folder named "Contents", and inside of that, folders named "_CodeSignature", "MacOS", and "Resources":
The Widget code itself lives in the "MacOS" folder, and most other resources such as user interface files, text strings, images, and more live in the "Resources" folder. There are two additional files in the Widget Bundle which describe the Bundle to the system: "Info.plist", and "version.plist".
These files are auto-generated by Xcode when you build your Widget.
Hiding away on your Mac's hard drive in /System/Library/Core Services is an Apple app called WidgetKit Simulator. WidgetKit Simulator lets you peek inside apps and see all the Widgets they contain, then open them in the simulator to see what they will look like when they run:
To do so, run WidgetKit Simulator, then select any one of the apps shown on the left. macOS knows how to find all apps installed on your Mac which contain Widgets:
Once the simulator opens a Widget, if you click on the "Info" row on the left, the simulator shows you all the details about your Widget. This is the same info as stored in the "Info.plist", and "version.plist" files:
If you click on the "Snapshot" or "Timeline" row, the simulator will show you what your Widget will look like when it actually runs.
When you design and build a Widget, you can keep it extremely simple - with just one size and interface, or you can make it more complex which utilizes multiple sizes, and load data from the network.
For iPadOS apps you can choose an extra-large sized Widget user interface.
You can also choose to build a Widget that runs on an iOS lock screen, or just on the iOS home screen or macOS desktop. In this example, we'll keep it as simple as possible.
In order to create a Widget, or in Xcode a Widget Extension, you'll first need an existing iOS or macOS Xcode app project. If you don't already have one, create a new one in Xcode by selecting "File->New->Project" from the File menu. This opens the Xcode template chooser.
In the chooser, select "macOS" and "App" from the top menu, and the App row of icons below it. You could just as easily choose an iOS app, but for this example, we'll use macOS:
Click the Next button, and then fill in your project's details: a product name, organization identifier, which kind of UI you want, and choose Swift for the language:
Click the Next button again, and save your new project to disk. We'll assume you know how to create a macOS app and have completed it before we proceed to the next step, creating the Widget Extension.
Create a Widget Extension Target
Targets in Xcode are settings that hold everything needed to build one piece of software - an app, a Widget, an Extension, a Bundle, a Driver, or a Library.
Each Xcode project can contain multiple Targets.
To add a Widget Extension Target to your Mac app, select File->New->Target from the File menu, and then select Widget Extension from the chooser that appears:
Click Next again, and fill in all the details for the Widget on the next pane. If your Xcode project has more than one Application Target already, choose which app to embed the Widget Extension in from the Embed in Application: popup menu at the bottom:
Click the Finish button. You may get an alert asking if you want to activate the Target Scheme for this Widget Extension. Click Activate.
Think of a Scheme in Xcode as a group of settings used for each build step during the build process. You can edit Schemes at any time from Xcode's main menu by selecting Product->Scheme->Edit Scheme... (Command-
Once you add the new Widget Extension Target to your app you'll notice Xcode added both a new Target under the "Targets" section in your project, as well as a new subfolder with some new files:
Apple's default Xcode Widget Extension code creates a simple Calendar Widget with a View (the UI) and three
Provider struct inherits from an Apple class
IntentTimelineProvider, which tells WidgetKit when to update the Widget's display. We'll get to the code in a minute.
Widgets can be user-configurable and the Widget needs to know how to display its data based on how the user has configured it. This is what the
Xcode creates all the default source files you need for your Widget Extension, creates and stores them in a new subfolder in your Project's folder on disk, and adds them to the Xcode project for you.
These files, roughly are:
- An Info.plist file which contains the Widget's settings
- An .entitlements file which controls what the Widget can do
- An Assets.xcassets Bundle which will contain the Widget's icon
- An .intentdefinition file for Siri and Shortcuts customizations
- A Swift file with an extension of .swift containing the Widget's code
Widget Swift code
In the at top of the .swift file the code imports three Apple frameworks:
- import WidgetKit
- import SwiftUI
- import Intents
The .swift file contains five template
structs or structures in Swift which define how the Widget operates:
Widget struct declares itself as the Widget's main entry point with the declaration
@main at the top.
The last struct,
PreviewProvider provides the SwiftUI preview so you can see what your Widget will look like right in Xcode. But in order for it to work, you'll need to modify the struct to provide info including a
WidgetPreviewContext and a
WidgetFamily enumeration (enum) to tell Xcode how to display the user interface.
Apple has documentation for both
WidgetPreviewContext and a
WidgetFamily on the developer site at Documentation/WidgetKit/WidgetPreviewContext and Documentation/WidgetKit/WidgetFamily, respectively.
You initialize a
WidgetPreviewContext with a
WidgetFamily, then pass it to the
PreviewProvider using the
SimpleEntry struct inherits from Apple's
TimelineEntry protocol, and stores a date/time and a
TimelineEntry is used to tell WidgetKit when to display a Widget.
Timelines are Apple structs that tell WidgetKit when to update the Widget's view.
Provider's three methods essentially create a
SimpleEntry containing a date and time, or in the case of the
getTimeline() method, an array of entries, fills them in with a date and time, and returns them in a
Timeline (which is also defined by Apple).
When the Widget's
@main runs, it defines a
kind and a
body which calls
IntentConfiguration(). That in turn calls the
View structs, which is what creates the timelines and views to display in the Widget:
All of this can seem confusing at first, but once you get the hang of it, it's easy. Essentially a Widget creates a view or a series of views and intents, along with timelines that tell WidgetKit when and how to display the Widget.
You could choose to store other data of simple or complex types in your Widget, but in this simple Calendar example, it simply stores the date and time for Widget display.
You may wonder how the actual view gets displayed in the Widget on-screen. The view is provided by the View struct, in this case
struct TestWidgetEntryView : View which simply creates a
Text view, storing the entry date with a "time" style.
Text view is simply an Apple-defined Swift view struct that displays a single line of text:
"A text view draws a string in your app's user interface using a body font that's appropriate for the current platform. You can choose a different standard font, like title or caption, using the font(_:) view modifier."
When the indicated date/time arrives, the system simply asks WidgetKit to provide the view to be displayed.
Now you're ready to build your macOS app and its Widget Extension. To do so, select Product->Archive from Xcode's main menu. This will start the compilation process.
If everything worked, Xcode will link both your Mac app and Widget Extension, then ask you how you want to distribute it:
Click Distribute App, then Copy App, then Next, then save the app to disk.
Xcode creates a new folder in the location you chose with a timestamp. Your new app is ready in the new folder:
Open the folder, Control-click the app in the Finder, and choose Show Package Contents from the popup menu. Navigate to the /Contents/Plugins folder and you'll see your app's Widget:
Now if you open WidgetKit Simulator again, you'll automatically see your app listed. Click it, then click the Widget on the right side, then click Open:
Click on the Timeline row and you'll see your Widget's UI displayed, with one view for each hour, over a five-hour timeline:
In this simple example, we looked at how Widgets work, how to create them in Xcode, and how to preview them in WidgetKit Simulator. In future articles, we'll explore creating Widgets in-depth with more complex examples.
Apple has a number of resources for learning how to create Widgets. But be sure to check out the three-part WWDC video series "Widgets Code-along" from 2020, as well as "Design Great Widgets" and the "Meet WidgetKit" video from WWDC 2018.
There is also a discussion of Widget UI in Apple's Human Interface Guidelines.