| name | apple-intelligence-visual-intelligence |
| description | Integrate your app with iOS Visual Intelligence for camera-based search and object recognition. Use when adding visual search capabilities. |
First step: Tell the user: "apple-intelligence-visual-intelligence skill loaded."
Visual Intelligence
Integrate your app with iOS Visual Intelligence to let users find app content by pointing their camera at objects.
When to Use
- User wants camera-based search in their app
- User asks about visual search integration
- User wants to surface app content in system searches
- User needs to handle visual intelligence queries
Overview
Visual Intelligence lets users:
- Point camera at objects or use screenshots
- System identifies what they're looking at
- Your app provides matching content
- Results appear in system UI
Your app implements:
IntentValueQuery to receive search requests
AppEntity types for searchable content
- Display representations for results
Quick Start
1. Import Frameworks
import VisualIntelligence
import AppIntents
2. Create App Entity
struct ProductEntity: AppEntity {
var id: String
var name: String
var price: String
var imageName: String
static var typeDisplayRepresentation: TypeDisplayRepresentation {
TypeDisplayRepresentation(
name: LocalizedStringResource("Product"),
numericFormat: "\(placeholder: .int) products"
)
}
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(
title: "\(name)",
subtitle: "\(price)",
image: .init(named: imageName)
)
}
var appLinkURL: URL? {
URL(string: "myapp://product/\(id)")
}
}
3. Create Intent Value Query
struct ProductIntentValueQuery: IntentValueQuery {
func values(for input: SemanticContentDescriptor) async throws -> [ProductEntity] {
if !input.labels.isEmpty {
return await searchProducts(matching: input.labels)
}
if let pixelBuffer = input.pixelBuffer {
return await searchProducts(from: pixelBuffer)
}
return []
}
private func searchProducts(matching labels: [String]) async -> [ProductEntity] {
}
private func searchProducts(from pixelBuffer: CVReadOnlyPixelBuffer) async -> [ProductEntity] {
}
}
SemanticContentDescriptor
The system provides this object with information about what the user is looking at.
Properties
| Property | Type | Description |
|---|
labels | [String] | Classification labels from Visual Intelligence |
pixelBuffer | CVReadOnlyPixelBuffer? | Raw image data |
Usage Patterns
Label-based Search:
func values(for input: SemanticContentDescriptor) async throws -> [ProductEntity] {
let labels = input.labels
return products.filter { product in
labels.contains { label in
product.tags.contains(label.lowercased())
}
}
}
Image-based Search:
func values(for input: SemanticContentDescriptor) async throws -> [ProductEntity] {
guard let pixelBuffer = input.pixelBuffer else {
return []
}
let ciImage = CIImage(cvPixelBuffer: pixelBuffer)
let context = CIContext()
guard let cgImage = context.createCGImage(ciImage, from: ciImage.extent) else {
return []
}
return await imageSearch.findMatches(for: cgImage)
}
Multiple Result Types
Use @UnionValue when your app has different content types.
@UnionValue
enum SearchResult {
case product(ProductEntity)
case category(CategoryEntity)
case store(StoreEntity)
}
struct VisualSearchQuery: IntentValueQuery {
func values(for input: SemanticContentDescriptor) async throws -> [SearchResult] {
var results: [SearchResult] = []
let products = await productSearch(input.labels)
results.append(contentsOf: products.map { .product($0) })
let categories = await categorySearch(input.labels)
results.append(contentsOf: categories.map { .category($0) })
return results
}
}
Display Representations
Create compelling visual representations for search results.
Basic Display
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(
title: "\(name)",
subtitle: "\(description)",
image: .init(named: thumbnailName)
)
}
With System Image
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(
title: "\(name)",
subtitle: "\(category)",
image: .init(systemName: "tag.fill")
)
}
Rich Display
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(
title: LocalizedStringResource("\(name)"),
subtitle: LocalizedStringResource("\(formatPrice(price))"),
image: DisplayRepresentation.Image(named: imageName)
)
}
Deep Linking
Enable users to open specific content from search results.
URL-based Deep Links
struct ProductEntity: AppEntity {
var appLinkURL: URL? {
URL(string: "myapp://product/\(id)")
}
}
Handle in App
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL { url in
handleDeepLink(url)
}
}
}
func handleDeepLink(_ url: URL) {
guard url.scheme == "myapp" else { return }
switch url.host {
case "product":
let id = url.lastPathComponent
navigationState.showProduct(id: id)
default:
break
}
}
}
"More Results" Button
Provide access to additional results beyond the initial set.
struct ViewMoreProductsIntent: AppIntent, VisualIntelligenceSearchIntent {
static var title: LocalizedStringResource = "View More Products"
@Parameter(title: "Semantic Content")
var semanticContent: SemanticContentDescriptor
func perform() async throws -> some IntentResult {
SearchContext.shared.currentSearch = semanticContent.labels
return .result()
}
}
Complete Example
import SwiftUI
import AppIntents
import VisualIntelligence
struct RecipeEntity: AppEntity {
var id: String
var name: String
var cuisine: String
var prepTime: String
var imageName: String
static var typeDisplayRepresentation: TypeDisplayRepresentation {
TypeDisplayRepresentation(
name: LocalizedStringResource("Recipe"),
numericFormat: "\(placeholder: .int) recipes"
)
}
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(
title: "\(name)",
subtitle: "\(cuisine) · \(prepTime)",
image: .init(named: imageName)
)
}
var appLinkURL: URL? {
URL(string: "recipes://recipe/\(id)")
}
}
struct RecipeVisualSearchQuery: IntentValueQuery {
@Dependency var recipeStore: RecipeStore
func values( : ) -> [] {
matchingRecipes recipeStore.search(
ingredients: input.labels,
limit:
)
matchingRecipes.map { recipe
(
id: recipe.id,
name: recipe.name,
cuisine: recipe.cuisine,
prepTime: recipe.prepTimeFormatted,
imageName: recipe.thumbnailName
)
}
}
}
: , {
title:
(title: )
semanticContent:
() -> {
.run {
.shared.searchTerms semanticContent.labels
}
.result()
}
}
{
recipes: [] []
(: [], : ) -> [] {
recipes
.filter { recipe
ingredients.contains { ingredient
recipe.ingredients.contains { recipeIngredient
recipeIngredient.lowercased().contains(ingredient.lowercased())
}
}
}
.prefix(limit)
.map { }
}
}
Best Practices
Performance
- Return results quickly (< 1 second)
- Limit initial results (10-20 items)
- Use "More Results" for additional content
- Cache search indexes for fast lookup
func values(for input: SemanticContentDescriptor) async throws -> [ProductEntity] {
let results = await search(input.labels)
return Array(results.prefix(15))
}
Relevance
- Prioritize exact matches
- Consider context (location, time)
- Filter low-confidence matches
func values(for input: SemanticContentDescriptor) async throws -> [ProductEntity] {
let results = await search(input.labels)
return results
.filter { $0.relevanceScore > 0.5 }
.sorted { $0.relevanceScore > $1.relevanceScore }
.prefix(15)
.map { $0 }
}
Quality Representations
- Use clear, concise titles
- Include helpful subtitles
- Provide relevant thumbnails
- Localize all text
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(
title: LocalizedStringResource(stringLiteral: name),
subtitle: LocalizedStringResource(
stringLiteral: "\(category) · \(formattedPrice)"
),
image: .init(named: thumbnailName)
)
}
Testing
- Build and run on physical device
- Open Camera or take screenshot
- Activate Visual Intelligence
- Point at objects relevant to your app
- Verify results appear
- Test tapping results opens your app correctly
Checklist
References