In iOS 16, Apple has given the Lock Screen a massive overhaul. One of the most anticipated features that come along with the overhaul is the Lock Screen widgets. As the name implies, Lock Screen widgets are widgets that display glanceable content that is constantly visible on the iPhone and iPad Lock Screen.
Since both the Home Screen and Lock Screen widgets are powered by WidgetKit, the way of creating a Lock Screen widget is very similar to how we create a Home Screen widget. Therefore, in this article, I won’t be showing you how to set up and create a widget from scratch as that has been covered in my previous article.
Instead, I will be focusing on showing you how to update your existing Home Screen widgets’ code to support Lock Screen widgets.
With all that being said, let’s get started!
A Quick Recap
For demonstration purposes, let’s update the View Size Widget that I created in my previous article. As a quick recap, the View Size Widget is a static Home Screen widget that displays the widget’s view size. Here’s how it looks:
Here’s the full implementation of the View Size Widget:
import WidgetKit
import SwiftUI
// MARK: - The Timeline Entry
struct ViewSizeEntry: TimelineEntry {
let date: Date
let providerInfo: String
}
// MARK: - The Widget View
struct ViewSizeWidgetView : View {
let entry: ViewSizeEntry
var body: some View {
GeometryReader { geometry in
VStack {
// Show view size
Text("\(Int(geometry.size.width)) x \(Int(geometry.size.height))")
.font(.system(.title2, weight: .bold))
// Show provider info
Text(entry.providerInfo)
.font(.footnote)
}
.containerBackground(for: .widget) {
Color.green
}
}
}
}
// MARK: - The Timeline Provider
struct ViewSizeTimelineProvider: TimelineProvider {
typealias Entry = ViewSizeEntry
func placeholder(in context: Context) -> Entry {
// This data will be masked
return ViewSizeEntry(date: Date(), providerInfo: "placeholder")
}
func getSnapshot(in context: Context, completion: @escaping (Entry) -> ()) {
let entry = ViewSizeEntry(date: Date(), providerInfo: "snapshot")
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
let entry = ViewSizeEntry(date: Date(), providerInfo: "timeline")
let timeline = Timeline(entries: [entry], policy: .never)
completion(timeline)
}
}
// MARK: - The Widget Configuration
@main
struct ViewSizeWidget: Widget {
var body: some WidgetConfiguration {
StaticConfiguration(
kind: "com.SwiftSenpaiDemo.ViewSizeWidget",
provider: ViewSizeTimelineProvider()
) { entry in
ViewSizeWidgetView(entry: entry)
}
.configurationDisplayName("View Size Widget")
.description("This is a demo widget.")
.supportedFamilies([
.systemSmall,
.systemMedium,
.systemLarge,
])
}
}
If the code above does not make any sense to you, I highly encourage you to first read my article called “Getting Started With WidgetKit” before proceeding.
Adding Lock Screen Widgets to Your Apps
Adding Supported Widget Families
In iOS 16, Apple introduced 3 new widget families that represent 3 different kinds of Lock Screen widgets, namely accessoryCircular
, accessoryRectangular
and accessoryInline
.
Let’s go ahead and make these 3 new widget families the supported families during widget configuration. That’s all we need to do to add Lock Screen widget support to our existing widget extension.
struct ViewSizeWidget: Widget {
var body: some WidgetConfiguration {
StaticConfiguration(
kind: "com.SwiftSenpaiDemo.ViewSizeWidget",
provider: ViewSizeTimelineProvider()
) { entry in
ViewSizeWidgetView(entry: entry)
}
.configurationDisplayName("View Size Widget")
.description("This is a demo widget.")
.supportedFamilies([
.systemSmall,
.systemMedium,
.systemLarge,
// Add Support to Lock Screen widgets
.accessoryCircular,
.accessoryRectangular,
.accessoryInline,
])
}
}
However, if you try to show the widget on the lock screen, you will notice that our existing widget UI does not look good in these new form factors. Moreover, it is not even possible to get the view size for accessoryInline
family.
To get all this sorted out, we will have to create 3 separate SwiftUI views for each of these widget families.
Implementing the Lock Screen Widget UIs
Let’s say the desired Lock Screen widget UIs are as follow:
We can implement each of them like so:
/// Widget view for `accessoryInline `
struct InlineWidgetView: View {
var body: some View {
Text("🤷🏻♂️ View size not available 🤷🏻♀️")
}
}
/// Widget view for `accessoryRectangular`
struct RectangularWidgetView: View {
var body: some View {
GeometryReader { geometry in
ZStack {
AccessoryWidgetBackground()
.cornerRadius(8)
GeometryReader { geometry in
Text("\(Int(geometry.size.width)) x \(Int(geometry.size.height))")
.font(.headline)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
.containerBackground(for: .widget) { }
}
}
}
/// Widget view for `accessoryCircular`
struct CircularWidgetView: View {
var body: some View {
GeometryReader { geometry in
ZStack {
AccessoryWidgetBackground()
VStack {
Text("W: \(Int(geometry.size.width))")
.font(.headline)
Text("H: \(Int(geometry.size.height))")
.font(.headline)
}
}
.containerBackground(for: .widget) { }
}
}
}
Do note that I am using AccessoryWidgetBackground()
as the background view for RectangularWidgetView
and CircularWidgetView
. It is a SwiftUI view with a standard appearance. We can place it in a ZStack
behind the widget’s content to create Lock Screen widgets with a non-transparent background.
Note:
If you are creating a separate SwiftUI file for each widget view, make sure to assign them to the widget extension target. On top of that, be sure to import the WidgetKit module if you are using the
AccessoryWidgetBackground()
as background.
With all these SwiftUI views in place, we can head back to the ViewSizeWidget
implementation and update its view accordingly:
struct ViewSizeWidgetView: View {
let entry: ViewSizeEntry
// Obtain the widget family value
@Environment(\.widgetFamily)
var family
var body: some View {
switch family {
case .accessoryRectangular:
RectangularWidgetView()
case .accessoryCircular:
CircularWidgetView()
case .accessoryInline:
InlineWidgetView()
default:
// UI for Home Screen widget
HomeScreenWidgetView(entry: entry)
}
}
}
From the above code, notice how we obtain the widgetFamily
environment value and use it to conditionally return the right SwiftUI view for each widget family.
Handling Content Truncation
At this stage, everything looks fine when running on large devices such as an iPhone 14 Pro Max. However, if we switch over to devices with a smaller screen size such as iPhone 14, you will notice that the content in the inline widget is truncated.
To go about that, we can use ViewThatFits
to supply another smaller view when the bigger view gets truncated. Here’s how:
struct InlineWidgetView: View {
var body: some View {
ViewThatFits {
// Provide 2 subviews for `ViewThatFits` evaluation
// Prioritizing from top to bottom
Text("🤷🏻♂️ View size not available 🤷🏻♀️")
Text("🤷🏻♂️ Nope! 🤷🏻♀️")
}
}
}
ViewThatFits
acts as a view that will evaluate its subview in the top to bottom order and return the first one that best fits the current context. This means that in most cases, we will want to order the largest subview at the top, followed by other alternative views that are smaller.
Here’s a side-by-side comparison between iPhone 14 and iPhone 14 Pro Max.
With that, we have successfully updated our existing Home Screen widgets’ code to support Lock Screen widgets. Bravo!
Feel free to get the full sample code here.
There you have it! Creating a Lock Screen widget is actually pretty straightforward.
Do you find this article helpful? If you do, feel free to check out my other articles related to iOS development here. Please do follow me on Twitter and subscribe to my newsletter so that you won’t miss out on any of my upcoming articles
Thanks for reading. 👨🏻💻
👋🏻 Hey!
While you’re still here, why not check out some of my favorite Mac tools on Setapp? They will definitely help improve your day-to-day productivity. Additionally, doing so will also help support my work.
- ✨ Bartender: Superpower your menu bar and take full control over your menu bar items.
- ✨ CleanShot X: The best screen capture app I’ve ever used.
- ✨ PixelSnap: Measure on-screen elements with ease and precision.
- ✨ iStat Menus: Track CPU, GPU, sensors, and more, all in one convenient tool.