-
探索适用于 Siri 和 Apple 智能的 App Intents 高级功能
使用高阶 App Intents API,优化你的 App 与 Siri 的协作配合。了解一些实用技巧,让用户仅用语音就能完成更多任务、帮助 Apple 智能发现你的内容,并为屏幕感知提供情境信息,从而让 Siri 能够理解你 App 的行为。
章节
- 0:00 - Introduction
- 1:59 - Customize how Siri responds
- 4:20 - Visual responses
- 6:22 - Interaction donations
- 9:46 - Confirmations and entity ownership
- 11:59 - Semantic index with IndexedEntity
- 13:32 - Structured search with IntentValueQuery
- 15:27 - In-app search
- 16:22 - Onscreen awareness
- 20:51 - Leverage existing integrations
- 23:30 - Next steps
资源
- App Intents Testing
- Donating your app’s data and actions to the system
- Donations and discovery
- Making app entities available in Spotlight
- Making actions and content discoverable by Apple Intelligence
- Providing contextual cues to Apple Intelligence and Siri
- Apple Intelligence and Siri AI
相关视频
WWDC26
-
搜索此视频…
-
-
2:42 - Custom dialog response
@AppIntent(schema: .audio.addToPlaylist) struct AddToPlaylistIntent { func perform() async throws -> some IntentResult & ProvidesDialog { // Adds song to playlist and responds return .result( dialog: IntentDialog( full: """ Added \(song.title) to the \ \(playlist.title) mix tape. """, supporting: "Added" ) ) } } -
3:42 - Ask a clarifying question within an inten
@AppIntent(schema: .clock.createTimer) struct CreateTimerIntent { // MARK: Schema Parameters var duration: Duration var label: String? var isSleepTimer: Bool func perform() async throws -> some ReturnsValue<TimerEntity> { // Checks active timers and requests label parameter label = try await $label.requestValue( """ You already have a timer running. \ What should we call this one? """ ) return .result(value: timerEntity) } } -
4:26 - Enhanced DisplayRepresentation
// Enhanced DisplayRepresentation @AppEntity(schema: .audio.song) struct SongEntity { var displayRepresentation: DisplayRepresentation { DisplayRepresentation( title: "\(title)", subtitle: "\(artistName)", image: artworkImage ) } } -
5:05 - Return a custom snippet view
@AppIntent(schema: .audio.addToPlaylist) struct AddToPlaylistIntent { var audioEntity: AudioEntity var playlist: PlaylistEntity func perform() async throws -> some IntentResult & ProvidesDialog & ShowsSnippetView { // Adds to playlist and shows dialog and snippet let view = PlaylistSnippetView( playlist: updatedEntity, tracks: updated.tracks ) return .result(dialog: dialog, view: view) } } -
7:44 - Donate a UI interaction
@ModelActor actor ModelManager { func sendMessage(_ /* ... */, donateIntent: Bool = false) async throws -> [Message.ID] { // Donate intent with parameters and result so Siri can learn user preferences if donateIntent { let intent = SendMessageIntent() intent.destination = .recipients(conversation.recipients.map(\.entity)) let result = messages.map(\.entity) Task { try await IntentDonationManager.shared.donate( intent: intent, result: .result(value: result) ) } } } } -
10:03 - Declare entity ownership for confirmations
// Informs system if entity is public or shared with others @AppEntity(schema: .calendar.event) struct EventEntity: OwnershipProvidingEntity { var ownership: EntityOwnership { // isShared used to compute ownership state: .shared, .public, or .unknown attendees.isEmpty ? .unknown : .shared } } -
11:30 - Index entities with IndexedEntity
// Indexing IndexedEntity with CSSearchableIndex struct EntityIndexingHelper { // Indexes playlist entities func indexPlaylist(_ playlist: Playlist) async throws { let entity = PlaylistEntity(playlist: playlist) try await CSSearchableIndex(name: indexName) .indexAppEntities([entity]) } } -
13:38 - Structured search with IntentValueQuer
// Structured search of songs and playlists struct AudioIntentValueQuery: IntentValueQuery { // AudioSearch, IntentPerson, and other system types may be supported as input func values(for input: AudioSearch) async throws -> [AudioEntity] { switch input.criteria { case .searchQuery(let query): return try await searchResults(for: query) case .unspecified: return try await likedSongResults() // ... also a .url case } } } -
14:49 - Re-run Siri search in your app
// Intent that re-runs the Siri search in app @AppIntent(schema: .system.searchInApp) struct SearchAudioLibraryIntent { var criteria: StringSearchCriteria func perform() async throws -> some IntentResult { // Perform in-app search with Siri search string navigation.searchText = criteria.term navigation.selectedTab = .library return .result() } } -
16:27 - Onscreen awareness annotations
// (a) Single primary entity on screen — NSUserActivity struct NowPlayingView: View { @Environment(PlaybackController.self) private var playback var body: some View { VStack { // Player UI } .userActivity("cosmotunes.nowPlaying", isActive: playback.currentTrack) { activity in activity.title = playback.currentTrack?.title activity.appEntityIdentifier = EntityIdentifier( for: SongEntity.self, identifier: playback.currentTrack.id ) } } } // (b) One entity among many — View Entity annotation struct AlbumView: View { private var header: some View { VStack(alignment: .leading, spacing: 6) { // ... } .appEntityIdentifier( EntityIdentifier(for: AlbumEntity.self, identifier: session.id.uuidString) ) } } // (c) Lists and collections — Collection annotation struct PlaylistDetailView: View { var body: some View { List { ForEach(playlist.tracks) { track in PlaylistTrackRow(track: track) } } .appEntityIdentifier(forSelectionType: GeneratedTrack.ID.self) { trackID in EntityIdentifier(for: SongEntity.self, identifier: trackID) } } } -
17:23 - Component-based display representation query
// Component-based display representation queries extension PlaylistQuery { func displayRepresentations( for identifiers: [PlaylistEntity.ID], requestedComponents: DisplayRepresentation.Components = .text ) async throws -> [PlaylistEntity.ID: DisplayRepresentation] { let entities = try await model.playlistEntities(for: identifiers) // Fetch display representations for fetched entities var result: [PlaylistEntity.ID: DisplayRepresentation] = [:] for entity in entities { result[entity.id] = await entity.displayRepresentation(with: requestedComponents) } return result } } -
21:07 - Entity annotations on system integrations
// (a) User notifications import AppIntents import UserNotifications func scheduleNotification(message: Message, author: Contact, conversation: Conversation) { let content = UNMutableNotificationContent() content.title = author.name content.body = message.body // Annotate with entity identifier content.appEntityIdentifiers = [ EntityIdentifier(for: MessageEntity.self, identifier: message.id) ] // Schedule the notification } // (b) Now Playing — most specific to least specific import NowPlaying final class CosmoTunesMediaSession: MediaSessionRepresentable { var content: (any MediaContentRepresentable)? { var content = MusicContent(id: track.id.uuidString, songTitle: track.title /* ... */) content.appEntityIdentifiers = [ EntityIdentifier(for: SongEntity.self, identifier: track.id), EntityIdentifier(for: ArtistEntity.self, identifier: track.session.artistName), EntityIdentifier(for: PlaylistEntity.self, identifier: currentPlaylist.id), ] return content } } // (c) AlarmKit import AlarmKit func scheduleAlarm(_ alarm: Alarm) async throws { let configuration = AlarmManager.AlarmConfiguration<CosmoTunesAlarmMetadata>.alarm( schedule: schedule, attributes: attributes, appEntityIdentifier: EntityIdentifier(for: AlarmEntity.self, identifier: alarm.id), stopIntent: DismissAlarmIntent(), secondaryIntent: SnoozeAlarmIntent(), sound: sound ) // Schedule alarm }
-
-
- 0:00 - Introduction
Advanced App Intents techniques to make your app's Siri and Apple Intelligence experience feel polished and personal. Agenda: shape the Siri conversation, improve content discovery, and leverage existing integrations, demoed with the CosmoTunes, UnicornChat, and CometCal sample apps.
- 1:59 - Customize how Siri responds
Shape Siri's responses to match your app's voice: return an empty result to let Siri respond, or adopt ProvidesDialog and return an IntentDialog with full and supporting strings. Ask clarifying questions mid-intent with a dialog request (such as requesting an optional timer label).
- 4:20 - Visual responses
Give Siri your app's look: an entity's DisplayRepresentation (title, subtitle, image) is used across responses, disambiguation, Spotlight, and Shortcuts, while a custom SwiftUI snippet view (ShowsSnippetView) styles specific actions. Customize only where it helps, and account for voice-only devices.
- 6:22 - Interaction donations
System interactions are known automatically, but UI interactions aren't, so donate them via IntentDonationManager (using schema-conforming intents) so Apple Intelligence learns app preferences and stays aware of ongoing activities (such as Maps navigation or Clock stopwatches). Donate accurately; excessive donations are ignored.
- 9:46 - Confirmations and entity ownership
Siri auto-confirms intents with meaningful side effects, especially on shared or public content. Conform shareable entities to the new OwnershipProvidingEntity protocol and keep the ownership state current so Siri confirms appropriately, using your display representations as the confirmation visuals.
- 11:59 - Semantic index with IndexedEntity
Make local content discoverable: adopt IndexedEntity and index entities in Spotlight via indexAppEntities for meaning-based search. Keep the index fresh (add, update, delete), and support re-indexing with the new IndexedEntityQuery.
- 13:32 - Structured search with IntentValueQuery
For content too large, server-side, or fast-changing to index, use IntentValueQuery: the system passes a structured search input and you can return multiple entity types. CosmoTunes maps an AudioSearch (query, unspecified, or URL criteria) to a UnionValue of songs and playlists.
- 15:27 - In-app search
Adopt the system searchInApp schema (formerly system.search) so "Show me running playlists in CosmoTunes" re-runs Siri's search inside your own crafted search UI, regardless of which domains you adopt or whether you index entities.
- 16:22 - Onscreen awareness
Connect what's visible to entities so Siri resolves "play the third one." Start with NSUserActivity (single primary item) and View Entity annotations (appEntityIdentifier, one of many); scale up with collection annotations (forSelectionType:) and custom canvas annotations, all supported in UIKit and AppKit. Enable display-representation queries so Siri resolves on-screen entities fast.
- 20:51 - Leverage existing integrations
Attach entities to system integrations you already use: appEntityIdentifiers on UNMutableNotificationContent (reply to announced notifications), on Now Playing via MediaSessionRepresentable (for example "play the live version"), and appEntityIdentifier on AlarmKit's AlarmConfiguration ("snooze it"). Persistent entities only, no transient entities.
- 23:30 - Next steps
Start by customizing entity display representations, then index entities and keep the index current, add IntentValueQuery and in-app search, annotate views and existing integrations, and finally donate UI interactions. See the sample projects and "Code-along: Make your app available to Siri."