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

Integrating your photo app with Apple Intelligence

At a glance

Item Summary
Purpose Adopt Photos-domain entities and create, update, delete, open, search, and album-membership schemas.
Architecture One SwiftUI app wraps PhotoKit with an observable media-library facade, an actor image cache, and a navigation state owner.
State strategy MediaLibrary owns the current asset/album collections; Asset and Album wrap PhotoKit objects; entities are system-facing snapshots.
Boundary App target only; there is no App Intents extension, widget, or separate package.

Project structure

PhotosDomainExample/
├── AppIntents/
│   ├── Album/                  # Album entity, enum, and intents
│   ├── Asset/                  # Asset entity, filters, and intents
│   └── ShortcutsProvider.swift
├── Managers/                   # Media, image, location, navigation services
├── Model/                      # PhotoKit-backed Album and Asset wrappers
├── Views/                      # Album, asset, and favorites UI
└── PhotosDomainExample.swift   # Composition root

Feature-oriented App Intent folders sit beside framework-neutral manager and model folders. The app keeps system schema declarations separate from PhotoKit wrappers.

Overall architecture

Reference code

PhotosDomainExample/PhotosDomainExample.swift:19 — the app gives views and intents the same library and navigation instances.

init() {
    let navigationManager = NavigationManager()
    let library = MediaLibrary()
    library.load()
    AppDependencyManager.shared.add(dependency: library)
    AppDependencyManager.shared.add(dependency: navigationManager)
    self.library = library
    self.navigationManager = navigationManager
}

The same objects are also placed in the SwiftUI environment. This keeps system-triggered work and foreground UI synchronized without an extension-specific store.

Ownership and state

Ownership evidence

PhotosDomainExample/Managers/MediaLibrary.swift:21 — collection mutation is restricted to the library facade.

private(set) var assets: [Asset] = []
private(set) var albums: [Album] = []
Owner Object or state Relationship Mutation authority
PhotosDomainExample MediaLibrary, NavigationManager Creates, retains, and injects Lifecycle and wiring
MediaLibrary Asset/album arrays Observable owner with externally read-only collections Load, create, import, delete, and indexing coordination
Asset / Album PhotoKit object plus mutable item snapshot Wrapper retains a framework object Item property and album-membership changes
ImageManager.shared PHCachingImageManager, cached IDs/options Actor singleton and private owner Image cache and request serialization
NavigationManager Navigation paths, selection, search term Observable foreground owner Open and search presentation state
Intent/query values Injected managers Borrow per invocation Resolve, validate, and delegate only

Class and protocol design

Type Design Responsibility
MediaLibrary @Observable final class Facade over authorization, collection loading, create/delete operations, and Spotlight synchronization.
Asset / Album @Observable final class, Identifiable, Hashable Give PhotoKit references stable app identity and mutation methods.
ImageManager actor singleton Serialize access to the mutable PhotoKit caching manager.
NavigationManager @Observable final class Route system-selected assets, albums, and searches into SwiftUI state.
AlbumEntity / AssetEntity schema AppEntity, IndexedEntity Adapt app wrappers to system-visible values.
Intent structs AppIntent, OpenIntent, or DeleteIntent Bind Photos/system schemas to app operations.

There is no app-defined repository protocol. Protocol-oriented design appears at framework seams (IndexedEntity, EntityQuery, OpenIntent, and DeleteIntent), while the concrete MediaLibrary is injected directly.

Access control

Symbol Access Verified effect Likely rationale
MediaLibrary.assets, albums private(set) Views, queries, and intents can read collections; only the library replaces or appends them. Preserve collection/index synchronization.
Fetch and authorization helpers private Callers use load rather than partially running the load pipeline. Keep permission and indexing behavior together.
PhotoKit helper functions nonisolated private static Helpers can run away from actor-isolated state but remain callable only inside MediaLibrary. Move pure PhotoKit work off the UI path without widening API.
ImageManager cache/options private Consumers can request/cancel caching but cannot mutate the underlying manager. Protect cache bookkeeping.
App, managers, models, intents, entities internal default Types collaborate across app files but are not exported. No reusable framework API is needed.
Architecture types no fileprivate, public, or open There is no file-only collaboration surface, exported API, or subclassing contract. Type-private invariants and target-internal composition are sufficient.

Reference code

PhotosDomainExample/Managers/MediaLibrary.swift:156 — off-main helpers stay both stateless and type-private.

// These run off the main actor so the library iteration and Photos
// change-blocks never block UI. They are pure helpers — they do not
// touch any actor-isolated state.

nonisolated private static func fetchAllPhotoAssets() async -> [PHAsset] {
    // ...
}

Logic ownership and placement

Logic Owner Placement rationale
Authorization, load, create/import/delete, Spotlight MediaLibrary Collection state and external side effects change together.
Favorite/hidden/title/album-membership mutations Asset and Album Item wrappers update PhotoKit and their observable snapshots in one method.
Image caching and async image requests ImageManager An actor protects mutable caching resources from concurrent callers.
Foreground routes and search NavigationManager Presentation state is independent of PhotoKit storage.
Schema conversion and lookup Entity files and nested query types Keeps system contracts adjacent to their entity representation.
Parameter validation and orchestration Intent perform() methods System optionality is resolved before delegating to managers/models.

Design patterns

Pattern Source evidence Purpose or tradeoff
Composition root / DI PhotosDomainExample/PhotosDomainExample.swift:19 Shares state owners across UI and App Intent execution.
Repository facade PhotosDomainExample/Managers/MediaLibrary.swift:26 Provides one entry point for collection loading and structural mutations.
Active record-like wrapper PhotosDomainExample/Model/Asset.swift:86 Keeps per-item PhotoKit mutation with the observable item snapshot.
Entity adapter PhotosDomainExample/Model/Album.swift:90 Projects framework-backed objects into schema values.
Actor service PhotosDomainExample/Managers/ImageManager.swift:11 Serializes mutable image-cache access.
Router PhotosDomainExample/Managers/NavigationManager.swift:28 Converts intent-driven opens/searches into SwiftUI navigation state.

Naming conventions

  • PhotoKit-backed domain wrappers use plain nouns (Asset, Album); system values append Entity.
  • Long-lived capabilities append Manager: MediaLibrary is the deliberate domain-named exception and reads as a facade.
  • Commands use verb-noun intent names (CreateAssetsIntent, OpenAlbumIntent, DeleteAssetsIntent).
  • Mutation methods begin with verbs (create, delete, set, open); lookup methods use assets(for:) and albums(for:).
  • App Intent folders are organized by schema noun, while view folders mirror UI features.

Architecture takeaways

  • Share one observable framework facade between views and intents when both run in the app target.
  • Use private(set) for collection snapshots whose writes must stay synchronized with an external framework and index.
  • Keep collection-level operations in the library facade and item-level mutations on the PhotoKit-backed wrappers.
  • Put mutable image caching behind an actor and keep navigation in a separate observable owner.
  • Prefer framework protocol conformances for system integration; introduce custom protocols only when a real replacement seam is needed.

Source map

Source Relevant symbols
PhotosDomainExample/PhotosDomainExample.swift:11 App entry and dependency registration
PhotosDomainExample/Managers/MediaLibrary.swift:12 Photo library facade and collection owner
PhotosDomainExample/Managers/ImageManager.swift:11 Actor image cache
PhotosDomainExample/Managers/NavigationManager.swift:11 Foreground router
PhotosDomainExample/Model/Asset.swift:14 PhotoKit-backed asset wrapper
PhotosDomainExample/Model/Album.swift:11 PhotoKit-backed album wrapper
PhotosDomainExample/AppIntents/Album/AlbumEntity.swift:10 Indexed album schema entity/query
PhotosDomainExample/AppIntents/Album/AlbumIntents.swift:11 Album schema adapters
PhotosDomainExample/AppIntents/Asset/AssetIntents.swift:16 Asset and system-search adapters