Integrate your app with iOS Visual Intelligence for camera-based search and object recognition. Use when adding visual search capabilities.
npx skills add https://github.com/rshankras/claude-code-apple-skills --skill visual-intelligence
Integrate your app with iOS Visual Intelligence to let users find app content by pointing their camera at objects.
Visual Intelligence lets users:
Your app implements:
IntentValueQuery to receive search requestsAppEntity types for searchable contentimport VisualIntelligence
import AppIntents
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)
)
}
// Deep link URL
var appLinkURL: URL? {
URL(string: "myapp://product/\(id)")
}
}
struct ProductIntentValueQuery: IntentValueQuery {
func values(for input: SemanticContentDescriptor) async throws -> [ProductEntity] {
// Search using labels
if !input.labels.isEmpty {
return await searchProducts(matching: input.labels)
}
// Search using image
if let pixelBuffer = input.pixelBuffer {
return await searchProducts(from: pixelBuffer)
}
return []
}
private func searchProducts(matching labels: [String]) async -> [ProductEntity] {
// Search your database using provided labels
// Return matching products
}
private func searchProducts(from pixelBuffer: CVReadOnlyPixelBuffer) async -> [ProductEntity] {
// Use image recognition on the pixel buffer
// Return matching products
}
}
The system provides this object with information about what the user is looking at.
| Property | Type | Description |
|----------|------|-------------|
| labels | [String] | Classification labels from Visual Intelligence |
| pixelBuffer | CVReadOnlyPixelBuffer? | Raw image data |
Label-based Search:
func values(for input: SemanticContentDescriptor) async throws -> [ProductEntity] {
// Labels like "shoe", "sneaker", "Nike" etc.
let labels = input.labels
// Search your content using these 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 []
}
// Convert to CGImage for processing
let ciImage = CIImage(cvPixelBuffer: pixelBuffer)
let context = CIContext()
guard let cgImage = context.createCGImage(ciImage, from: ciImage.extent) else {
return []
}
// Use your ML model or image matching logic
return await imageSearch.findMatches(for: cgImage)
}
Use @UnionValue when your app has different content types.
Rules (WWDC26 297):
IntentValueQuery that accepts a SemanticContentDescriptor. All result types must flow through that single query — a @UnionValue enum with one case per entity type.OpenIntent — without one, results of that type can't appear in image search.@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] = []
// Search products
let products = await productSearch(input.labels)
results.append(contentsOf: products.map { .product($0) })
// Search categories
let categories = await categorySearch(input.labels)
results.append(contentsOf: categories.map { .category($0) })
return results
}
}
Create compelling visual representations for search results.
DisplayRepresentation with an image URL, serve a thumbnail-sized image, not the full-resolution asset — smaller images load faster.// ❌ Full-res image URLs in DisplayRepresentation for multi-result responses
// ✅ Thumbnail-sized images (two-column sheet); full-width only when returning one result
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(
title: "\(name)",
subtitle: "\(description)",
image: .init(named: thumbnailName)
)
}
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(
title: "\(name)",
subtitle: "\(category)",
image: .init(systemName: "tag.fill")
)
}
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(
title: LocalizedStringResource("\(name)"),
subtitle: LocalizedStringResource("\(formatPrice(price))"),
image: DisplayRepresentation.Image(named: imageName)
)
}
Whether you're searching on device or hitting a server, the same principles apply: return results fast and ranked.
Vision-framework pattern:
GenerateImageFeaturePrintRequest feature prints for your catalog; at query time, convert the pixel buffer via VideoToolbox (VTCreateCGImageFromCVPixelBuffer) and generate just one feature print to compare.search(matching:limit: Int = 10, maxDistance: Double = 1.0).[] when nothing matches or the pixel buffer is absent — the system handles displaying an empty response. ❌ Don't pad with weak matches.// ❌ Compute feature prints at query time
// ✅ Pre-compute catalog prints; query = 1 print + threshold + sort + limit
let matches = catalogPrints
.map { entry in (entry, entry.print.distance(to: queryPrint)) }
.filter { $0.1 <= maxDistance }
.sorted { $0.1 < $1.1 }
.prefix(limit)
Vision offers more than feature prints for visual search: text extraction, barcode scanning, face detection, image classification (WWDC26 297).
Enable users to open specific content from search results.
Tapping a result runs your OpenIntent for that entity type, and its perform() runs as the app comes to the foreground:
perform(); defer heavy loading until after the view appears.// ❌ Heavy loading inside OpenIntent.perform (runs during foregrounding)
// ✅ Navigate only; load after the view appears; reuse one OpenIntent everywhere
struct ProductEntity: AppEntity {
// ... other properties
var appLinkURL: URL? {
URL(string: "myapp://product/\(id)")
}
}
@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
}
}
}
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 {
// Store search context for your app
SearchContext.shared.currentSearch = semanticContent.labels
// Return empty result - system will open your app
return .result()
}
}
The schema-based form: conform to .visualIntelligence.semanticContentSearch and the system supplies the semanticContent property automatically:
@AppIntent(schema: .visualIntelligence.semanticContentSearch)
struct SemanticContentSearchIntent: AppIntent {
static let openAppWhenRun: Bool = true
var semanticContent: SemanticContentDescriptor
func perform() async throws -> some IntentResult {
let results = try await library.search(matching: semanticContent)
await MainActor.run { AppState.shared.openSearch(with: results) }
return .result()
}
}
Rules (WWDC26 297): pre-populate the in-app search view from the captured context (never a blank screen), and use it to expose what the Visual Intelligence sheet can't — filters, categories, the full depth of your content.
import SwiftUI
import AppIntents
import VisualIntelligence
// MARK: - Entities
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)")
}
}
// MARK: - Intent Value Query
struct RecipeVisualSearchQuery: IntentValueQuery {
@Dependency var recipeStore: RecipeStore
func values(for input: SemanticContentDescriptor) async throws -> [RecipeEntity] {
// Use labels to find recipes
// Labels might include: "pasta", "tomato", "Italian", etc.
let matchingRecipes = await recipeStore.search(
ingredients: input.labels,
limit: 15
)
return matchingRecipes.map { recipe in
RecipeEntity(
id: recipe.id,
name: recipe.name,
cuisine: recipe.cuisine,
prepTime: recipe.prepTimeFormatted,
imageName: recipe.thumbnailName
)
}
}
}
// MARK: - More Results Intent
struct ViewMoreRecipesIntent: AppIntent, VisualIntelligenceSearchIntent {
static var title: LocalizedStringResource = "View More Recipes"
@Parameter(title: "Semantic Content")
var semanticContent: SemanticContentDescriptor
func perform() async throws -> some IntentResult {
// Save search context
await MainActor.run {
RecipeSearchState.shared.searchTerms = semanticContent.labels
}
return .result()
}
}
// MARK: - Recipe Store
@Observable
class RecipeStore {
private var recipes: [Recipe] = []
func search(ingredients: [String], limit: Int) async -> [Recipe] {
recipes
.filter { recipe in
ingredients.contains { ingredient in
recipe.ingredients.contains { recipeIngredient in
recipeIngredient.lowercased().contains(ingredient.lowercased())
}
}
}
.prefix(limit)
.map { $0 }
}
}
func values(for input: SemanticContentDescriptor) async throws -> [ProductEntity] {
// Limit results for quick response
let results = await search(input.labels)
return Array(results.prefix(15))
}
func values(for input: SemanticContentDescriptor) async throws -> [ProductEntity] {
let results = await search(input.labels)
// Sort by relevance score
return results
.filter { $0.relevanceScore > 0.5 }
.sorted { $0.relevanceScore > $1.relevanceScore }
.prefix(15)
.map { $0 }
}
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(
title: LocalizedStringResource(stringLiteral: name),
subtitle: LocalizedStringResource(
stringLiteral: "\(category) · \(formattedPrice)"
),
image: .init(named: thumbnailName)
)
}
Two integration directions: your app provides results (everything above), and your app receives data Visual Intelligence writes into shared system stores:
| Visual Intelligence system action | Store | Your app reads via |
|-----------------------------------|-------|--------------------|
| Create calendar events — including multiple events at once | EventKit | EKEventStore |
| Add to contacts | Contacts | CNContactStore |
| Log medical device readings (blood pressure monitors, glucose meters, weight scales) | HealthKit | HKHealthStore |
If your app already reads from these stores, Visual Intelligence becomes a source of input automatically — zero VI-specific code. One requirement: observe change notifications so VI-created data appears without a relaunch. EventKit pattern from Apple's sample: requestFullAccessToEvents() → fetch with a predicate (a 90-day window) → observe .EKEventStoreChanged notifications and refetch.
perform(), heavy loading deferred (WWDC26 297)[] when nothing matches — no weak-match padding (WWDC26 297)Integration with protocols.io API for managing scientific protocols. This skill should be used when working with protocols.io to search, create, update, or publish protocols; manage protocol steps and materials; handle discussions and comments; organize workspaces; upload and manage files; or integrate protocols.io functionality into workflows. Applicable for protocol discovery, collaborative protocol development, experiment tracking, lab protocol management, and scientific documentation.
Analyzes job descriptions and generates tailored resumes that highlight relevant experience, skills, and achievements to maximize interview chances
Generate Excalidraw diagrams from natural language descriptions. Use when asked to "create a diagram", "make a flowchart", "visualize a process", "draw a system architecture", "create a mind map", or "generate an Excalidraw file". Supports flowcharts, relationship diagrams, mind maps, and system architecture diagrams. Outputs .excalidraw JSON files that can be opened directly in Excalidraw.
Build and distribute Expo development clients locally or via TestFlight
Use when you have a written implementation plan to execute in a separate session with review checkpoints
Data structure for annotated matrices in single-cell analysis. Use when working with .h5ad files or integrating with the scverse ecosystem. This is the data format skill—for analysis workflows use scanpy; for probabilistic models use scvi-tools; for population-scale queries use cellxgene-census.
Benchling R&D platform integration. Access registry (DNA, proteins), inventory, ELN entries, workflows via API, build Benchling Apps, query Data Warehouse, for lab data management automation.
Comprehensive molecular biology toolkit. Use for sequence manipulation, file parsing (FASTA/GenBank/PDB), phylogenetics, and programmatic NCBI/PubMed access (Bio.Entrez). Best for batch processing, custom bioinformatics pipelines, BLAST automation. For quick lookups use gget; for multi-service integration use bioservices.
Take rshankras/visual-intelligence from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.