Widgets & App Extensions
Widgets & App Extensions
Share small data with App Groups and build Widgets using WidgetKit to show quick glanceable information.
Widget timeline
Build a widget that shows a timeline of time entries.
Syntax:
TimelineProviderprotocol to define the timeline provider
Example
import WidgetKit
import SwiftUI
struct TimeEntry: TimelineEntry { let date: Date }
struct TimeProvider: TimelineProvider {
func placeholder(in context: Context) -> TimeEntry { .init(date: Date()) }
func getSnapshot(in context: Context, completion: @escaping (TimeEntry) -> Void) { completion(.init(date: Date())) }
func getTimeline(in context: Context, completion: @escaping (Timeline<TimeEntry>) -> Void) {
let entries = stride(from: 0, through: 60*30, by: 60).map { offset in
TimeEntry(date: Date().addingTimeInterval(Double(offset)))
}
completion(Timeline(entries: entries, policy: .atEnd))
}
}
struct TimeWidgetView: View {
var entry: TimeEntry
var body: some View { Text(entry.date, style: .time).font(.headline) }
}
@main
struct TimeWidget: Widget {
var body: some WidgetConfiguration {
StaticConfiguration(kind: "TimeWidget", provider: TimeProvider()) { e in TimeWidgetView(entry: e) }
}
}
import SwiftUI
struct ContentView: View { var body: some View { Text("Widget timeline demo") } }
import SwiftUI
@main
struct MyApp: App { var body: some Scene { WindowGroup { ContentView() } } }
The example above shows a simple widget timeline with a placeholder, snapshot, and timeline provider.
Share extension skeleton
Build a share extension that allows users to share content with your app.
Syntax:
ShareExtensionSkeletonstruct to define the share extension skeleton
Example
import SwiftUI
struct ShareExtensionSkeleton: View {
@State private var sharedText = ""
var body: some View {
VStack(spacing: 12) {
Text("Incoming content")
TextEditor(text: $sharedText).frame(minHeight: 120).border(.secondary)
HStack { Spacer(); Button("Post") { /* handle share */ } }
}
.padding()
}
}
import SwiftUI
struct ContentView: View { var body: some View { ShareExtensionSkeleton() } }
import SwiftUI
struct MyApp: App { var body: some Scene { WindowGroup { ContentView() } } }
The example above shows a simple share extension skeleton with a text editor and a post button.
App Groups (Sharing Data with Widget)
Enable App Groups for both the app and the widget extension, and use a shared suite to exchange small data.
Syntax: UserDefaults(suiteName: "group.id"), write with set(_:forKey:), read with integer(forKey:).
Checklist
- Add capability App Groups to App target and Widget target
- Create a group identifier, e.g.
group.com.example.notes
import Foundation
func updateSharedNotesCount(_ count: Int) {
let defaults = UserDefaults(suiteName: "group.com.example.notes")
defaults?.set(count, forKey: "notesCount")
}
import WidgetKit
import SwiftUI
struct CountEntry: TimelineEntry { let date: Date; let count: Int }
struct NotesProvider: TimelineProvider {
func placeholder(in context: Context) -> CountEntry { .init(date: Date(), count: 0) }
func getSnapshot(in context: Context, completion: @escaping (CountEntry) -> Void) {
let defaults = UserDefaults(suiteName: "group.com.example.notes")
let count = defaults?.integer(forKey: "notesCount") ?? 0
completion(.init(date: Date(), count: count))
}
func getTimeline(in context: Context, completion: @escaping (Timeline<CountEntry>) -> Void) {
let defaults = UserDefaults(suiteName: "group.com.example.notes")
let count = defaults?.integer(forKey: "notesCount") ?? 0
let entries = [CountEntry(date: Date(), count: count)]
completion(Timeline(entries: entries, policy: .after(Date().addingTimeInterval(1800))))
}
}
This example shares a notes count via App Groups: the app writes the value and the widget reads it to display.
Widgets
Create a Widget Extension target in Xcode, then design timelines/views using WidgetKit and SwiftUI.
Syntax: implement TimelineProvider, define a Widget, and return a StaticConfiguration(kind:provider:content:).
Example
import WidgetKit
import SwiftUI
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> SimpleEntry { .init(date: Date()) }
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) { completion(.init(date: Date())) }
func getTimeline(in context: Context, completion: @escaping (Timeline<SimpleEntry>) -> ()) {
completion(Timeline(entries: [.init(date: Date())], policy: .atEnd))
}
}
struct SimpleEntry: TimelineEntry { let date: Date }
struct MyWidgetEntryView: View {
var entry: SimpleEntry
var body: some View { Text(entry.date, style: .time) }
}
@main
struct MyWidget: Widget {
var body: some WidgetConfiguration {
StaticConfiguration(kind: "MyWidget", provider: Provider()) { entry in
MyWidgetEntryView(entry: entry)
}
}
}
}
The example above shows a simple widget with a timeline provider and a static configuration.
Deep Links from Widgets
Open your app to a specific screen when the widget is tapped by providing a URL.
Syntax: implement Link and onOpenURL.
Example
import SwiftUI
struct NotesCountView: View {
var body: some View {
Link(destination: URL(string: "myapp://notes")!) {
Text("Notes: \(entry.count)")
}
}
}
import SwiftUI
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL { url in
// Route based on url.host / path (e.g., "notes")
}
}
}
}
The example above shows a simple widget with a link to a specific screen and an app that handles the URL.
Refresh & Timeline Policy
Choose a refresh strategy and trigger reloads when data changes.
Syntax: implement TimelineProvider and WidgetCenter.
Example
import WidgetKit
// Update at a future date
completion(Timeline(entries: entries, policy: .after(Date().addingTimeInterval(1800))))
// Or end when entries are exhausted
// completion(Timeline(entries: entries, policy: .atEnd))
import WidgetKit
// Refresh a specific widget kind after writing new data to App Group
WidgetCenter.shared.reloadTimelines(ofKind: "NotesCount")
// Or refresh all widget kinds
// WidgetCenter.shared.reloadAllTimelines()
The example above shows a simple widget with a timeline provider and a static configuration.
Supported Families
Advertise which sizes your widget supports.
Syntax: implement WidgetConfiguration and supportedFamilies.
Example
import WidgetKit
@main
struct NotesCountWidget: Widget {
var body: some WidgetConfiguration {
StaticConfiguration(kind: "NotesCount", provider: NotesProvider()) { entry in
NotesCountView(entry: entry)
}
.supportedFamilies([.systemSmall, .systemMedium])
}
}
The example above shows a simple widget with a timeline provider and a static configuration.
Testing Widgets
Syntax: implement WidgetPreviewProvider and WidgetCenter.
- Use Widget preview providers and test on device by adding the widget to the Home/Lock screen.
- Toggle App Group values and call
WidgetCenter.shared.reloadTimelinesto validate refresh. - Check different families (small/medium/large) and dark/light mode.