Contentful Personalization & Analytics
    Preparing search index...

    Contentful Logo

    Contentful Personalization & Analytics

    iOS Reference App

    Readme · Guides · Reference · Contributing

    Warning

    The Optimization SDK Suite is pre-release (alpha). Breaking changes can be published at any time.

    Reference app for native iOS bridge and preview-panel validation work. This app exercises two iOS shells against the mock server in lib/mocks/ and hosts the XCUITest suite.

    Note

    This is not a published iOS SDK package. For package status, see packages/ios.

    Use this app when you need to validate native iOS bridge and preview-panel behavior against the shared mock API. The app includes two host shells:

    • OptimizationAppSwiftUI - SwiftUI shell, with sources under swiftui/.
    • OptimizationAppUIKit - UIKit shell, with sources under uikit/.

    Both apps share shared/ for configuration, Contentful fetching, and analytics event storage. They run the same UI test source tree from uitests/ against their respective host apps so SDK behavior can be compared across UI frameworks.

    The app configures native SDK contentfulLocales and an app/content locale, then uses client.locale in the shared raw CDA fetch helper. Experience API calls use that resolved locale by default unless nested api.locale is configured as an explicit API override. Entries passed to SDK resolution use the standard single-locale CDA entry shape. Do not use all-locale CDA responses or locale=*, because entry resolution expects direct single-locale fields such as fields.nt_experiences and fields.nt_variants. See Locale handling in the Optimization SDK Suite for the broader locale model and Entry personalization and variant resolution for the entry contract.

    This mock app uses one Contentful locale, so ContentfulLocales(default: "en-US") is enough. Add supported only when a production app needs device-locale matching across multiple Contentful locales.

    • Xcode with an iOS Simulator available.
    • pnpm workspace dependencies installed from the monorepo root.
    • XcodeGen for changes that add, rename, or move iOS source files.
    • The mock server running at http://localhost:8000.

    The Xcode project is generated by XcodeGen from project.yml. Regenerate the project after you add, rename, or move a source file:

    brew install xcodegen
    xcodegen generate

    From the monorepo root, start the mock API server before running UI tests:

    pnpm serve:mocks
    

    The bootstrap script runs preflight checks, then configures, builds, and launches the app on an iOS Simulator with the mock server running. When it finishes it leaves a simulator booted with the reference app and the mock server running in the foreground:

    cd implementations/ios-sdk
    ./scripts/bootstrap.sh

    The preflight checks verify that you are on macOS with Xcode (full IDE, not just the Command Line Tools), an iOS Simulator runtime, Node.js, and pnpm. If a check fails, the script prints the exact remediation steps and stops before building anything. XcodeGen is installed automatically via Homebrew if it is missing.

    Useful environment variables:

    • APP_SHELLswiftui (default) or uikit.
    • IOS_SIM_NAME — simulator device name (default iPhone 16); falls back to the first available iPhone if the named device is absent.
    • SKIP_BUILD — set to true to reuse the last build.

    Unlike the Android app, no port forwarding is required: the iOS Simulator shares the host network, so the app reaches the mock server at localhost:8000 directly.

    Use this app when you need a debuggable native iOS surface for changes in packages/ios/ContentfulOptimization or the shared JS bridge. The Xcode project references the SDK as a local Swift Package, so app builds compile the package from workspace source rather than from a published artifact.

    The app schemes include a pre-action that builds @contentful/optimization-js-bridge before Swift Package resource resolution when bridge source is newer than the copied UMD resource. The pre-action writes its output to /tmp/optimization-ios-build-js-bridge.log; check that file if Xcode appears to use a stale bridge bundle or cannot find pnpm.

    The normal loop is:

    1. Edit Swift in packages/ios/ContentfulOptimization/Sources/... or bridge TypeScript in packages/universal/optimization-js-bridge/src/....
    2. Build or run the OptimizationAppSwiftUI or OptimizationAppUIKit scheme. Xcode recompiles the local Swift Package and refreshes the bridge bundle when needed.
    3. Validate with the targeted app flow or XCUITest scenario that exercises the changed behavior.

    From the command line, build a shell from implementations/ios-sdk/:

    xcodebuild build \
    -project OptimizationApp.xcodeproj \
    -scheme OptimizationAppSwiftUI \
    -destination 'platform=iOS Simulator,name=iPhone 16'

    Run the smallest check that covers the changed surface:

    Change area Suggested validation
    Bridge TypeScript only pnpm --filter @contentful/optimization-js-bridge typecheck and pnpm --filter @contentful/optimization-js-bridge build
    Swift package behavior Targeted xcodebuild test for the affected app shell or XCUITest class
    Preview-panel or cross-platform behavior Targeted preview-panel XCUITest plus the matching shared scenario contract review
    Documentation-only README changes Prettier on touched Markdown and git diff --check

    Common local pitfalls:

    • Run xcodegen generate after changing project.yml or adding, moving, or renaming iOS sources.
    • Build through a scheme, not a raw target, so the bridge pre-action runs before Swift Package resource resolution.
    • If Xcode cannot find pnpm, launch Xcode from a shell where pnpm --version works or check the pre-action log for the exact PATH issue.
    • After switching branches, force a bridge rebuild if the copied UMD resource has a newer timestamp than the checked-out bridge source.
    • Substitute another available simulator if the local Xcode runtime does not include iPhone 16.

    Run the full suite against both app shells from implementations/ios-sdk/:

    xcodebuild test \
    -project OptimizationApp.xcodeproj \
    -scheme OptimizationAppSwiftUI \
    -destination 'platform=iOS Simulator,name=iPhone 16'

    xcodebuild test \
    -project OptimizationApp.xcodeproj \
    -scheme OptimizationAppUIKit \
    -destination 'platform=iOS Simulator,name=iPhone 16'

    Run a single test class against the SwiftUI shell:

    xcodebuild test \
    -project OptimizationApp.xcodeproj \
    -scheme OptimizationAppSwiftUI \
    -destination 'platform=iOS Simulator,name=iPhone 16' \
    -only-testing:OptimizationAppUITestsSwiftUI/PreviewPanelOverridesTests

    Add the .swift file to uitests/Tests/ and run xcodegen generate. Both UI test bundles pick up the file from the generated project.

    The preview-panel override suite mirrors the React Native Detox suite in implementations/react-native-sdk/e2e/preview-panel-overrides.test.js. Both reference the shared contract document at implementations/PREVIEW_PANEL_SCENARIOS.md. Keep scenario names and fixture IDs identical across platforms so cross-platform regressions are visible in CI diffs.