2026 App ServicesAI & Machine Learning
WWDC26 · 18 min · App Services / AI & Machine Learning
Best practices for integrating visual intelligence in your app
Gain insight on how visual intelligence can transform content discovery in your app. Explore how to define entities, process images, and handle multiple result types effectively. Learn best practices for optimizing speed and relevance, and discover how intents enable direct actions like opening or playing content with a single tap.
Watch at developer.apple.com ↗Chapters
Code shown on screen · 11 snippets
Define the content you want to return as an App Entity
// Define the content you want to return as an App Entity
import AppIntents
struct AlbumEntity: AppEntity {
var id: String
var name: String
var artistName: String
var coverArtData: Data
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(
title: "\(name)",
subtitle: "\(artistName)",
image: .init(data: coverArtData)
)
}
static let defaultQuery = AlbumEntityQuery()
static var typeDisplayRepresentation: TypeDisplayRepresentation { "Album" }
}
struct AlbumEntityQuery: EntityQuery {
var catalog: AlbumCatalog
func entities(for identifiers: [String]) async throws -> [AlbumEntity] {
catalog.albums(for: identifiers)
}
} Adopt IntentValueQuery to return results
// Adopt IntentValueQuery to return visual search results
import AppIntents
import VisualIntelligence
struct SearchHandler: IntentValueQuery {
var catalog: AlbumCatalog
var concertFinder: ConcertFinder
func values(for input: SemanticContentDescriptor) async throws -> [VisualSearchResult] {
guard let pixelBuffer = input.pixelBuffer else {
return []
}
let albums = try await catalog.search(matching: pixelBuffer)
return albums.map { VisualSearchResult.album($0) }
}
} Build a catalog of albums with precomputed feature prints
// Build a catalog of albums with precomputed feature prints
import Vision
class AlbumCatalog {
static let shared = AlbumCatalog()
struct CatalogEntry: Sendable {
let album: AlbumEntity
let featurePrint: FeaturePrintObservation
}
private(set) var entries: [CatalogEntry] = []
private func generateFeaturePrint(
for image: CGImage
) async throws -> FeaturePrintObservation {
let request = GenerateImageFeaturePrintRequest()
let result = try await request.perform(on: image)
return result
}
} Search the catalog for albums matching the captured image
// Search the catalog for albums matching the captured image
func search(matching pixelBuffer: CVReadOnlyPixelBuffer, limit: Int = 10, maxDistance: Double = 1.0) async throws ->
[AlbumEntity] {
var cgImage: CGImage?
_ = pixelBuffer.withUnsafeBuffer { VTCreateCGImageFromCVPixelBuffer($0, options: nil, imageOut: &cgImage) }
guard let cgImage else { return [] }
let queryPrint = try await generateFeaturePrint(for: cgImage)
return try entries.compactMap { entry -> (album: AlbumEntity, distance: Double)? in
let distance = try queryPrint.distance(to: entry.featurePrint)
guard distance <= maxDistance else { return nil }
return (entry.album, distance)
}
.sorted { $0.distance < $1.distance }
.prefix(limit)
.map { $0.album }
} Create an open intent to land users on the right screen
// Create an open intent to land users on the right screen
import AppIntents
struct OpenAlbumIntent: OpenIntent {
static let title: LocalizedStringResource = "Open Album"
(title: "Album")
var target: AlbumEntity
var appState: AppState
func perform() async throws -> some IntentResult {
await appState.openAlbum(id: target.id)
return .result()
}
} Use UnionValue to return multiple visual search result types
// Use UnionValue to return multiple visual search result types
enum VisualSearchResult {
case album(AlbumEntity)
case concert(ConcertEntity)
}
struct OpenConcertIntent: OpenIntent {
static let title: LocalizedStringResource = "Open Concert"
(title: "Concert")
var target: ConcertEntity
var appState: AppState
func perform() async throws -> some IntentResult {
await appState.openConcert(id: target.id)
return .result()
}
} Expand the IntentValueQuery to return the UnionValue
// Expand the IntentValueQuery to return the UnionValue
struct SearchHandler: IntentValueQuery {
var catalog: AlbumCatalog
var concertFinder: ConcertFinder
func values(for input: SemanticContentDescriptor) async throws -> [VisualSearchResult] {
guard let pixelBuffer = input.pixelBuffer else {
return []
}
let albums = try await catalog.search(matching: pixelBuffer)
let artists = albums.map { $0.artistName }
let concerts = await concertFinder.findNearby(byArtists: artists)
return albums.map { VisualSearchResult.album($0) }
+ concerts.map { VisualSearchResult.concert($0) }
}
} Provide a link to in-app search
// Provide a link to in-app search
(schema: .visualIntelligence.semanticContentSearch)
struct SemanticContentSearchIntent: AppIntent {
static let title: LocalizedStringResource = "Search in app"
static let openAppWhenRun: Bool = true
var semanticContent: SemanticContentDescriptor
var catalog: AlbumCatalog
var concertFinder: ConcertFinder
var appState: AppState
func perform() async throws -> some IntentResult {
guard let pixelBuffer = semanticContent.pixelBuffer else { return .result() }
let albums = try await catalog.search(matching: pixelBuffer)
let artists = albums.map { $0.artistName }
let concerts = await concertFinder.findNearby(byArtists: artists)
await appState.openSearch(albums: albums, concerts: concerts)
return .result()
}
} Request calendar access and fetch upcoming concerts
// Request calendar access and fetch upcoming concerts
import EventKit
class UpcomingConcertManager {
private let eventStore = EKEventStore()
var upcomingConcerts: [EKEvent] = []
var authorizationStatus: EKAuthorizationStatus = .notDetermined
func requestAccessAndFetch() async throws {
let granted = try await eventStore.requestFullAccessToEvents()
guard granted else {
authorizationStatus = .denied
return
}
authorizationStatus = .fullAccess
await fetchUpcomingConcerts()
// ...
}
} Filter for upcoming events that match known artists in our catalog
// Filter for upcoming events that match known artists in our catalog
class UpcomingConcertManager {
func fetchUpcomingConcerts() async {
let predicate = eventStore.predicateForEvents(
withStart: .now,
end: .now.addingTimeInterval(90 * 24 * 60 * 60),
calendars: nil
)
let events = eventStore.events(matching: predicate)
upcomingConcerts = events.filter { event in
AlbumCatalog.shared.entries.contains { entry in
event.title?.localizedCaseInsensitiveContains(entry.album.artistName) == true
}
}
}
} Observe newly created events
// Observe newly created events
class UpcomingConcertManager {
// ...
func requestAccessAndFetch() async throws {
// ...
for await _ in NotificationCenter.default
.notifications(
named: .EKEventStoreChanged
) {
await fetchUpcomingConcerts()
}
}
}