@devo-bmad-custom/agent-orchestration 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/package.json +4 -2
  2. package/src/.agents/skills/tmux-commands/SKILL.md +1 -1
  3. package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/vision-framework/SKILL.md +475 -0
  4. package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/vision-framework/references/vision-requests.md +736 -0
  5. package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/vision-framework/references/visionkit-scanner.md +738 -0
  6. package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/weatherkit/SKILL.md +410 -0
  7. package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/weatherkit/references/weatherkit-patterns.md +567 -0
  8. package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/widgetkit/SKILL.md +497 -0
  9. package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/widgetkit/references/widgetkit-advanced.md +871 -0
  10. package/src/.agents/skills/ui-ux-pro-custom/data/typography.csv +58 -0
  11. package/src/.agents/skills/ui-ux-pro-custom/data/ui-reasoning.csv +101 -0
  12. package/src/.agents/skills/ui-ux-pro-custom/data/ux-guidelines.csv +100 -0
  13. package/src/.agents/skills/ui-ux-pro-custom/data/web-interface.csv +31 -0
  14. package/src/.agents/skills/ui-ux-pro-custom/scripts/core.py +253 -0
  15. package/src/.agents/skills/ui-ux-pro-custom/scripts/design_system.py +1067 -0
  16. package/src/.agents/skills/ui-ux-pro-custom/scripts/search.py +114 -0
  17. package/src/.agents/skills/ux-audit/SKILL.md +151 -0
  18. package/src/.agents/skills/websocket-engineer/SKILL.md +168 -0
  19. package/src/.agents/skills/websocket-engineer/references/alternatives.md +391 -0
  20. package/src/.agents/skills/websocket-engineer/references/patterns.md +400 -0
  21. package/src/.agents/skills/websocket-engineer/references/protocol.md +195 -0
  22. package/src/.agents/skills/websocket-engineer/references/scaling.md +333 -0
  23. package/src/.agents/skills/websocket-engineer/references/security.md +474 -0
  24. package/src/.agents/skills/writing-skills/SKILL.md +655 -0
  25. package/src/.agents/skills/writing-skills/anthropic-best-practices.md +1150 -0
  26. package/src/.agents/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
  27. package/src/.agents/skills/writing-skills/graphviz-conventions.dot +172 -0
  28. package/src/.agents/skills/writing-skills/persuasion-principles.md +187 -0
  29. package/src/.agents/skills/writing-skills/render-graphs.js +168 -0
  30. package/src/.agents/skills/writing-skills/testing-skills-with-subagents.md +384 -0
  31. package/src/.claude/commands/bmad-master.md +15 -0
  32. package/src/.claude/commands/bmad-review-dry-loop.md +15 -0
  33. package/src/.claude/commands/bmad-review-dry.md +15 -0
  34. package/src/.claude/commands/bmad-review-security-loop.md +15 -0
  35. package/src/.claude/commands/bmad-review-security.md +15 -0
  36. package/src/.claude/commands/bmad-review-ui-loop.md +15 -0
  37. package/src/.claude/commands/bmad-review-ui.md +15 -0
  38. package/src/.claude/commands/bmad-track-compact.md +19 -0
  39. package/src/.claude/commands/bmad-track-extended.md +19 -0
  40. package/src/.claude/commands/bmad-track-large.md +19 -0
  41. package/src/.claude/commands/bmad-track-medium.md +19 -0
  42. package/src/.claude/commands/bmad-track-nano.md +19 -0
  43. package/src/.claude/commands/bmad-track-rv.md +18 -0
  44. package/src/.claude/commands/bmad-track-small.md +19 -0
  45. package/src/.claude/commands/bmad-triage.md +15 -0
  46. package/src/.claude/commands/master-orchestrator.md +15 -0
  47. package/src/_memory/master-orchestrator-sidecar/docs-index.md +3 -0
  48. package/src/_memory/master-orchestrator-sidecar/instructions.md +2616 -0
  49. package/src/_memory/master-orchestrator-sidecar/memories.md +8 -0
  50. package/src/_memory/master-orchestrator-sidecar/session-state.md +15 -0
  51. package/src/_memory/master-orchestrator-sidecar/triage-history.md +3 -0
  52. package/src/_memory/master-orchestrator-sidecar/workflows-overview.html +1230 -0
  53. package/src/core/agents/master-orchestrator.md +54 -0
  54. package/src/docs/dev/tmux/actions_popup.py +291 -0
  55. package/src/docs/dev/tmux/actions_popup.sh +110 -0
  56. package/src/docs/dev/tmux/claude_usage.sh +15 -0
  57. package/src/docs/dev/tmux/colors.conf +26 -0
  58. package/src/docs/dev/tmux/cpu_usage.sh +7 -0
  59. package/src/docs/dev/tmux/dispatch.sh +10 -0
  60. package/src/docs/dev/tmux/float_init.sh +13 -0
  61. package/src/docs/dev/tmux/float_term.sh +23 -0
  62. package/src/docs/dev/tmux/open_clip.sh +14 -0
  63. package/src/docs/dev/tmux/paste_claude.sh +26 -0
  64. package/src/docs/dev/tmux/paste_clipboard.sh +13 -0
  65. package/src/docs/dev/tmux/paste_image_wrapper.sh +98 -0
  66. package/src/docs/dev/tmux/ram_usage.sh +3 -0
  67. package/src/docs/dev/tmux/title_sync.sh +54 -0
  68. package/src/docs/dev/tmux/tmux-setup.md +867 -0
  69. package/src/docs/dev/tmux/tmux-test.sh +255 -0
  70. package/src/docs/dev/tmux/tmux.conf +127 -0
  71. package/src/docs/dev/tmux/xclip +18 -0
@@ -0,0 +1,738 @@
1
+ # VisionKit Scanner Patterns
2
+
3
+ Complete implementation patterns for DataScannerViewController and
4
+ VNDocumentCameraViewController covering availability checking, configuration,
5
+ SwiftUI integration, delegate handling, custom overlays, and camera permissions.
6
+ All patterns target iOS 26+ with Swift 6.2 unless noted.
7
+
8
+ ## Contents
9
+ - Camera Permission Setup
10
+ - DataScannerViewController
11
+ - Delegate Methods
12
+ - SwiftUI Integration
13
+ - Custom Overlay UI
14
+ - VNDocumentCameraViewController
15
+
16
+ ## Camera Permission Setup
17
+
18
+ Add the camera usage description to Info.plist before using any scanner:
19
+
20
+ ```xml
21
+ <key>NSCameraUsageDescription</key>
22
+ <string>Camera access is needed to scan text and barcodes.</string>
23
+ ```
24
+
25
+ Request permission before presenting the scanner:
26
+
27
+ ```swift
28
+ import AVFoundation
29
+
30
+ func requestCameraAccess() async -> Bool {
31
+ let status = AVCaptureDevice.authorizationStatus(for: .video)
32
+ switch status {
33
+ case .authorized:
34
+ return true
35
+ case .notDetermined:
36
+ return await AVCaptureDevice.requestAccess(for: .video)
37
+ case .denied, .restricted:
38
+ return false
39
+ @unknown default:
40
+ return false
41
+ }
42
+ }
43
+ ```
44
+
45
+ ## DataScannerViewController
46
+
47
+ `DataScannerViewController` provides a full-screen live camera scanner for text
48
+ and barcodes with built-in highlighting and interaction. Available on devices
49
+ with an A12 chip or later (iOS 16+).
50
+
51
+ ### Availability Checking
52
+
53
+ Always check both hardware support and runtime availability before presenting.
54
+
55
+ ```swift
56
+ import VisionKit
57
+
58
+ func canUseDataScanner() -> Bool {
59
+ // Hardware check: requires A12 Bionic or later
60
+ guard DataScannerViewController.isSupported else {
61
+ return false
62
+ }
63
+ // Runtime check: camera authorized and not restricted
64
+ guard DataScannerViewController.isAvailable else {
65
+ return false
66
+ }
67
+ return true
68
+ }
69
+ ```
70
+
71
+ `isSupported` checks hardware capability (A12+). `isAvailable` checks that the
72
+ camera is authorized and not restricted by device management. Both must be true.
73
+
74
+ ### Configuration and Initialization
75
+
76
+ ```swift
77
+ import VisionKit
78
+
79
+ func createTextScanner() -> DataScannerViewController {
80
+ DataScannerViewController(
81
+ recognizedDataTypes: [
82
+ .text(languages: ["en"]),
83
+ ],
84
+ qualityLevel: .balanced,
85
+ recognizesMultipleItems: true,
86
+ isHighFrameRateTrackingEnabled: true,
87
+ isPinchToZoomEnabled: true,
88
+ isGuidanceEnabled: true,
89
+ isHighlightingEnabled: true
90
+ )
91
+ }
92
+
93
+ func createBarcodeScanner() -> DataScannerViewController {
94
+ DataScannerViewController(
95
+ recognizedDataTypes: [
96
+ .barcode(symbologies: [.qr, .ean13, .code128]),
97
+ ],
98
+ qualityLevel: .fast,
99
+ recognizesMultipleItems: false,
100
+ isHighFrameRateTrackingEnabled: false,
101
+ isPinchToZoomEnabled: false,
102
+ isGuidanceEnabled: true,
103
+ isHighlightingEnabled: true
104
+ )
105
+ }
106
+
107
+ func createMixedScanner() -> DataScannerViewController {
108
+ DataScannerViewController(
109
+ recognizedDataTypes: [
110
+ .text(languages: ["en"]),
111
+ .barcode(symbologies: [.qr, .ean13]),
112
+ ],
113
+ qualityLevel: .balanced,
114
+ recognizesMultipleItems: true,
115
+ isHighFrameRateTrackingEnabled: true,
116
+ isPinchToZoomEnabled: true,
117
+ isGuidanceEnabled: true,
118
+ isHighlightingEnabled: true
119
+ )
120
+ }
121
+ ```
122
+
123
+ ### Recognized Data Types
124
+
125
+ ```swift
126
+ // Text with language hints
127
+ let textType: DataScannerViewController.RecognizedDataType =
128
+ .text(languages: ["en", "fr", "de"])
129
+
130
+ // Text filtered by content type
131
+ let emailType: DataScannerViewController.RecognizedDataType =
132
+ .text(textContentType: .emailAddress)
133
+ let urlType: DataScannerViewController.RecognizedDataType =
134
+ .text(textContentType: .URL)
135
+ let phoneType: DataScannerViewController.RecognizedDataType =
136
+ .text(textContentType: .telephoneNumber)
137
+ let addressType: DataScannerViewController.RecognizedDataType =
138
+ .text(textContentType: .fullAddress)
139
+ let flightType: DataScannerViewController.RecognizedDataType =
140
+ .text(textContentType: .flightNumber)
141
+ let trackingType: DataScannerViewController.RecognizedDataType =
142
+ .text(textContentType: .shipmentTrackingNumber)
143
+
144
+ // Barcode with specific symbologies
145
+ let qrOnly: DataScannerViewController.RecognizedDataType =
146
+ .barcode(symbologies: [.qr])
147
+ let retailBarcodes: DataScannerViewController.RecognizedDataType =
148
+ .barcode(symbologies: [.ean8, .ean13, .upce, .code128])
149
+ ```
150
+
151
+ ### Quality Levels
152
+
153
+ | Level | Use Case | Notes |
154
+ |---|---|---|
155
+ | `.fast` | Barcode scanning, quick text grab | Lowest latency |
156
+ | `.balanced` | General purpose text + barcode | Default choice |
157
+ | `.accurate` | Detailed OCR, small text | Higher latency |
158
+
159
+ ### Starting and Stopping
160
+
161
+ ```swift
162
+ func presentScanner(_ scanner: DataScannerViewController,
163
+ from presenter: UIViewController) {
164
+ scanner.delegate = presenter as? DataScannerViewControllerDelegate
165
+ presenter.present(scanner, animated: true) {
166
+ try? scanner.startScanning()
167
+ }
168
+ }
169
+
170
+ func dismissScanner(_ scanner: DataScannerViewController) {
171
+ scanner.stopScanning()
172
+ scanner.dismiss(animated: true)
173
+ }
174
+ ```
175
+
176
+ ## Delegate Methods
177
+
178
+ Implement `DataScannerViewControllerDelegate` to handle recognized items and
179
+ scanner lifecycle events.
180
+
181
+ ```swift
182
+ import VisionKit
183
+
184
+ final class ScannerCoordinator: NSObject, DataScannerViewControllerDelegate {
185
+
186
+ var onTextRecognized: ((String) -> Void)?
187
+ var onBarcodeRecognized: ((String, VNBarcodeSymbology) -> Void)?
188
+
189
+ // Called when the user taps on a recognized item
190
+ func dataScanner(
191
+ _ scanner: DataScannerViewController,
192
+ didTapOn item: RecognizedItem
193
+ ) {
194
+ switch item {
195
+ case .text(let text):
196
+ onTextRecognized?(text.transcript)
197
+ case .barcode(let barcode):
198
+ if let payload = barcode.payloadStringValue {
199
+ onBarcodeRecognized?(payload, barcode.observation.symbology)
200
+ }
201
+ @unknown default:
202
+ break
203
+ }
204
+ }
205
+
206
+ // Called when new items appear in the camera view
207
+ func dataScanner(
208
+ _ scanner: DataScannerViewController,
209
+ didAdd addedItems: [RecognizedItem],
210
+ allItems: [RecognizedItem]
211
+ ) {
212
+ for item in addedItems {
213
+ switch item {
214
+ case .text(let text):
215
+ print("New text: \(text.transcript)")
216
+ case .barcode(let barcode):
217
+ print("New barcode: \(barcode.payloadStringValue ?? "nil")")
218
+ @unknown default:
219
+ break
220
+ }
221
+ }
222
+ }
223
+
224
+ // Called when items are updated (position or content changes)
225
+ func dataScanner(
226
+ _ scanner: DataScannerViewController,
227
+ didUpdate updatedItems: [RecognizedItem],
228
+ allItems: [RecognizedItem]
229
+ ) {
230
+ // Handle position or content updates
231
+ }
232
+
233
+ // Called when items leave the camera view
234
+ func dataScanner(
235
+ _ scanner: DataScannerViewController,
236
+ didRemove removedItems: [RecognizedItem],
237
+ allItems: [RecognizedItem]
238
+ ) {
239
+ // Clean up UI for removed items
240
+ }
241
+
242
+ // Called when the scanner becomes unavailable (e.g., camera revoked)
243
+ func dataScannerDidChangeUnavailabilityReasons(
244
+ _ scanner: DataScannerViewController
245
+ ) {
246
+ // Handle unavailability -- dismiss or show fallback
247
+ }
248
+ }
249
+ ```
250
+
251
+ ### Async Sequence for Recognized Items
252
+
253
+ Use `recognizedItems` for a reactive stream of all currently visible items:
254
+
255
+ ```swift
256
+ func observeRecognizedItems(_ scanner: DataScannerViewController) async {
257
+ for await items in scanner.recognizedItems {
258
+ let texts = items.compactMap { item -> String? in
259
+ guard case .text(let text) = item else { return nil }
260
+ return text.transcript
261
+ }
262
+ let barcodes = items.compactMap { item -> String? in
263
+ guard case .barcode(let barcode) = item else { return nil }
264
+ return barcode.payloadStringValue
265
+ }
266
+ await MainActor.run {
267
+ // Update UI with current texts and barcodes
268
+ }
269
+ }
270
+ }
271
+ ```
272
+
273
+ ### Capturing a Photo
274
+
275
+ Capture a still image from the scanner for further processing:
276
+
277
+ ```swift
278
+ func captureAndProcess(_ scanner: DataScannerViewController) async throws {
279
+ let photo = try await scanner.capturePhoto()
280
+ // photo is a UIImage -- process with Vision or save
281
+ }
282
+ ```
283
+
284
+ ## SwiftUI Integration
285
+
286
+ Wrap `DataScannerViewController` in `UIViewControllerRepresentable` for use
287
+ in SwiftUI views.
288
+
289
+ ### Full DataScanner Representable
290
+
291
+ ```swift
292
+ import SwiftUI
293
+ import VisionKit
294
+
295
+ struct DataScannerRepresentable: UIViewControllerRepresentable {
296
+ let recognizedDataTypes: Set<DataScannerViewController.RecognizedDataType>
297
+ let qualityLevel: DataScannerViewController.QualityLevel
298
+ let recognizesMultipleItems: Bool
299
+ @Binding var recognizedText: [String]
300
+ @Binding var recognizedBarcodes: [String]
301
+
302
+ func makeUIViewController(context: Context) -> DataScannerViewController {
303
+ let scanner = DataScannerViewController(
304
+ recognizedDataTypes: recognizedDataTypes,
305
+ qualityLevel: qualityLevel,
306
+ recognizesMultipleItems: recognizesMultipleItems,
307
+ isHighFrameRateTrackingEnabled: true,
308
+ isPinchToZoomEnabled: true,
309
+ isGuidanceEnabled: true,
310
+ isHighlightingEnabled: true
311
+ )
312
+ scanner.delegate = context.coordinator
313
+ return scanner
314
+ }
315
+
316
+ func updateUIViewController(
317
+ _ controller: DataScannerViewController,
318
+ context: Context
319
+ ) {
320
+ // No dynamic updates needed
321
+ }
322
+
323
+ func makeCoordinator() -> Coordinator {
324
+ Coordinator(parent: self)
325
+ }
326
+
327
+ static func dismantleUIViewController(
328
+ _ controller: DataScannerViewController,
329
+ coordinator: Coordinator
330
+ ) {
331
+ controller.stopScanning()
332
+ }
333
+
334
+ @MainActor
335
+ final class Coordinator: NSObject, DataScannerViewControllerDelegate {
336
+ let parent: DataScannerRepresentable
337
+
338
+ init(parent: DataScannerRepresentable) {
339
+ self.parent = parent
340
+ }
341
+
342
+ func dataScanner(
343
+ _ scanner: DataScannerViewController,
344
+ didTapOn item: RecognizedItem
345
+ ) {
346
+ switch item {
347
+ case .text(let text):
348
+ parent.recognizedText.append(text.transcript)
349
+ case .barcode(let barcode):
350
+ if let payload = barcode.payloadStringValue {
351
+ parent.recognizedBarcodes.append(payload)
352
+ }
353
+ @unknown default:
354
+ break
355
+ }
356
+ }
357
+
358
+ func dataScanner(
359
+ _ scanner: DataScannerViewController,
360
+ didAdd addedItems: [RecognizedItem],
361
+ allItems: [RecognizedItem]
362
+ ) {
363
+ // Handle newly recognized items
364
+ }
365
+
366
+ func dataScanner(
367
+ _ scanner: DataScannerViewController,
368
+ didUpdate updatedItems: [RecognizedItem],
369
+ allItems: [RecognizedItem]
370
+ ) {
371
+ // Handle item updates
372
+ }
373
+
374
+ func dataScanner(
375
+ _ scanner: DataScannerViewController,
376
+ didRemove removedItems: [RecognizedItem],
377
+ allItems: [RecognizedItem]
378
+ ) {
379
+ // Handle removed items
380
+ }
381
+ }
382
+ }
383
+ ```
384
+
385
+ ### SwiftUI Scanner View
386
+
387
+ ```swift
388
+ import SwiftUI
389
+ import VisionKit
390
+
391
+ struct ScannerView: View {
392
+ @State private var recognizedText: [String] = []
393
+ @State private var recognizedBarcodes: [String] = []
394
+ @State private var isShowingScanner = false
395
+
396
+ var body: some View {
397
+ VStack {
398
+ if DataScannerViewController.isSupported {
399
+ Button("Scan") {
400
+ isShowingScanner = true
401
+ }
402
+ .fullScreenCover(isPresented: $isShowingScanner) {
403
+ NavigationStack {
404
+ DataScannerRepresentable(
405
+ recognizedDataTypes: [
406
+ .text(languages: ["en"]),
407
+ .barcode(symbologies: [.qr]),
408
+ ],
409
+ qualityLevel: .balanced,
410
+ recognizesMultipleItems: true,
411
+ recognizedText: $recognizedText,
412
+ recognizedBarcodes: $recognizedBarcodes
413
+ )
414
+ .ignoresSafeArea()
415
+ .toolbar {
416
+ ToolbarItem(placement: .cancellationAction) {
417
+ Button("Done") {
418
+ isShowingScanner = false
419
+ }
420
+ }
421
+ }
422
+ }
423
+ }
424
+ } else {
425
+ ContentUnavailableView(
426
+ "Scanner Not Available",
427
+ systemImage: "camera.fill",
428
+ description: Text("This device does not support scanning.")
429
+ )
430
+ }
431
+
432
+ List {
433
+ Section("Text") {
434
+ ForEach(recognizedText, id: \.self) { text in
435
+ Text(text)
436
+ }
437
+ }
438
+ Section("Barcodes") {
439
+ ForEach(recognizedBarcodes, id: \.self) { barcode in
440
+ Text(barcode)
441
+ }
442
+ }
443
+ }
444
+ }
445
+ }
446
+ }
447
+ ```
448
+
449
+ ### Starting the Scanner After Presentation
450
+
451
+ The scanner must be started after the view controller is fully presented.
452
+ Use `onAppear` with a coordinator flag or start in the completion handler:
453
+
454
+ ```swift
455
+ struct AutoStartScannerRepresentable: UIViewControllerRepresentable {
456
+ func makeUIViewController(context: Context) -> DataScannerViewController {
457
+ let scanner = DataScannerViewController(
458
+ recognizedDataTypes: [.text(languages: ["en"])],
459
+ qualityLevel: .balanced,
460
+ recognizesMultipleItems: false,
461
+ isHighFrameRateTrackingEnabled: true,
462
+ isHighlightingEnabled: true
463
+ )
464
+ scanner.delegate = context.coordinator
465
+ // Start scanning after a brief delay to ensure presentation is complete
466
+ Task { @MainActor in
467
+ try? scanner.startScanning()
468
+ }
469
+ return scanner
470
+ }
471
+
472
+ func updateUIViewController(
473
+ _ controller: DataScannerViewController,
474
+ context: Context
475
+ ) {}
476
+
477
+ func makeCoordinator() -> ScannerCoordinator {
478
+ ScannerCoordinator()
479
+ }
480
+
481
+ static func dismantleUIViewController(
482
+ _ controller: DataScannerViewController,
483
+ coordinator: ScannerCoordinator
484
+ ) {
485
+ controller.stopScanning()
486
+ }
487
+ }
488
+ ```
489
+
490
+ ## Custom Overlay UI
491
+
492
+ Add custom views on top of the scanner for region-of-interest indicators,
493
+ instructions, or result display.
494
+
495
+ ### Overlay with Region of Interest
496
+
497
+ ```swift
498
+ struct ScannerWithOverlay: View {
499
+ @State private var isShowingScanner = false
500
+ @State private var lastScannedText = ""
501
+
502
+ var body: some View {
503
+ ZStack {
504
+ AutoStartScannerRepresentable()
505
+ .ignoresSafeArea()
506
+
507
+ VStack {
508
+ // Top instruction bar
509
+ Text("Point camera at text or barcode")
510
+ .font(.subheadline)
511
+ .padding(.horizontal, 16)
512
+ .padding(.vertical, 8)
513
+ .background(.ultraThinMaterial, in: Capsule())
514
+ .padding(.top, 60)
515
+
516
+ Spacer()
517
+
518
+ // Scan region indicator
519
+ RoundedRectangle(cornerRadius: 12)
520
+ .strokeBorder(.white.opacity(0.6), lineWidth: 2)
521
+ .frame(width: 280, height: 180)
522
+
523
+ Spacer()
524
+
525
+ // Result display
526
+ if !lastScannedText.isEmpty {
527
+ Text(lastScannedText)
528
+ .font(.body)
529
+ .padding()
530
+ .frame(maxWidth: .infinity)
531
+ .background(.ultraThinMaterial)
532
+ .clipShape(RoundedRectangle(cornerRadius: 12))
533
+ .padding()
534
+ }
535
+ }
536
+ }
537
+ }
538
+ }
539
+ ```
540
+
541
+ ## VNDocumentCameraViewController
542
+
543
+ `VNDocumentCameraViewController` provides a full-screen document camera with
544
+ auto-capture, perspective correction, and multi-page scanning. Available on
545
+ all devices running iOS 13+.
546
+
547
+ ### UIKit Presentation
548
+
549
+ ```swift
550
+ import VisionKit
551
+
552
+ final class DocumentScannerPresenter: NSObject,
553
+ VNDocumentCameraViewControllerDelegate
554
+ {
555
+ weak var presenter: UIViewController?
556
+
557
+ func showDocumentScanner() {
558
+ let scanner = VNDocumentCameraViewController()
559
+ scanner.delegate = self
560
+ presenter?.present(scanner, animated: true)
561
+ }
562
+
563
+ func documentCameraViewController(
564
+ _ controller: VNDocumentCameraViewController,
565
+ didFinishWith scan: VNDocumentCameraScan
566
+ ) {
567
+ controller.dismiss(animated: true)
568
+ for pageIndex in 0..<scan.pageCount {
569
+ let pageImage = scan.imageOfPage(at: pageIndex)
570
+ // Process each scanned page image
571
+ }
572
+ }
573
+
574
+ func documentCameraViewControllerDidCancel(
575
+ _ controller: VNDocumentCameraViewController
576
+ ) {
577
+ controller.dismiss(animated: true)
578
+ }
579
+
580
+ func documentCameraViewController(
581
+ _ controller: VNDocumentCameraViewController,
582
+ didFailWithError error: Error
583
+ ) {
584
+ controller.dismiss(animated: true)
585
+ // Handle scanning error
586
+ }
587
+ }
588
+ ```
589
+
590
+ ### SwiftUI Document Scanner
591
+
592
+ ```swift
593
+ import SwiftUI
594
+ import VisionKit
595
+
596
+ struct DocumentScannerRepresentable: UIViewControllerRepresentable {
597
+ @Binding var scannedImages: [UIImage]
598
+ @Environment(\.dismiss) private var dismiss
599
+
600
+ func makeUIViewController(context: Context) -> VNDocumentCameraViewController {
601
+ let scanner = VNDocumentCameraViewController()
602
+ scanner.delegate = context.coordinator
603
+ return scanner
604
+ }
605
+
606
+ func updateUIViewController(
607
+ _ controller: VNDocumentCameraViewController,
608
+ context: Context
609
+ ) {}
610
+
611
+ func makeCoordinator() -> Coordinator {
612
+ Coordinator(parent: self)
613
+ }
614
+
615
+ @MainActor
616
+ final class Coordinator: NSObject, VNDocumentCameraViewControllerDelegate {
617
+ let parent: DocumentScannerRepresentable
618
+
619
+ init(parent: DocumentScannerRepresentable) {
620
+ self.parent = parent
621
+ }
622
+
623
+ func documentCameraViewController(
624
+ _ controller: VNDocumentCameraViewController,
625
+ didFinishWith scan: VNDocumentCameraScan
626
+ ) {
627
+ parent.scannedImages = (0..<scan.pageCount).map { scan.imageOfPage(at: $0) }
628
+ parent.dismiss()
629
+ }
630
+
631
+ func documentCameraViewControllerDidCancel(
632
+ _ controller: VNDocumentCameraViewController
633
+ ) {
634
+ parent.dismiss()
635
+ }
636
+
637
+ func documentCameraViewController(
638
+ _ controller: VNDocumentCameraViewController,
639
+ didFailWithError error: Error
640
+ ) {
641
+ parent.dismiss()
642
+ }
643
+ }
644
+ }
645
+ ```
646
+
647
+ ### Document Scanner with OCR Pipeline
648
+
649
+ Combine document scanning with Vision text recognition for a complete OCR flow:
650
+
651
+ ```swift
652
+ import SwiftUI
653
+ import VisionKit
654
+ import Vision
655
+
656
+ @MainActor
657
+ @Observable
658
+ final class DocumentOCRModel {
659
+ var scannedPages: [UIImage] = []
660
+ var extractedText: [String] = []
661
+ var isProcessing = false
662
+
663
+ func processScannedPages() async {
664
+ isProcessing = true
665
+ defer { isProcessing = false }
666
+
667
+ extractedText = []
668
+ for page in scannedPages {
669
+ guard let cgImage = page.cgImage else { continue }
670
+ do {
671
+ var request = RecognizeTextRequest()
672
+ request.recognitionLevel = .accurate
673
+ request.recognitionLanguages = [Locale.Language(identifier: "en-US")]
674
+ request.usesLanguageCorrection = true
675
+
676
+ let observations = try await request.perform(on: cgImage)
677
+ let pageText = observations
678
+ .compactMap { $0.topCandidates(1).first?.string }
679
+ .joined(separator: "\n")
680
+ extractedText.append(pageText)
681
+ } catch {
682
+ extractedText.append("[Recognition failed]")
683
+ }
684
+ }
685
+ }
686
+ }
687
+
688
+ struct DocumentOCRView: View {
689
+ @State private var model = DocumentOCRModel()
690
+ @State private var isShowingScanner = false
691
+
692
+ var body: some View {
693
+ NavigationStack {
694
+ List {
695
+ if model.isProcessing {
696
+ ProgressView("Recognizing text...")
697
+ }
698
+ ForEach(Array(model.extractedText.enumerated()), id: \.offset) { index, text in
699
+ Section("Page \(index + 1)") {
700
+ Text(text)
701
+ .font(.body)
702
+ .textSelection(.enabled)
703
+ }
704
+ }
705
+ }
706
+ .navigationTitle("Document OCR")
707
+ .toolbar {
708
+ Button("Scan") {
709
+ isShowingScanner = true
710
+ }
711
+ }
712
+ .fullScreenCover(isPresented: $isShowingScanner) {
713
+ DocumentScannerRepresentable(scannedImages: $model.scannedPages)
714
+ }
715
+ .onChange(of: model.scannedPages) {
716
+ Task { await model.processScannedPages() }
717
+ }
718
+ }
719
+ }
720
+ }
721
+ ```
722
+
723
+ ## Performance Considerations
724
+
725
+ ### DataScannerViewController
726
+
727
+ - Use `.fast` quality for barcode-only scanning
728
+ - Set `recognizesMultipleItems = false` when only one result is needed
729
+ - Disable `isHighFrameRateTrackingEnabled` for barcode scanning to save power
730
+ - Limit `recognizedDataTypes` to only what you need
731
+ - Stop scanning when processing results to avoid wasted CPU cycles
732
+
733
+ ### VNDocumentCameraViewController
734
+
735
+ - Pages are returned as `UIImage` at full resolution -- resize before
736
+ processing if memory is a concern
737
+ - Process pages sequentially to avoid memory spikes
738
+ - Use `autoreleasepool` when processing many pages in a loop