-
Explorez les fonctionnalités avancées d’App Intents pour Siri et Apple Intelligence
Peaufinez le fonctionnement de votre app avec Siri à l'aide des API avancées d'App Intents. Découvrez des techniques qui permettent aux personnes d'en faire plus juste avec la voix, aident Apple Intelligence à trouver votre contenu et fournissent du contexte pour l'analyse à l'écran afin que Siri comprenne ce qui se passe dans votre app.
Chapitres
- 0:00 - Introduction
- 1:59 - Personnalisez les réponses de Siri
- 4:20 - Réponses visuelles
- 6:22 - Transmissions d’interactions
- 9:46 - Confirmations et propriété des entités
- 11:59 - Index sémantique avec IndexedEntity
- 13:32 - Recherche structurée avec IntentValueQuery
- 15:27 - Recherche dans l’app
- 16:22 - Détection du contenu à l’écran
- 20:51 - Tirez parti des intégrations existantes
- 23:30 - Étapes suivantes
Ressources
- 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
Vidéos connexes
WWDC26
-
Rechercher dans cette vidéo…
-
-
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 }
-