Info.plist file.<key>MWDAT</key> <dict> ... <key>DAMEnabled</key> <true/> </dict>
import MWDATCore import MWDATDisplay
// Configure the SDK
try Wearables.configure()
// Create a session targeting display-capable glasses
let wearables = Wearables.shared
let session = try wearables.createSession()
// Start the session and wait for it to connect. Then observe session.stateStream() for .started
try session.start()
// Attach the display capability
let display = try session.addDisplay()
// Start the display and observe state. Then observe display.statePublisher for .started
await display.start()
// Send content once the display reaches .started
let view = FlexBox(direction: .column, spacing: 12) {
Text("Hello, glasses!", style: .heading)
}
try await display.send(view)
// Using async stream
for await state in display.statePublisher.stream {
switch state {
case .started:
// Display is ready, send content
case .stopped:
// Display disconnected or stopped
case .starting, .stopping:
// Transitional states
}
}
await display.stop() session.stop()
send() replaces the entire display, so there are no incremental updates. Views are presented one at a time with vertical scrolling. Horizontal scrolling is not supported.FlexBox(
direction: .column, // .column, .row, .columnReverse, .rowReverse
spacing: 12, // Gap between children in dp
alignment: .start, // Main axis: .start, .center, .end, .stretch
crossAlignment: .center, // Cross axis: .start, .center, .end, .stretch
wrap: false, // Whether children wrap to the next line
padding: EdgeInsets(all: 16)
) {
// Child components go here
}
TextStyle | Description |
|---|---|
heading | Large, bold text for section titles |
body | Standard text for general content |
meta | Small text for captions and metadata |
TextColor | Description |
|---|---|
primary | Default high-contrast text |
secondary | Lower-contrast supporting text |
Text("Welcome", style: .heading)
Text("Tap a button to continue", style: .body, color: .secondary)
Text("Step 1 of 5", style: .meta, color: .secondary)
ImageSize | Description |
|---|---|
icon | Small, inline-sized image |
fill | Fills the available space |
CornerRadius | Description |
|---|---|
none | Sharp corners |
small | Slightly rounded |
medium | Moderately rounded |
Image(
uri: "https://example.com/photo.png",
sizePreset: .fill,
cornerRadius: .medium
)
ButtonStyle | Description |
|---|---|
primary | High-emphasis filled button |
secondary | Medium-emphasis filled button |
outline | Low-emphasis outlined button |
Button(label: "Continue", style: .primary, iconName: .arrowRight) {
// Handle tap
}
Button(label: "Go back", style: .outline, iconName: .arrowLeft) {
// Handle tap
}
IconStyle | Description |
|---|---|
filled | Solid icon |
outline | Hollow icon |
Icon(name: .checkmarkCircle, style: .filled) Icon(name: .bell, style: .outline)
IconName values.Flex modifiers (flexGrow, flexShrink, alignSelf) only apply to FlexBox — wrap a non-FlexBox child in a FlexBox to opt into flex layout behavior.// Padding and tap handlers apply to any component.
Text("Tappable text", style: .body)
.padding(16) // Uniform padding
.onTap {
// Handle tap on this component
}
// Per-edge padding
Image(uri: imageUrl, sizePreset: .fill)
.padding(EdgeInsets(top: 8, bottom: 8, leading: 16, trailing: 16))
// Flex modifiers only apply to a FlexBox.
// Wrap a non-FlexBox child in a FlexBox to apply flexGrow, flexShrink, or alignSelf.
FlexBox {
Text("Flexible text", style: .body)
}
.flexGrow(1) // Grow to fill available space
.flexShrink(0) // Don't shrink below natural size
.alignSelf(.center) // Override cross-axis alignment
direction to determine the main axis along which children are arranged. This code sample illustrates a vertical layout with a header, body text, and action button.let view = FlexBox(direction: .column, spacing: 12, padding: EdgeInsets(all: 16)) {
Text("Oil Change Guide", style: .heading)
Text("Learn how to change your car's oil in 6 easy steps.", style: .body)
Text("Duration: 30 min", style: .meta, color: .secondary)
Button(label: "Start", style: .primary, iconName: .arrowRight) {
showFirstStep()
}
}
try await display.send(view)
let view = FlexBox(direction: .row, spacing: 12, crossAlignment: .center,
padding: EdgeInsets(all: 16)) {
FlexBox {
Image(uri: thumbnailUrl, sizePreset: .fill, cornerRadius: .medium)
}
.flexGrow(1)
FlexBox(direction: .column, spacing: 4) {
Text("Brake Pad Replacement", style: .body)
Text("45 min", style: .meta, color: .secondary)
}
.flexGrow(3)
}
try await display.send(view)
let view = FlexBox(direction: .column, spacing: 8, padding: EdgeInsets(all: 16)) {
Text("Tutorials", style: .heading)
for tutorial in tutorials {
FlexBox(direction: .row, spacing: 12, crossAlignment: .center) {
Icon(name: .gear, style: .filled)
Text(tutorial.title, style: .body)
}
.padding(12)
.onTap { selectTutorial(tutorial) }
}
}
try await display.send(view)
let view = FlexBox(direction: .column, spacing: 12, padding: EdgeInsets(all: 16)) {
Text("Step \(stepIndex + 1) of \(totalSteps)", style: .meta, color: .secondary)
Text(step.title, style: .heading)
Text(step.description, style: .body)
Image(uri: step.imageUrl, sizePreset: .fill, cornerRadius: .medium)
FlexBox(direction: .row, spacing: 8, alignment: .center) {
Button(label: "Back", style: .outline, iconName: .arrowLeft) {
showPreviousStep()
}
Button(label: "Next", style: .primary, iconName: .arrowRight) {
showNextStep()
}
}
}
try await display.send(view)
Button(label: "Accept", style: .primary) {
handleAccept()
}
FlexBox(direction: .row, spacing: 8) {
Icon(name: .heart, style: .filled)
Text("Favorites", style: .body)
}
// Tap handler
.onTap { openFavorites() }
https onlyVideoPlayer.// Start video playback
let video = VideoPlayer(
provider: .uri("https://example.com/tutorial-clip.mp4"),
codec: .mp4,
onError: { error in
print("Video error: \(error)")
}
)
try await display.send(video)
// Stop video playback
await display.sendVideoStop()
display.onPlaybackEvent = { event in
switch event.type {
case .started:
print("Video started playing")
case .ended:
print("Video finished")
case .error:
print("Video error: \(event.errorType)")
case .stopped:
print("Video was stopped")
case .unknown:
break
}
}
DisplayError identifies errors for display operations on Meta AI glasses.do {
try await display.send(view)
} catch let error as DisplayError {
switch error {
case .deviceDisconnected:
// The glasses disconnected — prompt user to reconnect
showReconnectPrompt()
case .connectionNotAvailable:
// No connection available to the glasses
showConnectionError()
case .deviceNotFound:
// The target device was not found
showDeviceNotFoundError()
case .invalidVideoURL:
// The video URL is blank or uses an unsupported scheme
showInvalidUrlError()
case .displayError(let message):
// The glasses reported an error (e.g., capability not active)
print("Display error: \(message)")
}
}
do {
let display = try session.addDisplay()
} catch let error as DeviceSessionError {
switch error {
case .sessionIdle:
// Session hasn't been started yet — call session.start() first
case .sessionAlreadyStopped:
// Session was already stopped — create a new session
case .capabilityAlreadyActive:
// A display capability is already attached to this session
case .unexpectedError:
// SDK initialization issue
}
}
VideoError outlines errors for video playback on Meta AI glasses.let video = VideoPlayer(
provider: .uri(videoUrl),
codec: .mp4,
onError: { error in
switch error {
case .playbackFailed(let errorType):
switch errorType {
case .urlInvalid:
print("Invalid video URL")
case .alreadyPlaying:
print("Another video is already playing")
case .playbackFailed:
print("Playback failed on the glasses")
case .unknown:
print("Unknown video error")
}
}
}
)