vision-framework
dpearson2699/swift-ios-skills
Detect text, faces, barcodes, and objects in iOS images and video using on-device Vision framework.
What is vision-framework?
Implement computer vision features including OCR, face detection, barcode scanning, image segmentation, object tracking, and document scanning in iOS apps. Covers both the modern Swift-native Vision API (iOS 18+) and legacy VNRequest patterns, VisionKit DataScannerViewController for live camera scanning, and Core ML model inference. Use when adding OCR, barcode scanning, face detection, or custom model inference with Vision.
- Text recognition (OCR) with language support and confidence scores
- Face detection with landmarks (eyes, nose, mouth) and pose angles
- Barcode and QR code detection across multiple symbologies
- Document scanning with structured layout understanding (tables, lists, paragraphs)
- Image segmentation and object tracking for video processing
- Core ML model inference integration via CoreMLRequest and VNCoreMLRequest
How to install vision-framework
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill vision-framework- iOS 11+ (legacy API) or iOS 18+ (modern API)
- Swift 6.3 or later
- Vision framework imported in project
- CGImage, CIImage, CVPixelBuffer, or CMSampleBuffer for input
How to use vision-framework
- 1.Import Vision framework in your Swift file
- 2.Create a request object (e.g., RecognizeTextRequest, DetectFaceRectanglesRequest)
- 3.Configure request properties (recognition level, languages, symbologies, etc.)
- 4.Call perform(on:) with your image and await the result
- 5.Process the typed observations returned (text, faces, barcodes, etc.)
- 6.Convert normalized coordinates to UIKit coordinates if displaying results
Use cases
- Extract text from images or live camera feeds for document digitization
- Scan QR codes and barcodes in retail or inventory applications
- Detect and track faces in photos or video for security or social features
- Recognize document structure (tables, lists) for automated data extraction
- Run custom Core ML models on images for specialized vision tasks
- iOS app developers implementing computer vision features
- Teams building document scanning or OCR applications
- Developers integrating barcode/QR code readers
- Engineers deploying custom Core ML models in iOS apps
- Developers targeting iOS 18+ or maintaining backward compatibility
vision-framework FAQ
Prefer the modern API (iOS 18+) for new code—it uses Swift-native types and async/await. Use legacy VNImageRequestHandler only when targeting older iOS versions or maintaining existing code.
Vision uses a normalized coordinate system with origin at bottom-left. Convert to UIKit (top-left origin) using toImageCoordinates(imageSize, origin: .upperLeft) before displaying results.
Yes. Use .fast recognition level for OCR, process CMSampleBuffer frames, and leverage stateful requests like TrackObjectRequest for video sequences.
Use CoreMLRequest (modern API) or VNCoreMLRequest (legacy) to run custom models. Pass your Core ML model and the request will handle inference on the image.
Modern API accepts Locale.Language identifiers (e.g., en-US, fr-FR). Legacy API uses string identifiers. Both support language correction via usesLanguageCorrection property.
Full instructions (SKILL.md)
Source of truth, from dpearson2699/swift-ios-skills.
name: vision-framework description: "Implement computer vision features including text recognition (OCR), face detection, barcode scanning, image segmentation, object tracking, and document scanning in iOS apps. Covers both the modern Swift-native Vision API (iOS 18+) and legacy VNRequest patterns, VisionKit DataScannerViewController for live camera scanning, and CoreMLRequest/VNCoreMLRequest for custom model inference. Use when adding OCR, barcode scanning, face detection, or custom Core ML model inference with Vision."
Vision Framework
Detect text, faces, barcodes, objects, and body poses in images and video using on-device computer vision. Patterns target iOS 26+ with Swift 6.3, backward-compatible where noted.
See references/vision-requests.md for complete code patterns and references/visionkit-scanner.md for DataScannerViewController integration.
Contents
- Two API Generations
- Request Pattern (Modern API)
- Text Recognition (OCR)
- Face Detection
- Barcode Detection
- Document Scanning (iOS 26+)
- Image Segmentation
- Object Tracking
- Other Request Types
- Core ML Integration
- VisionKit: DataScannerViewController
- Common Mistakes
- Review Checklist
- References
Two API Generations
Vision has two distinct API layers. Prefer the modern API for new code:
Swift-native request types plus try await request.perform(on:). Keep VN*,
VNImageRequestHandler, VNSequenceRequestHandler, completion handlers, and
legacy CGRect helpers inside explicit legacy fallback sections or files.
| Aspect | Modern (iOS 18+) | Legacy |
|---|---|---|
| Pattern | let result = try await request.perform(on: image) | VNImageRequestHandler + completion handler |
| Request types | Swift types — structs and classes (RecognizeTextRequest, DetectFaceRectanglesRequest) | ObjC classes (VNRecognizeTextRequest, VNDetectFaceRectanglesRequest) |
| Concurrency | Native async/await | Completion handlers or synchronous perform |
| Observations | Typed return values | Cast results from [Any] |
| Availability | iOS 18+ / macOS 15+ | iOS 11+ |
The modern API uses the ImageProcessingRequest protocol. Each request type
has a perform(on:orientation:) method that accepts CGImage, CIImage,
CVPixelBuffer, CMSampleBuffer, Data, or URL. Most requests are
structs; stateful requests such as GeneratePersonSegmentationRequest,
TrackObjectRequest, TrackRectangleRequest, and DetectTrajectoriesRequest
are final classes.
Request Pattern (Modern API)
All modern Vision requests follow the same pattern: create a request, call
perform(on:), and handle the typed result.
import Vision
func recognizeText(in image: CGImage) async throws -> [String] {
var request = RecognizeTextRequest()
request.recognitionLevel = .accurate
request.recognitionLanguages = [Locale.Language(identifier: "en-US")]
let observations = try await request.perform(on: image)
return observations.compactMap { observation in
observation.topCandidates(1).first?.string
}
}
Legacy Pattern (Pre-iOS 18)
Use VNImageRequestHandler with completion-based requests when targeting
older deployment versions.
import Vision
func recognizeTextLegacy(in image: CGImage) throws -> [String] {
var recognized: [String] = []
let request = VNRecognizeTextRequest { request, error in
guard let observations = request.results as? [VNRecognizedTextObservation] else { return }
recognized = observations.compactMap { $0.topCandidates(1).first?.string }
}
request.recognitionLevel = .accurate
let handler = VNImageRequestHandler(cgImage: image)
try handler.perform([request])
return recognized
}
Text Recognition (OCR)
Modern: RecognizeTextRequest (iOS 18+)
var request = RecognizeTextRequest()
request.recognitionLevel = .accurate // .fast for real-time
request.recognitionLanguages = [
Locale.Language(identifier: "en-US"),
Locale.Language(identifier: "fr-FR"),
]
request.usesLanguageCorrection = true
request.customWords = ["SwiftUI", "Xcode"] // domain-specific terms
let observations = try await request.perform(on: cgImage)
for observation in observations {
guard let candidate = observation.topCandidates(1).first else { continue }
let text = candidate.string
let confidence = candidate.confidence // 0.0 ... 1.0
let bounds = observation.boundingBox // NormalizedRect
}
Legacy: VNRecognizeTextRequest
let request = VNRecognizeTextRequest()
request.recognitionLevel = .accurate
request.recognitionLanguages = ["en-US", "fr-FR"]
request.usesLanguageCorrection = true
Key differences: Modern API uses Locale.Language for languages; legacy
uses string identifiers. Both support .accurate (best quality) and .fast
(real-time suitable) recognition levels.
Face Detection
Detect face rectangles, landmarks (eyes, nose, mouth), and capture quality.
// Modern API
let faceRequest = DetectFaceRectanglesRequest()
let faces = try await faceRequest.perform(on: cgImage)
for face in faces {
let boundingBox = face.boundingBox // NormalizedRect
let roll = face.roll // Measurement<UnitAngle>
let yaw = face.yaw // Measurement<UnitAngle>
}
// Landmarks (eyes, nose, mouth contours)
var landmarkRequest = DetectFaceLandmarksRequest()
let landmarkFaces = try await landmarkRequest.perform(on: cgImage)
for face in landmarkFaces {
let landmarks = face.landmarks
let leftEye = landmarks?.leftEye.points
let nose = landmarks?.nose.points
}
Coordinate System
Vision uses a normalized coordinate system with origin at the bottom-left. Convert to UIKit (top-left origin) before display:
import Vision
func imageRectForDisplay(_ rect: NormalizedRect, imageSize: CGSize) -> CGRect {
rect.toImageCoordinates(imageSize, origin: .upperLeft)
}
Barcode Detection
Detect 1D and 2D barcodes including QR codes.
var request = DetectBarcodesRequest()
let symbologies: [BarcodeSymbology] = [.qr, .ean13, .code128, .pdf417]
request.symbologies = symbologies
let barcodes = try await request.perform(on: cgImage)
for barcode in barcodes {
let payload = barcode.payloadString // decoded content
let symbology = barcode.symbology // .qr, .ean13, etc.
let bounds = barcode.boundingBox // NormalizedRect
}
Type annotate local values first, then assign request properties separately.
Document Scanning (iOS 26+)
RecognizeDocumentsRequest provides structured document reading with layout
understanding beyond basic OCR. Returns DocumentObservation objects with a
nested Container structure for paragraphs, tables, lists, and barcodes.
Currently, Vision returns one document observation for each image.
var request = RecognizeDocumentsRequest()
let documents = try await request.perform(on: cgImage)
for observation in documents {
let container = observation.document
// Full text content
let fullText = container.text
// Structured access to paragraphs
for paragraph in container.paragraphs {
let paragraphText = paragraph.text
}
// Tables and lists
for table in container.tables { /* structured table data */ }
for list in container.lists { /* structured list data */ }
// Embedded barcodes detected within the document
for barcode in container.barcodes { /* barcode data */ }
// Document title if detected
if let title = container.title { print(title) }
}
For simpler document camera scanning, use VisionKit's
VNDocumentCameraViewController which provides a full-screen camera UI with
auto-capture, perspective correction, and multi-page scanning.
Image Segmentation
Modern: GeneratePersonSegmentationRequest (iOS 18+)
var request = GeneratePersonSegmentationRequest()
request.qualityLevel = .accurate // .balanced, .fast
let mask = try await request.perform(on: cgImage)
// mask is a PixelBufferObservation with a pixelBuffer property
let maskBuffer = mask.pixelBuffer
// Apply mask using Core Image: CIFilter.blendWithMask()
Legacy: VNGeneratePersonSegmentationRequest
let request = VNGeneratePersonSegmentationRequest()
request.qualityLevel = .accurate // .balanced, .fast
request.outputPixelFormat = kCVPixelFormatType_OneComponent8
let handler = VNImageRequestHandler(cgImage: cgImage)
try handler.perform([request])
guard let mask = request.results?.first?.pixelBuffer else { return }
// Apply mask using Core Image: CIFilter.blendWithMask()
Quality levels:
.accurate-- best quality, slowest (~1s), full resolution.balanced-- good quality, moderate speed (~100ms), 960x540.fast-- lowest quality, fastest (~10ms), 256x144, suitable for real-time
Instance Segmentation (iOS 18+)
Separate masks per person for individual effects.
// Modern API (iOS 18+)
let request = GeneratePersonInstanceMaskRequest()
let observation = try await request.perform(on: cgImage)
let indices = observation.allInstances
for index in indices {
let mask = try observation.generateMask(for: IndexSet(integer: index))
// mask is a CVPixelBuffer with only this person visible
}
// Legacy API (iOS 17+)
let request = VNGeneratePersonInstanceMaskRequest()
let handler = VNImageRequestHandler(cgImage: cgImage)
try handler.perform([request])
guard let result = request.results?.first else { return }
let indices = result.allInstances
for index in indices {
let instanceMask = try result.generateMaskedImage(
ofInstances: IndexSet(integer: index),
from: handler,
croppedToInstancesExtent: false
)
}
See references/vision-requests.md for mask composition and Core Image filter integration patterns.
Object Tracking
Modern: TrackObjectRequest (iOS 18+)
TrackObjectRequest is a stateful request that maintains tracking context
across frames.
// Initialize with a detected object's bounding box
let initialObservation = DetectedObjectObservation(boundingBox: detectedBox)
let request = TrackObjectRequest(detectedObject: initialObservation)
for pixelBuffer in framePixelBuffers {
let results = try await request.perform(on: pixelBuffer)
if let tracked = results.first {
let updatedBounds = tracked.boundingBox // NormalizedRect
}
}
Modern TrackObjectRequest has no trackingLevel or qualityLevel.
Legacy: VNTrackObjectRequest
let trackRequest = VNTrackObjectRequest(detectedObjectObservation: initialObservation)
trackRequest.trackingLevel = .accurate
let sequenceHandler = VNSequenceRequestHandler()
// For each frame:
try sequenceHandler.perform([trackRequest], on: pixelBuffer)
if let result = trackRequest.results?.first {
let updatedBounds = result.boundingBox
trackRequest.inputObservation = result
}
Other Request Types
Vision provides additional requests covered in references/vision-requests.md:
| Request | Purpose |
|---|---|
ClassifyImageRequest | Classify scene content (outdoor, food, animal, etc.) |
GenerateAttentionBasedSaliencyImageRequest | Single SaliencyImageObservation for where viewers focus attention |
GenerateObjectnessBasedSaliencyImageRequest | Single SaliencyImageObservation for object-like regions |
GenerateForegroundInstanceMaskRequest | Foreground object segmentation (not person-specific) |
DetectRectanglesRequest | Detect rectangular shapes (documents, cards, screens) |
DetectHorizonRequest | Detect horizon angle for auto-leveling photos |
DetectHumanBodyPoseRequest | Detect body joints (shoulders, elbows, knees) |
DetectHumanBodyPose3DRequest | 3D human body pose estimation |
DetectHumanHandPoseRequest | Detect hand joints and finger positions |
DetectAnimalBodyPoseRequest | Detect animal body joint positions |
DetectFaceCaptureQualityRequest | Face capture quality scoring (0–1) for photo selection |
TrackRectangleRequest | Track rectangular objects across video frames |
TrackOpticalFlowRequest | Optical flow between video frames |
DetectTrajectoriesRequest | Detect object trajectories in video |
All modern request types above are iOS 18+ / macOS 15+.
Core ML Integration
Run custom Core ML models through Vision for automatic image preprocessing.
Vision runs already-prepared models with CoreMLRequest or VNCoreMLRequest;
hand conversion, profiling, packaging, and lifecycle decisions to coreml.
import CoreML
import Vision
// Modern API (iOS 18+): CoreMLRequest takes a CoreMLModelContainer.
let model = try MLModel(contentsOf: modelURL)
let container = try CoreMLModelContainer(model: model, featureProvider: nil)
let request = CoreMLRequest(model: container)
let results = try await request.perform(on: cgImage)
// Classification model
if let classification = results.first as? ClassificationObservation {
let label = classification.identifier
let confidence = classification.confidence
}
CoreMLModelContainer is the public iOS 18+ Vision container for
CoreMLRequest: load an MLModel, wrap it with
CoreMLModelContainer(model:featureProvider:), then pass that container to
CoreMLRequest(model:). State result mapping when reviewing Core ML through
Vision: classifiers produce ClassificationObservation, image outputs produce
PixelBufferObservation, and general predictors produce CoreMLFeatureValueObservation.
// Legacy API
let vnModel = try VNCoreMLModel(for: model)
let request = VNCoreMLRequest(model: vnModel) { request, error in
guard let results = request.results as? [VNClassificationObservation] else { return }
let topResult = results.first
}
let handler = VNImageRequestHandler(cgImage: cgImage)
try handler.perform([request])
VisionKit: DataScannerViewController
DataScannerViewController provides a live camera scanner for text and
barcodes; see references/visionkit-scanner.md. VisionKit uses
VNBarcodeSymbology; modern DetectBarcodesRequest uses BarcodeSymbology.
Quick Start
import AVFoundation
import Vision
import VisionKit
@MainActor
func presentScanner() async {
// Add NSCameraUsageDescription before requesting camera access.
guard await AVCaptureDevice.requestAccess(for: .video) else { return }
guard DataScannerViewController.isSupported,
DataScannerViewController.isAvailable else { return }
let scannerSymbologies: [VNBarcodeSymbology] = [.qr, .ean13]
let scanner = DataScannerViewController(
recognizedDataTypes: [
.text(languages: ["en"]),
.barcode(symbologies: scannerSymbologies)
],
qualityLevel: .balanced,
recognizesMultipleItems: true,
isHighFrameRateTrackingEnabled: true,
isHighlightingEnabled: true
)
scanner.delegate = self
present(scanner, animated: true) {
// Start scanning after presentation, on the main actor.
try? scanner.startScanning()
}
}
SwiftUI Integration
Wrap DataScannerViewController in UIViewControllerRepresentable and start in
updateUIViewController with Task { @MainActor in try? controller.startScanning() }; see references/visionkit-scanner.md.
Common Mistakes
DON'T: Use the legacy VNImageRequestHandler API for new iOS 18+ projects.
DO: Use modern Swift-native requests with perform(on:) and async/await.
Why: Modern API provides type safety, better Swift concurrency support, and cleaner error handling.
DON'T: Forget to convert normalized coordinates before drawing bounding boxes.
DO: Use NormalizedRect.toImageCoordinates(_:origin:) for modern observations, or VNImageRectForNormalizedRect(_:_:_:) for legacy CGRect observations.
Why: Vision uses normalized coordinates (0...1) with bottom-left origin; UIKit uses points with top-left origin.
DON'T: Run Vision requests on the main thread. DO: Perform requests on a background thread or use async/await from a detached task. Why: Image analysis is CPU/GPU-intensive and blocks the UI if run on the main actor.
DON'T: Use .accurate recognition level for real-time camera feeds.
DO: Use .fast for live video, .accurate for still images or offline processing.
Why: Accurate recognition is too slow for 30fps video; fast recognition trades quality for speed.
DON'T: Treat every Vision observation as having the same properties. DO: Check each observation type for its bounding box, confidence, payload, mask, or angle fields before writing shared helpers. Why: Modern Vision returns strongly typed observations, and result shapes vary by request.
DON'T: Recreate stateful tracking requests for each video frame.
DO: Keep the same modern TrackObjectRequest instance, or use VNSequenceRequestHandler with legacy tracking requests.
Why: Tracking relies on temporal context across frames.
DON'T: Request all barcode symbologies when you only need QR codes. DO: Specify only the symbologies you need in the request. Why: Fewer symbologies means faster detection and fewer false positives.
DON'T: Assume DataScannerViewController is available on all devices.
DO: Check both isSupported (hardware) and isAvailable (user permissions) before presenting.
Why: Requires A12+ chip; isAvailable also checks camera access authorization.
Review Checklist
- Uses modern Vision API (iOS 18+) unless targeting older deployments
- Vision requests run off the main thread (async/await or background queue)
- Normalized coordinates converted before UI display
- Confidence threshold applied to filter low-quality observations
- Recognition level matches use case (
.fastfor video,.accuratefor stills) - Language hints set for text recognition when input language is known
- Barcode symbologies limited to only those needed
-
DataScannerViewControlleravailability checked before presentation - Camera usage description (
NSCameraUsageDescription) in Info.plist for VisionKit - VisionKit camera access requested before presentation and scanning started after presentation
- Person segmentation quality level appropriate for use case
- Stateful tracking request or
VNSequenceRequestHandlerpreserved across video frames - Error handling covers request failures and empty results
References
- Vision request patterns: references/vision-requests.md
- VisionKit scanner integration: references/visionkit-scanner.md
- Apple docs: Vision | VisionKit | RecognizeTextRequest | DataScannerViewController | CoreMLRequest | CoreMLModelContainer
Related skills
More from dpearson2699/swift-ios-skills and the wider catalog.

weatherkit
Fetch WeatherKit forecasts, alerts, and attribution for iOS 18+ apps using Swift 6.3.

widgetkit
Build Home Screen, Lock Screen, Control Center, and StandBy widgets for iOS with timeline providers and interactive controls.

accessorysetupkit
Privacy-preserving Bluetooth and Wi-Fi accessory discovery and setup for iOS 18+

activitykit
Build real-time Lock Screen and Dynamic Island Live Activities for iOS with ActivityKit.

tech-news-digest
Generate tech news digests with unified source model, quality scoring, and multi-format output. Six-source data collection from RSS feeds, Twitter/X KOLs, GitHub releases, GitHub Trending, Reddit, and web search. Pipeline-based scripts with retry mechanisms and deduplication. Supports Discord, email, and markdown templates.

pdf-to-markdown
[Document Processing] Use when you need to convert PDF files to Markdown with support for native text PDFs and scanned documents (OCR).