← Overview
oe-swift.json

iOS & SwiftUI

Configure OpenEditor to build, preview, and live-reload your iOS and SwiftUI projects directly on the visual canvas using the iOS Simulator.

Quick Start

Create an oe-swift.json file in your Xcode project root (next to .xcodeproj):

oe-swift.json
{
  "project": "MyApp.xcodeproj",
  "scheme": "MyApp",
  "simulator": "iPhone 16 Pro",
  "bundleId": "com.example.myapp",
  "launchArgs": ["-hasCompletedOnboarding", "YES"],
  "views": [
    { "name": "WelcomeView", "file": "MyApp/Onboarding/WelcomeView.swift", "requiresLogout": true, "launchArgs": [], "verifyLabels": ["Welcome"] },
    { "name": "HomeView", "file": "MyApp/Views/HomeView.swift", "tapLabel": "Home", "verifyLabels": ["Your Home"] }
  ]
}

OpenEditor will auto-detect your project, build it, install on any booted simulators, and capture screenshots for each view on the canvas.

Project Configuration

Field Type Description
project Optional string Path to .xcodeproj or .xcworkspace, relative to the config file. Auto-detected if omitted.
scheme Optional string Xcode build scheme. Defaults to the project name.
simulator Optional string Default simulator device name. Default: iPhone 16 Pro.
bundleId Optional string App bundle identifier. Auto-detected from the built .app if omitted.
launchArgs Optional array Launch arguments passed every time the app is started for captures. Useful when one project should boot into the logged-in app while specific onboarding screens override the launch args per view.
urlScheme Optional string URL scheme for deep link navigation (e.g. myapp). Enables per-view screenshots via xcrun simctl openurl.
views Optional array List of SwiftUI views to display on the canvas. If omitted, views are auto-discovered by scanning .swift files for struct ... : View.

View Configuration

Each entry in the views array describes a SwiftUI view to preview:

Field Type Description
name Required string The SwiftUI struct name (e.g. ContentView, ProfileView).
file Optional string Relative path to the .swift file from the project root.
label Optional string Display label on the canvas. Defaults to the struct name with "View" stripped.
device Optional string Override the simulator device for this view (e.g. iPad Air 11-inch (M3)).
deepLink Optional string Deep link URL to navigate to this view before capturing (e.g. myapp://profile).
tapLabel Optional string Accessibility label to tap to navigate to this view (e.g. a tab bar label like Profile).
tapId Optional string Accessibility identifier to tap to navigate to this view.
tapXY Optional object Tap coordinates in points {"x": 120, "y": 832} to navigate (for tab bars not exposed via accessibility in iOS 18+).
requiresLogout Optional boolean Marks screens that should be captured before demo/login bootstrap, such as welcome and onboarding flows.
launchArgs Optional array Overrides project-level launch arguments for this screen. Use an empty array [] to clear the project defaults for onboarding or logged-out captures.
verifyLabels Optional array Accessibility labels that confirm the app really reached this screen before a capture is accepted.

Per-View Screenshots

OpenEditor can capture a unique screenshot for each view by navigating the app in the simulator before taking the screenshot. Three strategies are available, tried in order:

Deep Links

If your app registers a URL scheme, add urlScheme at the project level and deepLink per view. The engine uses xcrun simctl openurl to navigate.

Deep link navigation
{
  "urlScheme": "myapp",
  "views": [
    { "name": "HomeView", "deepLink": "myapp://home" },
    { "name": "ProfileView", "deepLink": "myapp://profile" }
  ]
}

Accessibility Tap

For apps with a TabView or navigation bar, use tapLabel to tap the matching accessibility label. This works with standard SwiftUI Label components.

Tab bar navigation
{
  "views": [
    { "name": "FeedView", "tapLabel": "Feed" },
    { "name": "ExploreView", "tapLabel": "Explore" },
    { "name": "ProfileView", "tapLabel": "Profile" }
  ]
}
Requires AXe CLI

Accessibility-based navigation requires the axe CLI tool. Install via: brew install cameroncooke/axe/axe

Coordinate Tap

iOS 18+ changed how TabView exposes its tabs to the accessibility tree — individual tab buttons are no longer visible to automation tools. Use tapXY with point coordinates to tap specific UI elements like tab bar items.

Coordinate-based navigation (iOS 18+)
{
  "views": [
    { "name": "FeedView", "tapXY": { "x": 40, "y": 832 } },
    { "name": "ExploreView", "tapXY": { "x": 120, "y": 832 } },
    { "name": "MapView", "tapXY": { "x": 280, "y": 832 } }
  ]
}
Finding coordinates

Use axe describe-ui --udid <UDID> to inspect the accessibility tree and find the frame positions of UI elements. Tab bar center Y is typically around 832 points on iPhone. Divide pixel coordinates by the device scale factor (3x for Pro devices).

Welcome and Onboarding Screens

If your app normally skips onboarding, keep the project-level launchArgs set for the main app flow and override specific welcome/onboarding screens with launchArgs: [] plus requiresLogout: true. Add verifyLabels so OpenEditor only accepts a capture once the simulator has landed on the intended onboarding step.

Capturing both onboarding and logged-in screens
{
  "launchArgs": ["-hasCompletedOnboarding", "YES"],
  "views": [
    {
      "name": "WelcomeIntroScreen",
      "requiresLogout": true,
      "launchArgs": [],
      "verifyLabels": ["Welcome to Brunchclub"]
    },
    {
      "name": "FeedView",
      "tapXY": { "x": 40, "y": 832 },
      "verifyLabels": ["Curated Selection"]
    }
  ]
}

Fallback

When no navigation strategy is configured, OpenEditor captures whatever is currently visible on the simulator. iPhone and iPad get separate screenshots if both simulators are booted.

How It Works

  1. OpenEditor reads oe-swift.json and resolves the Xcode project and views.
  2. It runs xcodebuild to build the app for the iOS Simulator.
  3. The built .app is installed and launched on all booted simulators.
  4. For each view with navigation config, the engine navigates to it (deep link or tap), then captures a screenshot.
  5. Screenshots are cached to /tmp/oe-preview-cache/ for instant reload on next session.
  6. A file watcher monitors .swift files — on save, it triggers an incremental rebuild and re-capture.

Full Example

oe-swift.json
{
  "project": "BrunchclubApp.xcodeproj",
  "scheme": "BrunchclubApp",
  "simulator": "iPhone 16 Pro",
  "bundleId": "com.lisbonbrunchclub.app",
  "launchArgs": ["-hasCompletedOnboarding", "YES"],
  "views": [
    {
      "name": "WelcomeIntroScreen",
      "file": "Sources/Views/OnboardingView.swift",
      "label": "Welcome",
      "requiresLogout": true,
      "launchArgs": [],
      "verifyLabels": ["Welcome to Brunchclub"]
    },
    {
      "name": "FeedView",
      "file": "Sources/Views/FeedView.swift",
      "label": "Feed",
      "tapXY": { "x": 40, "y": 832 },
      "verifyLabels": ["Curated Selection"]
    },
    {
      "name": "ExploreView",
      "file": "Sources/Views/ExploreView.swift",
      "label": "Explore",
      "tapXY": { "x": 120, "y": 832 }
    },
    {
      "name": "MenuView",
      "file": "Sources/Views/MenuView.swift",
      "label": "Menu",
      "tapXY": { "x": 200, "y": 832 }
    },
    {
      "name": "MapView",
      "file": "Sources/Views/TabViews.swift",
      "label": "Map",
      "tapXY": { "x": 280, "y": 832 }
    },
    {
      "name": "FavoritesView",
      "file": "Sources/Views/TabViews.swift",
      "label": "Saved",
      "tapXY": { "x": 360, "y": 832 }
    }
  ]
}

Auto-Discovery

If views is omitted or empty, OpenEditor scans the project directory for .swift files and finds all structs conforming to View. Preview provider structs (ending in _Previews) are excluded.

If project is omitted, the first .xcworkspace or .xcodeproj found in the directory is used.

Tip

Auto-discovery is convenient for getting started, but defining views explicitly gives you control over which views appear on the canvas and enables per-view navigation for unique screenshots.

Prerequisites

  • Xcode installed with command line tools (xcode-select --install)
  • iOS Simulator booted — boot via Xcode or xcrun simctl boot "iPhone 16 Pro"
  • OpenEditor Desktop (macOS) — the Tauri runtime is required for simulator interaction. Download the current .pkg, .dmg, or tarball from the download page.
  • AXe CLI (optional) — for accessibility-based navigation: brew install cameroncooke/axe/axe

Best Practices

  • Place oe-swift.json next to your .xcodeproj or .xcworkspace.
  • Keep one oe-swift.json per Swift project. OpenEditor regenerates the canvas per workspace, so Pitcher, Brunchclub, and other local iOS projects can be switched independently.
  • Boot both an iPhone and iPad simulator for side-by-side responsive previews.
  • Add tapLabel to views that correspond to tab bar items for automatic per-view screenshots.
  • Add .oe-build to your .gitignore — this is where incremental build artifacts are stored.
  • For apps with deep linking, set urlScheme and deepLink per view for the most reliable navigation.

Testing with oe-tests.json

Define screenshot-based test specs alongside your project config. Each test is a sequence of actions (tap, navigate, wait) followed by screenshot captures. Run them from the Tests tab in OpenEditor.

oe-tests.json
{
  "project": "MyApp",
  "simulator": "iPhone 16 Pro",
  "bundleId": "com.example.myapp",
  "tests": [
    {
      "name": "All tabs load",
      "description": "Navigate through all tabs and verify each renders",
      "steps": [
        { "action": "tapXY", "x": 40, "y": 832, "name": "Tap Feed tab" },
        { "action": "screenshot", "name": "Feed" },
        { "action": "tapXY", "x": 200, "y": 832, "name": "Tap Map tab" },
        { "action": "screenshot", "name": "Map" }
      ]
    }
  ]
}

Test Spec Fields

Field Type Description
project Optional string Project name for display.
simulator Optional string Default simulator device. Default: iPhone 16 Pro.
bundleId Optional string App bundle identifier for the test target.
tests Required array Array of test cases, each with a name and steps.

Step Actions

Action Fields Description
tapXY x, y, wait? Tap at screen coordinates (points). Waits wait ms after (default 800).
tapLabel label, wait? Tap an accessibility label. Requires AXe or XcodeBuildMCP.
deepLink url, wait? Open a deep link URL via simctl openurl.
screenshot name? Capture a screenshot. Displayed inline in the test results.
wait wait? Pause for wait ms (default 1000). Use between actions that need settling time.
navigate url, wait? Open a URL in the simulator (same as deepLink).

Full Example

oe-tests.json — multi-test suite
{
  "project": "Brunchclub",
  "simulator": "iPhone 16 Pro",
  "bundleId": "com.lisbonbrunchclub.app",
  "tests": [
    {
      "name": "All tabs load",
      "description": "Navigate all 5 tabs and verify each renders",
      "steps": [
        { "action": "tapXY", "x": 40, "y": 832, "name": "Tap Feed" },
        { "action": "screenshot", "name": "Feed" },
        { "action": "tapXY", "x": 120, "y": 832, "name": "Tap Explore" },
        { "action": "screenshot", "name": "Explore" },
        { "action": "tapXY", "x": 200, "y": 832, "name": "Tap Menu" },
        { "action": "screenshot", "name": "Menu" },
        { "action": "tapXY", "x": 280, "y": 832, "name": "Tap Map" },
        { "action": "screenshot", "name": "Map" },
        { "action": "tapXY", "x": 360, "y": 832, "name": "Tap Saved" },
        { "action": "screenshot", "name": "Favorites" }
      ]
    },
    {
      "name": "Venue detail flow",
      "description": "Open a venue card from Explore",
      "steps": [
        { "action": "tapXY", "x": 120, "y": 832, "name": "Go to Explore" },
        { "action": "wait", "wait": 600 },
        { "action": "tapXY", "x": 100, "y": 500, "name": "Tap venue card" },
        { "action": "wait", "wait": 1000 },
        { "action": "screenshot", "name": "Venue detail" }
      ]
    }
  ]
}
Video Recording

The Tests tab includes a Record button that captures a video of the simulator while tests run. Requires xcodebuildmcp: brew install getsentry/xcodebuildmcp/xcodebuildmcp

Placement

Place oe-tests.json in the same directory as oe-swift.json, next to your .xcodeproj. OpenEditor auto-discovers it when you switch to the Tests tab.