sample-app-guide
por facebook
Creación de una aplicación DAT completa con transmisión de cámara y captura de fotos
npx skills add https://github.com/facebook/meta-wearables-dat-ios --skill sample-app-guideSample App Guide (iOS)
Build an iOS DAT app with camera streaming and photo capture.
This walkthrough covers app setup, registration, streaming, and capture. Pair it with the CameraAccess sample.
Project setup
- Create a new Xcode project (SwiftUI App)
- Add the SDK via SPM:
https://github.com/facebook/meta-wearables-dat-ios - Add
MWDATCore,MWDATCamera, andMWDATMockDeviceto your target - Configure
Info.plist(see Getting Started)
App architecture
A typical DAT app has these components:
MyDATApp/
├── MyDATApp.swift # App entry point, SDK init
├── ViewModels/
│ ├── WearablesViewModel.swift # Registration, device management
│ └── StreamViewModel.swift # Streaming, photo capture
└── Views/
├── MainAppView.swift # Navigation
├── RegistrationView.swift # Registration UI
└── StreamView.swift # Video preview, capture button
SDK initialization
import MWDATCore
@main
struct MyDATApp: App {
init() {
do {
try Wearables.configure()
} catch {
assertionFailure("Wearables SDK configuration failed: \(error)")
}
}
var body: some Scene {
WindowGroup {
MainAppView()
.onOpenURL { url in
guard
let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
components.queryItems?.contains(where: { $0.name == "metaWearablesAction" }) == true
else {
return
}
Task {
_ = try? await Wearables.shared.handleUrl(url)
}
}
}
}
}
Wearables ViewModel
devicesStream() yields [DeviceIdentifier] (a [String]), not device objects.
Resolve each identifier with wearables.deviceForIdentifier(_:) when you need
nameOrId(), deviceType(), linkState, or compatibility().
import MWDATCore
@MainActor
class WearablesViewModel: ObservableObject {
@Published var registrationState: RegistrationState
@Published var devices: [DeviceIdentifier] = []
private let wearables = Wearables.shared
init() {
registrationState = Wearables.shared.registrationState
}
func observeState() {
Task {
for await state in wearables.registrationStateStream() {
self.registrationState = state
}
}
Task {
for await deviceIds in wearables.devicesStream() {
self.devices = deviceIds
}
}
}
func register() async {
try? await wearables.startRegistration()
}
func unregister() async {
try? await wearables.startUnregistration()
}
}
Stream ViewModel
import MWDATCamera
import MWDATCore
@MainActor
class StreamViewModel: ObservableObject {
@Published var currentFrame: UIImage?
@Published var streamState: String = "Stopped"
@Published var capturedPhoto: Data?
private let wearables = Wearables.shared
private var deviceSession: DeviceSession?
private var camera: Camera?
private var stream: Stream?
func startStream() async {
let config = StreamConfiguration(
videoCodec: .raw,
resolution: .medium,
frameRate: 24
)
let selector = AutoDeviceSelector(wearables: wearables)
do {
let deviceSession = try wearables.createSession(deviceSelector: selector)
try deviceSession.start()
// Wait for the device session to reach the started state
for await state in deviceSession.stateStream() {
if state == .started { break }
}
guard let camera = try deviceSession.addCamera(config: config) else { return }
self.deviceSession = deviceSession
self.camera = camera
self.stream = camera.stream
} catch {
return
}
guard let stream else { return }
_ = stream.statePublisher.listen { [weak self] state in
Task { @MainActor in
self?.streamState = "\(state)"
}
}
_ = stream.videoFramePublisher.listen { [weak self] frame in
guard let image = frame.makeUIImage() else { return }
Task { @MainActor in
self?.currentFrame = image
}
}
_ = stream.photoDataPublisher.listen { [weak self] photoData in
Task { @MainActor in
self?.capturedPhoto = photoData.data
}
}
stream.start()
}
func stopStream() {
// Stopping the camera cascades to its stream child
camera?.stop()
deviceSession?.stop()
stream = nil
camera = nil
deviceSession = nil
}
func capturePhoto() {
stream?.capturePhoto(format: .jpeg)
}
}
Behaviors the CameraAccess sample also covers
- Two explicit steps. "Start Session" creates and starts the
DeviceSession; "Preview" adds theCameraand starts the stream. Stopping the preview leaves the session connected so the user can start it again without re-creating the session. - Bind the UI to the SDK's own states. Mirror
DeviceSessionStateandStreamStateinstead of maintaining a parallel state machine, and show a busy indicator whilestarting,waitingForDevice, orstopping. - Background handling. On
UIApplication.didEnterBackgroundNotification, end the session. Suppress the teardown errors that the SDK emits during that intentional stop so the user does not see a false failure on return. - Update prompts. Watch
device.compatibility()withdevice.addCompatibilityListener; on.deviceUpdateRequired, offerWearables.shared.openFirmwareUpdate(). When session start reportsDeviceSessionError.datAppOnTheGlassesUpdateRequired, offerWearables.shared.openDATGlassesAppUpdate(). - Single error surface. Present
error.localizedDescriptionfromsession.errorPublisherandstream.errorPublisherin one alert rather than an app-maintained error map.
Testing with MockDeviceKit
Add mock device support to develop without glasses:
import MWDATMockDevice
func setupMockDevice() async {
let mockDeviceKit = MockDeviceKit.shared
mockDeviceKit.enable()
guard let device = try? mockDeviceKit.pairGlasses(model: .rayBanMeta) else { return }
device.powerOn()
device.unfold()
device.don()
if let videoURL = Bundle.main.url(forResource: "test_video", withExtension: "mp4") {
let camera = device.services.camera
camera.setCameraFeed(fileURL: videoURL)
}
}
func tearDownMockDevice() {
MockDeviceKit.shared.disable()
}
Allowed dependencies
Your DAT app should only depend on:
MWDATCore— always requiredMWDATCamera— for camera streamingMWDATMockDevice— for testing (can be test-only dependency)MWDATDisplay— for Display experiences