Integrate Display capability into your iOS app

Updated: May 18, 2026

Overview

This guide explains how to integrate display functionality into your existing iOS app. For a complete guide on adding Wearables Device Access Toolkit capabilities to an existing iOS app, including registration, streaming, and photo capture, check out the iOS integration guide.

Create a visual display experience for your app

Step 1: Configure Info.plist

Enable DAT App Model (DAM) in your Info.plist file.
<key>MWDAT</key>
<dict>
  ...
  <key>DAMEnabled</key>
  <true/>
</dict>

Step 2: Import modules

import MWDATCore
import MWDATDisplay

Step 3: Connect to the display

Connect to the display by configuring the SDK, creating a session, and then initializing it.
// 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)

Step 4: Observe state changes

// 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
    }
}

Step 5: Stop display

When your app is done using the display capabilities, clean up resources:
await display.stop()
session.stop()
Note: The glasses display dims after 20 seconds of inactivity and enters sleep mode at 25 seconds. Display sleep does not end the Wearables Device Access Toolkit session. When the display wakes, your app can either show the previously displayed content or send a new view.

Build content

Wearables Device Access Toolkit uses a declarative component system to build UI layouts for display, so you compose views using a result builder in Swift.
Each call to 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

FlexBox is the primary layout container for Meta Ray-Ban Display apps. It helps you build declarative UI trees with text, images, icons, and buttons. This means that every layout starts with a root FlexBox. Inside, you arrange either these child components or nested FlexBoxes using specific layout properties.
FlexBox arranges children along a main axis (row or column) with control over spacing, alignment, and wrapping.
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
}

Text

Displays styled text. Three style presets match the glasses design system.
TextStyleDescription
heading
Large, bold text for section titles
body
Standard text for general content
meta
Small text for captions and metadata
TextColorDescription
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)

Images

Display images loaded from URLs.
ImageSizeDescription
icon
Small, inline-sized image
fill
Fills the available space
CornerRadiusDescription
none
Sharp corners
small
Slightly rounded
medium
Moderately rounded
Image(
    uri: "https://example.com/photo.png",
    sizePreset: .fill,
    cornerRadius: .medium
)
Note: The Meta Ray-Ban Display glasses render at 600x600 resolution. Use appropriately sized images — there is no benefit to sending larger images, and oversized assets introduce lag due to Bluetooth bandwidth constraints.

Buttons

Implement tappable buttons with corresponding labels, optional icons, and click handlers.
ButtonStyleDescription
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
}

Icons

Display a system icon from a built-in catalog of 100+ glyphs, covering navigation, media, weather, social, and more.
IconStyleDescription
filled
Solid icon
outline
Hollow icon
Icon(name: .checkmarkCircle, style: .filled)
Icon(name: .bell, style: .outline)
See the icon catalog for all available IconName values.
If you need an icon outside the built-in catalog, you can also provide your own custom image.

View Modifiers

On iOS, padding and tap handlers can be applied to any component. 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

Layouts

Column layout

Use 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)

Row layout

This code sample illustrates a horizontal layout with an image thumbnail and text.
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)

Clickable list items

Create a list of tappable items, each with its own icon and label.
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)
Create a step-by-step view with navigation buttons located at the bottom of the viewport.
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)

User inputs

Users can interact with display content through captouch. These interactions are abstracted as tap events and delivered to your app through callbacks.

Button clicks

Buttons receive tap events through a handler.
Button(label: "Accept", style: .primary) {
    handleAccept()
}

Tappable areas

Make any component tappable by wrapping it in a tap handler.
FlexBox(direction: .row, spacing: 8) {
    Icon(name: .heart, style: .filled)
    Text("Favorites", style: .body)
}
// Tap handler
.onTap { openFavorites() }

Back gesture

The back gesture (a two-finger tap on the glasses temple) ends the display session.

Video

Video playback allows you to play video content from a URL on the full-screen video player of Meta Ray-Ban Display glasses.

Constraints

  • Format: MP4 only
  • Maximum dimensions: 400 pixels per side, 70,000 total pixels
  • URL scheme:https only
  • Concurrency: One video at a time per display session

Play video

Play video using VideoPlayer.
// 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()

Observe video events

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
    }
}

Error handling

Display operations may fail. Handle errors gracefully to provide a better experience for your users.

Display errors

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)")
    }
}

Session-level errors when adding a display

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
    }
}

Video errors

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")
            }
        }
    }
)