Widgets – Adding content and options for configuration
Layout some content and add parameters.
Overview
We already learned the basics of creating widgets. We also learned a handful of key concepts that make widgets special on visionOS. Let’s add some content and learn how to add options.
The template we started from had a parameter to pass in an emoji.
struct ConfigurationAppIntent: WidgetConfigurationIntent {
...
@Parameter(title: "Emoji", default: "🌸")
var emoji: String
}When a user opens the configuration menu on the widget, this parameter is presented as a text field. They can use the keyboard to select any emoji they want. We’ll build on this. Let’s mock up a quick view to repeat this emoji in a grid. We’ll offset every other row to get a staggered pattern.
struct EmojiBackground: View {
var emoji: String
var body: some View {
GeometryReader { geometry in
let emojiSize: CGFloat = 24
let spacing: CGFloat = 4
let patternWidth = geometry.size.width * 3
let patternHeight = geometry.size.height * 3
let rows = Int(patternHeight / (emojiSize + spacing))
let cols = Int(patternWidth / (emojiSize + spacing))
VStack(spacing: spacing) {
ForEach(0..<rows, id: \.self) { row in
HStack(spacing: spacing) {
ForEach(0..<cols, id: \.self) { col in
Text(emoji)
.font(.system(size: emojiSize))
}
}
.offset(x: row % 2 == 0 ? 0 : emojiSize / 2 + spacing / 2)
}
}
.frame(width: patternWidth, height: patternHeight)
.offset(x: -patternWidth / 3, y: -patternHeight / 3)
.clipped()
}
}
}When we use this we can set the position to start just slightly off screen from the top left. We can also use opacity to fade the images out just a bit. I used 0.3 for .simplified LOD and 0.6 for default.
EmojiBackground(emoji: entry.configuration.emoji)
.position(x: -50, y: -50)
.opacity(0.3)What if we want users to pick a value from a list? We’ll need to create a custom type DisplayRepresentation. Let’s make one with three cringy values.
enum DisplayOption: String, AppEnum {
case live = "Live"
case laugh = "Laugh"
case love = "Love"
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Title Options"
static var caseDisplayRepresentations: [Self: DisplayRepresentation] = [
.live: DisplayRepresentation(title: "Live"),
.laugh: DisplayRepresentation(title: "Laugh"),
.love: DisplayRepresentation(title: "Love")
]
}Then use in a parameter.
struct ConfigurationAppIntent: WidgetConfigurationIntent {
...
@Parameter(title: "Title", default: .live)
var display: DisplayOption
...
}That gives us a basic UI for a picker.

We’ll show the raw value in a Text View. We’ll also adjust the layout based on which value is selected.
struct DisplayLayout: View {
let display: DisplayOption
let text: String
var body: some View {
GeometryReader { geometry in
switch display {
case .live:
VStack {
HStack {
DisplayTitle(text: text)
Spacer()
}
Spacer()
}
case .laugh:
VStack {
Spacer()
DisplayTitle(text: text)
Spacer()
}
case .love:
VStack {
Spacer()
HStack {
Spacer()
DisplayTitle(text: text)
}
}
}
}
}
}With all the pieces put together we are left with this.
Support our work so we can continue to bring you new examples and articles.

Follow Step Into Vision