iOS 14 WidgetKit Development Guide: Creating, Configuring, and Communicating Widgets
This article provides a comprehensive tutorial on developing iOS 14 WidgetKit widgets, covering widget sizes, Xcode target creation, configuration options, essential SwiftUI components, timeline refresh mechanisms, communication between the main app and widget via App Groups and Keychain, as well as troubleshooting common issues.
iOS 14 introduced WidgetKit, allowing developers to add small, medium, and large widgets to the home screen or macOS Notification Center. Widgets can display up-to-date information and support deep‑link navigation to any part of the main app.
Widget sizes : three predefined families (systemSmall, systemMedium, systemLarge) with different dimensions for various device resolutions.
1. Creating a Widget target
In Xcode 12 (or later) choose File → New → Target → Widget Extension . Two configuration types are available:
StaticConfiguration : for widgets without user‑configurable properties.
IntentConfiguration : for widgets that expose configurable parameters via SiriKit intents.
The “Include Configuration Intent” checkbox determines which configuration Xcode uses.
2. Core Widget components
kind : a string identifier for the widget.
Provider : conforms to TimelineProvider and supplies a timeline of TimelineEntry objects.
Placeholder : a SwiftUI view shown before real data loads.
Content Closure : renders the widget UI using the entry supplied by the provider.
3. Required provider functions
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), model: LJWidgetModel.preview_widget)
} func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date(), model: LJWidgetModel.preview_widget)
completion(entry)
} func getTimeline(in context: Context, completion: @escaping (Timeline
) -> ()) {
let date = Calendar.current.date(byAdding: .hour, value: 12, to: Date()) ?? Date()
LJWidgetAPI.loadData { (model, error) in
guard let model = model else {
let timeline = Timeline(entries: [SimpleEntry(date: date, model: LJWidgetModel.preview_widget)], policy: .after(date))
completion(timeline)
return
}
let timeline = Timeline(entries: [SimpleEntry(date: date, model: model)], policy: .after(date))
completion(timeline)
}
}4. Data model
struct SimpleEntry: TimelineEntry {
let date: Date
let model: LJWidgetModel
}5. Widget view
struct LJWidgetEntryView: View {
var entry: Provider.Entry
@Environment(\.widgetFamily) var family
@ViewBuilder
var body: some View {
switch family {
case .systemSmall:
LJWidgetSmall(entry.model.small).previewLayout(.sizeThatFits)
case .systemMedium:
LJWidgetMedium(entry.model.medium).previewLayout(.sizeThatFits)
case .systemLarge:
LJWidgetLarge(entry.model.large).previewLayout(.sizeThatFits)
@unknown default:
Text("unknown")
}
}
}6. Widget declaration
struct LJWidget: Widget {
let kind = "LJWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
LJWidgetEntryView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
}
}7. Refresh mechanisms
Automatic refresh: the timeline defines future dates; when a date is reached WidgetKit requests a new timeline.
Manual refresh: push notifications or explicit calls to WidgetCenter.shared.reloadAllTimelines() from the main app.
8. Mixing Objective‑C and Swift
To call Objective‑C code from Swift, create a bridging header (e.g., Project‑Bridging‑Header.h ) and import the OC headers. To call Swift from Objective‑C, Xcode generates a Project‑Swift.h file automatically.
9. Communication between the main app and the widget
Because the widget runs in a separate process, shared data must be stored in a common container.
• App Group – configure the same App Group identifier for both targets, then read/write a file:
// Main app writes to shared file
NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.com.simon.app.test"];
NSURL *fileURL = [groupURL URLByAppendingPathComponent:@"appGroup.txt"];
[textField.text writeToURL:fileURL atomically:YES encoding:NSUTF8StringEncoding error:nil]; // Widget reads from shared file
NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.com.simon.app.test"];
NSURL *fileURL = [groupURL URLByAppendingPathComponent:@"appGroup.txt"];
NSString *str = [NSString stringWithContentsOfURL:fileURL encoding:NSUTF8StringEncoding error:nil];
self.shareLabel.text = str;• Keychain Sharing – store encrypted user data in the keychain and enable the same keychain access group for both targets.
10. Common issues and solutions
Intent configuration not found – deselect Intent when not needed.
Widget preview not showing in Xcode 12 beta – switch to the new build system.
Swift version mismatch – set Swift language version to 5.0.
AppGroup file missing – ensure the .entitlements file is added to both targets.
Multiple command errors – use Legacy Build System or remove duplicate Info.plist copies.
Library not loaded errors – verify WidgetKit framework is linked and the deployment target supports iOS 14.
References:
WidgetKit Documentation
SwiftUI Tutorial
Beike Product & Technology
As Beike's official product and technology account, we are committed to building a platform for sharing Beike's product and technology insights, targeting internet/O2O developers and product professionals. We share high-quality original articles, tech salon events, and recruitment information weekly. Welcome to follow us.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.