Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

2026 App ServicesAI & Machine Learning

WWDC26 · 24 min · App Services / AI & Machine Learning

Explore advanced App Intents features for Siri and Apple Intelligence

Polish how your app works with Siri using advanced App Intents APIs. Learn techniques that let people accomplish more with just their voice, help Apple Intelligence find your content, and provide context for on-screen awareness so Siri understands what’s happening in your app.

Watch at developer.apple.com ↗

Transcript all transcripts

Chapters

  • 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

Code shown on screen · 12 snippets

Custom dialog response swift · at 2:42 ↗
@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"
              )
          )
      }
  }
Ask a clarifying question within an inten swift · at 3:42 ↗
@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)
      }
  }
Enhanced DisplayRepresentation swift · at 4:26 ↗
// Enhanced DisplayRepresentation
  @AppEntity(schema: .audio.song)
  struct SongEntity {

      var displayRepresentation: DisplayRepresentation {
          DisplayRepresentation(
              title: "\(title)",
              subtitle: "\(artistName)",
              image: artworkImage
          )
      }
  }
Return a custom snippet view swift · at 5:05 ↗
@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)
      }
  }
Donate a UI interaction swift · at 7:44 ↗
@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)
                  )
              }
          }
      }
  }
Declare entity ownership for confirmations swift · at 10:03 ↗
// 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
      }
  }
Index entities with IndexedEntity swift · at 11:30 ↗
// 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])
      }
  }
Structured search with IntentValueQuer swift · at 13:38 ↗
// 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
          }
      }
  }
Re-run Siri search in your app swift · at 14:49 ↗
// 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()
      }
  }
Onscreen awareness annotations swift · at 16:27 ↗
// (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)
          }
      }
  }
Component-based display representation query swift · at 17:23 ↗
// 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
      }
  }
Entity annotations on system integrations swift · at 21:07 ↗
// (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
  }

Resources