Highlight
Apple demonstrates the construction process of SwiftUI widget views with the Wired Caffeine widget: VStack/ZStack layout, dynamic time text, ContainerRelativeShape, placeholder and systemMedium conditional layout.
Core Content
This session starts with a very specific need. The Wired App records the caffeinated drinks a user has consumed and estimates how much caffeine is left in the body. The author wants to put this status on the Home Screen so that users can see the current caffeine level, the last drink, and how long it has been since the last drink at a glance.
The problem lies in the way the widget is displayed. It needs to continue the color and visual identity of the app, keep focus in the small size, and keep the time at the bottom updated. The rounded corners cannot be hard-coded casually, because the system will determine the shape, size and corner radius for the Widget.
The demo has a timeline configuration ready, so all focus is on the view layer. The SwiftUI view starts with VStack and gradually adds ZStack background, HStack alignment, Spacer to push content, default padding, asset catalog color, and color variants that automatically switch by Dark Mode.
When the view becomes more complex, Apple extracts the caffeine value at the top into CaffeineAmountView and the beverage information at the bottom into DrinkView. The purpose of this step is very practical: it allows the widget to continue to expand to the placeholder and systemMedium sizes, while the code remains clear.
Finally, the medium widget adds drink photos, and the placeholder directly reuses the same set of views. SwiftUI will replace text with rounded rectangles and images with fill colors. What developers have to deal with is information hierarchy and conditional layout, rather than rewriting a placeholder UI.
Detailed Content
1. Use preview and data structures to tighten the view boundaries (02:23)
The demo begins by putting widget configuration aside and only dealing with SwiftUI views. preview uses WidgetPreviewContext to specify systemSmall, and the view data comes from CaffeineWidgetData, which contains caffeine amount, drink name and drinking time.
WidgetPreviewContext
systemSmall
CaffeineWidgetData
VStack
ZStack
HStack
Spacer
Key points:
WidgetPreviewContextis used to render the preview according to the specified widget family.systemSmallis the first target size, suitable for running through the core information first.CaffeineWidgetDatacollects the data required by the UI. The preview can use fixed samples to make the interface look close to the real state.VStackis responsible for arranging caffeine titles, values, drink names and times up and down.ZStackadds a background color behind the content.HStackandSpacerpush the content to the leading edge to avoid losing information hierarchy when the widget content is centered.
2. Let the time text update automatically (05:16)
The “How long ago was your last drink” at the bottom cannot stay at the moment when the snapshot was generated. SwiftUI’s Text new initializer accepts date and style, and the system will automatically update the display content according to time changes. The demo also uses string interpolation plus ago, and the framework will generate localizable keys.
// Displaying date and time
// June 3, 2019
Text(event.startDate, style: .date)
// 11:23PM
Text(event.startDate, style: .time)
// 9:30AM - 3:30PM
Text(event.startDate...event.endDate)
// +2 hours
// -3 months
Text(event.startDate, style: .offset)
// 2 hours, 23 minutes – Automatically updating as time pass
Text(event.startDate, style: .relative)
// 36:59:01 – Automatically updating as time pass
Text(event.startDate, style: .timer)
Key points:
.dateand.timedisplay absolute dates and times in system format.- Date range can directly display a period of time, such as the start to the end of an event.
.offsetis suitable for displaying relative offsets..relativewill be updated as time passes, which corresponds to “how long ago was the last drink”..timeris suitable for countdown or timer type information.
3. Use ContainerRelativeShape to process concentric fillets (18:40)
The background of the caffeine value needs to maintain concentric rounded corners with the widget frame. Handwriting cornerRadius suffers from device differences, as different devices and configurations may use different widget rounding. iOS 14’s ContainerRelativeShape will read the nearest container shape and calculate appropriate fillets for the internal shape based on the current position.
// Concentric corner radius with ContainerRelativeShape
struct PillView : View {
var title: Text
var color: Color
var body: some View {
Text(title)
.background(ContainerRelativeShape().fill(color))
}
}
Key points:
PillViewextracts text with background into a reusable view.ContainerRelativeShape()uses the shape information provided by the parent container..fill(color)Fills this relative shape with the specified color..background(...)makes the background follow the text view while remaining concentric with the widget outline.- When the outer padding changes, the inner fillet also adjusts, and the border thickness remains consistent on the curve.
4. placeholder and size family share the same set of views (14:24)
The widget requires a placeholder because the system may use it when requesting the extension view or when the device is locked. Demonstrate adding isPlaceholder(true) to preview, SwiftUI automatically replaces the text content with a rounded rectangle that matches the original content. If some text must be retained, such as a fixed caffeine tag, you can add isPlaceholder(false) to that content.
.isPlaceholder(true)
.isPlaceholder(false)
systemMedium
Key points:
.isPlaceholder(true)puts the entire view into placeholder style..isPlaceholder(false)can retain a certain fixed text.systemMediumpreview is used to check the information density of medium-sized widgets.- In the medium layout, the demo is judged by the widget family condition, and the picture is only displayed when systemMedium has a photo.
- placeholder will also process the image and replace the image content with fill color.
Core Takeaways
1. Make a “current status” widget
What to do: Put the most frequently viewed number in the app into a small widget, such as caffeine level, budget balance, steps taken today, or device battery.
Why it’s worth doing: Wired’s example proves that as long as the small size captures a core state, it can reduce the number of times users open the app.
How ​​to start: First define a data structure containing only core values, names and times, and then use VStack and Spacer to make the first version of systemSmall preview.
2. Add relative time to recent events
What to do: Display dynamic text such as “How long ago was the last drink”, “How long ago was the last sync”, and “How long until the next event”.
Why it’s worth doing: Text(event.startDate, style: .relative) and .timer will update over time, suitable for keeping information on the Home Screen alive.
How ​​to start: Access the date style of Text from a real date field, first use .relative or .timer to verify the update effect in preview.
3. Give the brand color to the asset catalog
What to do: Use the same colors for the widgets as the app, and configure Dark Mode variants for those colors.
Why it’s worth doing: All the colors in the demo come from the asset catalog. After switching to Dark Mode, the widget automatically adapts without changing the layout code.
How ​​to start: Put the background color and text color used in the widget into the asset catalog, and then use preview to copy the Dark Mode state to check the contrast.
4. Add a second layer of information to the medium widget
What to do: small displays the core status, medium then adds pictures or supplementary information, such as a photo of a recent drink, a recent task cover, or an agenda location.
Why it’s worth doing: The demo only includes the drink photos in systemMedium to avoid the small widget being crowded with secondary content.
How ​​to start: First add the image name to the data structure, and then use the widget family condition to determine and control the image to only appear in the medium layout.
5. Directly reuse the formal view as placeholder
What to do: Keep the placeholder and the real widget in the same structure, and only hide the specific data.
Why it’s worth doing: isPlaceholder(true) will replace the text with a rounded rectangle and the image with a fill color. The skeleton that the user sees is consistent with the final layout.
How ​​to start: Add placeholder status to preview, and then use isPlaceholder(false) to retain fixed tags or brand words.
Related Sessions
- Meet WidgetKit: First understand the WidgetKit timeline and the mechanism by which Home Screen displays SwiftUI views at the appropriate time.
- Widgets Code-along, part 1: The adventure begins: Follow the starter project to create widgets from scratch, suitable as a hands-on exercise after 10033.
- Widgets Code-along, part 2: Alternate timelines: Continue to process the widget timeline and complete the data refresh process outside the view.
- Widgets Code-along, part 3: Advancing timelines: Continuation of code-along and further advancement of timeline-related implementation.
- Add configuration and intelligence to your widgets: Continue working with configuration and intelligence beyond the base widget view.
Comments
GitHub Issues · utterances