You are currently viewing How to Create an iOS Lock Screen Widget?

How to Create an iOS Lock Screen Widget?

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:

Static Home Screen Widget example in iOS
The View Size Widget

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.

The new widget families for Lock Screen widgets in iOS 16
The new widget families for Lock Screen widgets

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.

Supporting new form factors for Lock Screen widgets in iOS 16
View Size Widget in new form factors

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:

Lock Screen widgets in iOS 16
The View Size Lock Screen Widget

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.

iOS Lock Screen widget with truncated content
Lock Screen widget with truncated content

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.

Fixing truncated content in iOS Lock Screen widget
Fixing truncated content in Lock Screen widget

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.