Integrating your calendar app with Apple Intelligence
At a glance
| Item | Summary |
|---|---|
| Purpose | Adopt calendar-domain entities and create, update, delete, and open schemas for system experiences. |
| Architecture | One SwiftUI/SwiftData app with a main-actor CalendarManager shared by views, entity queries, and intents. |
| State strategy | SwiftData models are authoritative; entity structs are snapshots/adapters; NavigationManager.shared owns foreground presentation state. |
| Boundary | App and UI-test targets only; there is no App Intents extension or widget. |
Project structure
CometCal/
├── AppIntents/
│ ├── Entities/
│ ├── Intents/
│ └── TestSupport/
├── Managers/
├── Model/
├── Views/
└── CometCalApp.swift
CometCalUITests/ # AppIntentsTesting user-path tests
Feature folders keep the system contract, persistence models, orchestration, and views distinct.
Overall architecture
flowchart LR
Root["CometCalApp"] --> Manager["CalendarManager"]
Manager --> Data["SwiftData ModelContainer / ModelContext"]
Root --> DI["AppDependencyManager"]
DI --> Intents["Calendar schema intents"]
DI --> Queries["Entity queries"]
Queries --> Entities["Calendar/Event/Attendee entities"]
Intents --> Manager
Views["SwiftUI views"] --> Manager
Open["OpenEventIntent"] --> Nav["NavigationManager"]
Reference code
CometCal/CometCalApp.swift:14 — one manager is registered for UI-independent intent execution.
init() {
let manager = CalendarManager.shared
AppDependencyManager.shared.add(dependency: manager)
}
WindowGroup { CalendarListView() }
.modelContainer(CalendarManager.shared.modelContainer)Ownership and state
classDiagram
CometCalApp o-- CalendarManager : shared
CalendarManager *-- ModelContainer
CalendarManager *-- ModelContext
CalendarManager *-- CSSearchableIndex
CalendarModel *-- EventModel
EventEntity o-- CalendarEntity : snapshot
AppIntent --> CalendarManager : injected
CalendarListView o-- NavigationManager : shared State
Ownership evidence
CometCal/Managers/CalendarManager.swift:13 — the main-actor repository owns persistence and indexing collaborators.
@MainActor @Observable
final class CalendarManager {
static let shared = CalendarManager()
let modelContainer: ModelContainer
var modelContext: ModelContext
nonisolated(unsafe) let searchableIndex: CSSearchableIndex
}Only CalendarManager performs CRUD and synchronization with Spotlight. Entities and intents do not own stored models.
Class and protocol design
Entities adopt domain schemas and framework protocols: EventEntity is an IndexedEntity and OwnershipProvidingEntity at CometCal/AppIntents/Entities/EventEntity.swift:11. Nested query types bind lookup closely to their entity. Intent structs use @AppIntent(schema:) rather than app-defined command protocols, and inject the repository through @Dependency.
Access control
| Symbol | Access | Rationale |
|---|---|---|
| Manager and app types | internal default | The system discovers App Intents from the app target; no public library API is required. |
| Update resolution helpers | private |
Preserve unset-vs-clear translation inside one intent (CometCal/AppIntents/Intents/UpdateEventIntent.swift:69). |
| Seed fixtures/helpers | private |
Test/sample data cannot be mutated externally. |
searchableIndex |
internal + nonisolated(unsafe) |
Async indexing needs nonisolated use; the exception is explicit and narrow. |
| Public/file-private API | none | Cross-file collaboration is target-internal. |
Logic ownership and placement
| Logic | Owner | Placement reason |
|---|---|---|
| CRUD, SwiftData save, Spotlight, donations | CalendarManager |
One transactional mutation boundary (CometCal/Managers/CalendarManager.swift:139). |
| Schema conversion and query | Entity files | Keeps system representation near its contract. |
| Parameter resolution | Intent perform and private helpers |
Translates system optionality before calling the repository. |
| Foreground routes | NavigationManager |
UI state is isolated from persistence (CometCal/Managers/NavigationManager.swift:9). |
| Test seeding | AppIntents/TestSupport |
Keeps testing entry points out of production feature code. |
Design patterns
| Pattern | Evidence | Purpose |
|---|---|---|
| Repository | CalendarManager owns all model operations |
Prevents intents and views from duplicating persistence rules. |
| Entity adapter | Model .entity projections |
Separates SwiftData identity/lifetime from system values. |
| Composition root / DI | Manager registration in app init | Makes background invocations resolve data. |
| Router | NavigationManager.shared |
Opens system-selected content in foreground UI (CometCal/AppIntents/Intents/OpenEventIntent.swift:20). |
| Schema adapter | Calendar @AppIntent declarations |
Maps domain operations into system contracts. |
Naming conventions
- Stored types end in
Model; system projections end inEntity. - Mutation entry points use verb-noun names (
CreateEventIntent,UpdateEventIntent). - Coordinators use
Manager; UI presentation types useView. - Test-only intents state their operation (
Seed,Fetch,Reset,Clear).
Architecture takeaways
- Put persistence, indexing, and donations behind one mutation owner.
- Treat entities as snapshots and resolve them back to stored models by stable identifiers.
- Keep schema optionality resolution in the intent adapter, not the repository.
- A separate extension is unnecessary when the app target can host these App Intents.
Source map
| Source | Role |
|---|---|
CometCal/Managers/CalendarManager.swift:28 |
Data stack creation |
CometCal/AppIntents/Entities/EventEntity.swift:67 |
Entity ownership semantics |
CometCal/AppIntents/Entities/EventEntity.swift:125 |
Event query |
CometCal/AppIntents/Intents/CreateEventIntent.swift:30 |
Create schema adapter |
CometCal/AppIntents/Intents/UpdateEventIntent.swift:33 |
Update orchestration |