Sample CodeiOS 27.0+, iPadOS 27.0+, Mac Catalyst 27.0+, macOS 27.0+Reviewed 2026-07-21View on Apple Developer

Adopting App Intents to support system experiences

At a glance

Item Summary
Purpose Demonstrate App Intents across navigation, Spotlight, visual search, snippets, transfer, shortcuts, and an interactive widget.
Architecture Host app and widget consume a three-layer Swift package: Snippets → Intents → Core.
State strategy Each process creates ModelData; an app-group SharedDataStore persists the small mutable subset shared across app and widget.
Boundary App target, widget extension, and three library products are explicit; process memory is not shared.

Project structure

AppIntentsTravelTracking/
├── AppIntentsTravelTracking/      # host app and views
├── TravelGalleryWidget/           # widget extension
└── TravelTrackingShared/
    └── Sources/
        ├── TravelTrackingCore/
        ├── TravelTrackingIntents/
        └── TravelTrackingSnippets/

Package dependencies point inward: views/targets may depend on shared libraries, while Core never imports the app target.

Overall architecture

Reference code

AppIntentsTravelTracking/TravelTrackingShared/Package.swift:21 — the package manifest enforces the layers (abridged).

products: [
    .library(name: "TravelTrackingCore", targets: ["TravelTrackingCore"]),
    .library(name: "TravelTrackingIntents", targets: ["TravelTrackingIntents"]),
    .library(name: "TravelTrackingSnippets", targets: ["TravelTrackingSnippets"])
],
targets: [
    .target(name: "TravelTrackingCore"),
    .target(name: "TravelTrackingIntents", dependencies: ["TravelTrackingCore"]),
    .target(name: "TravelTrackingSnippets", dependencies: ["TravelTrackingIntents"])
]

Ownership and state

Ownership evidence

AppIntentsTravelTracking/TravelTrackingShared/Sources/TravelTrackingCore/Persistence/SharedDataStore.swift:50 — the process-local singleton owns synchronized access to shared defaults.

public final class SharedDataStore: Sendable {
    public static let shared = SharedDataStore()
    public static let suiteName =
        "group.com.example.apple-samplecode.AppIntentsTravelTracking"

    nonisolated(unsafe) private let defaults: UserDefaults
    private let lock = Mutex<Void>(())
}

ModelData is the in-memory mutation owner. The store shares encoded values, not object identity, across the app and widget processes.

Class and protocol design

Core owns models/persistence; Intents owns public entities, queries, intents, and Navigator; Snippets owns interactive intent flows and their views. Types adopt specialized framework contracts such as IndexedEntity, Transferable, AppIntentTimelineProvider, and UndoableIntent. LandmarkEntityQuery combines EntityQuery, EntityStringQuery, EnumerableEntityQuery, and IndexedEntityQuery at AppIntentsTravelTracking/TravelTrackingShared/Sources/TravelTrackingIntents/Entities/LandmarkEntity.swift:130.

Access control

Symbol Access Rationale
Package-facing models/entities/intents public App and widget targets consume them across module boundaries.
SharedDataStore.defaults and lock private Preserve typed and atomic access paths.
nonisolated(unsafe) private defaults scoped concurrency escape UserDefaults is documented thread-safe; the exception is limited to one field.
Widget provider helpers private Timeline construction details remain inside the widget (AppIntentsTravelTracking/TravelGalleryWidget/TravelGalleryWidget.swift:37).
File logger private Logging implementation does not widen the package API (AppIntentsTravelTracking/TravelTrackingShared/Sources/TravelTrackingCore/Persistence/ModelData.swift:13).

The prevalence of public is evidence of true library boundaries, unlike target-only samples.

Logic ownership and placement

Logic Owner Placement reason
Domain models and persisted shared state Core Used by app, intents, snippets, and widget.
Entity/query/intent mapping Intents System contracts depend on Core without UI imports.
Multi-step snippet UI Snippets Snippet rendering depends on intent types, not host views.
App navigation/rendering Host app Process-specific scene behavior stays outside libraries.
Widget timelines/actions Widget target WidgetKit lifecycle is an executable-target concern (AppIntentsTravelTracking/TravelGalleryWidget/TravelGalleryWidget.swift:16).

Design patterns

Pattern Evidence Purpose
Layered package Manifest dependency graph Prevents host/widget details from leaking into Core.
Composition root / DI App registers ModelData, Navigator, SearchEngine (AppIntentsTravelTracking/AppIntentsTravelTracking/AppIntentsTravelTracking.swift:21) Supports intent/query construction.
Shared-store repository Typed DefaultsKey + app-group suite Coordinates a bounded cross-process state subset.
Entity adapter/query LandmarkEntity and query Publishes stable system-facing representations and Spotlight hooks.
Multi-target reuse Widget links Core + Intents Shares contracts while preserving separate lifecycle/state.

Naming conventions

  • Module names encode dependency purpose: Core, Intents, Snippets.
  • System types use precise role suffixes (Entity, Query, Intent, Provider, TimelineEntry).
  • Mutation APIs use domain verbs (setLandmarkFavorite, setTags, applySavedState).

Architecture takeaways

  • Use package targets when app and extension genuinely need shared compiled contracts.
  • Share compact persisted values across processes, never assumptions about singleton identity.
  • Keep system schema adapters outside Core domain models when they add framework dependencies.
  • Register dependencies independently in every executable process (AppIntentsTravelTracking/TravelGalleryWidget/TravelGalleryWidgetBundle.swift:13).

Source map

Source Role
AppIntentsTravelTracking/AppIntentsTravelTracking/AppIntentsTravelTracking.swift:15 Host composition root
AppIntentsTravelTracking/TravelTrackingShared/Sources/TravelTrackingCore/Persistence/ModelData.swift:15 Main-actor data owner
AppIntentsTravelTracking/TravelTrackingShared/Sources/TravelTrackingCore/Persistence/SharedDataStore.swift:113 Shared mutations
AppIntentsTravelTracking/TravelTrackingShared/Sources/TravelTrackingIntents/Intents/NavigateIntent.swift:11 Cross-platform navigation intent
AppIntentsTravelTracking/TravelTrackingShared/Sources/TravelTrackingIntents/Entities/LandmarkEntity.swift:16 Indexed entity