@@ -12,19 +12,17 @@ import UIKit
1212
1313@available ( macCatalyst 14 . 0 , * )
1414extension CodeScannerView {
15-
15+
1616 public final class ScannerViewController : UIViewController , UINavigationControllerDelegate {
17- private let photoOutput = AVCapturePhotoOutput ( )
18- private var isCapturing = false
1917 private var handler : ( ( UIImage ? ) -> Void ) ?
2018 var parentView : CodeScannerView !
2119 var codesFound = Set < String > ( )
2220 var didFinishScanning = false
2321 var lastTime = Date ( timeIntervalSince1970: 0 )
2422 private let showViewfinder : Bool
25-
23+ private var latestSampleBuffer : CMSampleBuffer ?
2624 let fallbackVideoCaptureDevice = AVCaptureDevice . default ( for: . video)
27-
25+
2826 private var isGalleryShowing : Bool = false {
2927 didSet {
3028 // Update binding
@@ -44,15 +42,15 @@ extension CodeScannerView {
4442 self . showViewfinder = false
4543 super. init ( coder: coder)
4644 }
47-
45+
4846 func openGallery( ) {
4947 isGalleryShowing = true
5048 let imagePicker = UIImagePickerController ( )
5149 imagePicker. delegate = self
5250 imagePicker. presentationController? . delegate = self
5351 present ( imagePicker, animated: true , completion: nil )
5452 }
55-
53+
5654 @objc func openGalleryFromButton( _ sender: UIButton ) {
5755 openGallery ( )
5856 }
@@ -99,9 +97,9 @@ extension CodeScannerView {
9997 type: parentView. codeTypes. first ?? . qr, image: nil , corners: [ ]
10098 ) )
10199 }
102-
100+
103101 #else
104-
102+
105103 var captureSession : AVCaptureSession ?
106104 var previewLayer : AVCaptureVideoPreviewLayer !
107105
@@ -114,7 +112,7 @@ extension CodeScannerView {
114112 imageView. translatesAutoresizingMaskIntoConstraints = false
115113 return imageView
116114 } ( )
117-
115+
118116 private lazy var manualCaptureButton : UIButton = {
119117 let button = UIButton ( type: . system)
120118 let image = UIImage ( named: " capture " , in: . module, with: nil )
@@ -173,12 +171,12 @@ extension CodeScannerView {
173171
174172 setupSession ( )
175173 }
176-
174+
177175 private func setupSession( ) {
178176 guard let captureSession else {
179177 return
180178 }
181-
179+
182180 if previewLayer == nil {
183181 previewLayer = AVCaptureVideoPreviewLayer ( session: captureSession)
184182 }
@@ -213,7 +211,7 @@ extension CodeScannerView {
213211 case . authorized:
214212 self . setupCaptureDevice ( )
215213 self . setupSession ( )
216-
214+
217215 default :
218216 break
219217 }
@@ -228,7 +226,7 @@ extension CodeScannerView {
228226 completion ? ( )
229227 }
230228 }
231-
229+
232230 private func addOrientationDidChangeObserver( ) {
233231 NotificationCenter . default. addObserver (
234232 self ,
@@ -237,11 +235,11 @@ extension CodeScannerView {
237235 object: nil
238236 )
239237 }
240-
238+
241239 private func setBackgroundColor( _ color: UIColor = . black) {
242240 view. backgroundColor = color
243241 }
244-
242+
245243 private func setupCaptureDevice( ) {
246244 captureSession = AVCaptureSession ( )
247245
@@ -265,12 +263,15 @@ extension CodeScannerView {
265263 return
266264 }
267265 let metadataOutput = AVCaptureMetadataOutput ( )
266+ let videoOutput = AVCaptureVideoDataOutput ( )
268267
269268 if captureSession!. canAddOutput ( metadataOutput) {
270269 captureSession!. addOutput ( metadataOutput)
271- captureSession!. addOutput ( photoOutput )
270+ captureSession!. addOutput ( videoOutput )
272271 metadataOutput. setMetadataObjectsDelegate ( self , queue: DispatchQueue . main)
273272 metadataOutput. metadataObjectTypes = parentView. codeTypes
273+
274+ videoOutput. setSampleBufferDelegate ( self , queue: DispatchQueue ( label: " com.hackingwithswift.codeScanner.videoQueue " ) )
274275 } else {
275276 didFail ( reason: . badOutput)
276277 return
@@ -337,11 +338,11 @@ extension CodeScannerView {
337338 device. exposureMode = AVCaptureDevice . ExposureMode. continuousAutoExposure
338339 device. unlockForConfiguration ( )
339340 }
340-
341+
341342 @objc func manualCapturePressed( _ sender: Any ? ) {
342343 self . readyManualCapture ( )
343344 }
344-
345+
345346 func showManualCaptureButton( _ isManualCapture: Bool ) {
346347 if manualCaptureButton. superview == nil {
347348 view. addSubview ( manualCaptureButton)
@@ -352,11 +353,11 @@ extension CodeScannerView {
352353 view. safeAreaLayoutGuide. bottomAnchor. constraint ( equalTo: manualCaptureButton. bottomAnchor, constant: 32 )
353354 ] )
354355 }
355-
356+
356357 view. bringSubviewToFront ( manualCaptureButton)
357358 manualCaptureButton. isHidden = !isManualCapture
358359 }
359-
360+
360361 func showManualSelectButton( _ isManualSelect: Bool ) {
361362 if manualSelectButton. superview == nil {
362363 view. addSubview ( manualSelectButton)
@@ -367,39 +368,39 @@ extension CodeScannerView {
367368 view. safeAreaLayoutGuide. bottomAnchor. constraint ( equalTo: manualSelectButton. bottomAnchor, constant: 32 )
368369 ] )
369370 }
370-
371+
371372 view. bringSubviewToFront ( manualSelectButton)
372373 manualSelectButton. isHidden = !isManualSelect
373374 }
374375 #endif
375-
376+
376377 func updateViewController( isTorchOn: Bool , isGalleryPresented: Bool , isManualCapture: Bool , isManualSelect: Bool ) {
377378 guard let videoCaptureDevice = parentView. videoCaptureDevice ?? fallbackVideoCaptureDevice else {
378379 return
379380 }
380-
381+
381382 if videoCaptureDevice. hasTorch {
382383 try ? videoCaptureDevice. lockForConfiguration ( )
383384 videoCaptureDevice. torchMode = isTorchOn ? . on : . off
384385 videoCaptureDevice. unlockForConfiguration ( )
385386 }
386-
387+
387388 if isGalleryPresented, !isGalleryShowing {
388389 openGallery ( )
389390 }
390-
391+
391392 #if !targetEnvironment(simulator)
392393 showManualCaptureButton ( isManualCapture)
393394 showManualSelectButton ( isManualSelect)
394395 #endif
395396 }
396-
397+
397398 public func reset( ) {
398399 codesFound. removeAll ( )
399400 didFinishScanning = false
400401 lastTime = Date ( timeIntervalSince1970: 0 )
401402 }
402-
403+
403404 public func readyManualCapture( ) {
404405 guard parentView. scanMode. isManual else { return }
405406 self . reset ( )
@@ -409,7 +410,7 @@ extension CodeScannerView {
409410 var isPastScanInterval : Bool {
410411 Date ( ) . timeIntervalSince ( lastTime) >= parentView. scanInterval
411412 }
412-
413+
413414 var isWithinManualCaptureInterval : Bool {
414415 Date ( ) . timeIntervalSince ( lastTime) <= 0.5
415416 }
@@ -429,7 +430,26 @@ extension CodeScannerView {
429430 self . parentView. completion ( . failure( reason) )
430431 }
431432 }
432-
433+
434+ }
435+ }
436+
437+ // MARK: - AVCaptureVideoDataOutputSampleBufferDelegate
438+ @available ( macCatalyst 14 . 0 , * )
439+ extension CodeScannerView . ScannerViewController : AVCaptureVideoDataOutputSampleBufferDelegate {
440+ public func captureOutput( _ output: AVCaptureOutput , didOutput sampleBuffer: CMSampleBuffer , from connection: AVCaptureConnection ) {
441+ latestSampleBuffer = sampleBuffer
442+ }
443+
444+ func processFrame( _ sampleBuffer: CMSampleBuffer ) {
445+ guard let imageBuffer = CMSampleBufferGetImageBuffer ( sampleBuffer) else { return }
446+
447+ let ciImage = CIImage ( cvPixelBuffer: imageBuffer)
448+
449+ let context = CIContext ( )
450+ if let cgImage = context. createCGImage ( ciImage, from: ciImage. extent) {
451+ handler ? ( UIImage ( cgImage: cgImage) )
452+ }
433453 }
434454}
435455
@@ -443,7 +463,6 @@ extension CodeScannerView.ScannerViewController: AVCaptureMetadataOutputObjectsD
443463 guard let metadataObject = metadataObjects. first,
444464 !parentView. isPaused,
445465 !didFinishScanning,
446- !isCapturing,
447466 let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject ,
448467 let stringValue = readableObject. stringValue else {
449468
@@ -484,9 +503,8 @@ extension CodeScannerView.ScannerViewController: AVCaptureMetadataOutputObjectsD
484503 }
485504 }
486505
487- if parentView. requiresPhotoOutput {
488- isCapturing = true
489- photoOutput. capturePhoto ( with: AVCapturePhotoSettings ( ) , delegate: self )
506+ if parentView. requiresPhotoOutput, let latestSampleBuffer {
507+ self . processFrame ( latestSampleBuffer)
490508 } else {
491509 handler ? ( nil )
492510 }
@@ -516,7 +534,7 @@ extension CodeScannerView.ScannerViewController: UIImagePickerControllerDelegate
516534 guard !features. isEmpty else {
517535 didFail ( reason: . badOutput)
518536 return
519- }
537+ }
520538 for feature in features. compactMap ( { $0 as? CIQRCodeFeature } ) {
521539 guard let qrCodeLink = feature. messageString, !qrCodeLink. isEmpty else {
522540 didFail ( reason: . badOutput)
@@ -551,54 +569,16 @@ extension CodeScannerView.ScannerViewController: UIAdaptivePresentationControlle
551569 }
552570}
553571
554- // MARK: - AVCapturePhotoCaptureDelegate
555-
556- @available ( macCatalyst 14 . 0 , * )
557- extension CodeScannerView . ScannerViewController : AVCapturePhotoCaptureDelegate {
558-
559- public func photoOutput(
560- _ output: AVCapturePhotoOutput ,
561- didFinishProcessingPhoto photo: AVCapturePhoto ,
562- error: Error ?
563- ) {
564- isCapturing = false
565- guard let imageData = photo. fileDataRepresentation ( ) else {
566- print ( " Error while generating image from photo capture data. " ) ;
567- return
568- }
569- guard let qrImage = UIImage ( data: imageData) else {
570- print ( " Unable to generate UIImage from image data. " ) ;
571- return
572- }
573- handler ? ( qrImage)
574- }
575-
576- public func photoOutput(
577- _ output: AVCapturePhotoOutput ,
578- willCapturePhotoFor resolvedSettings: AVCaptureResolvedPhotoSettings
579- ) {
580- AudioServicesDisposeSystemSoundID ( 1108 )
581- }
582-
583- public func photoOutput(
584- _ output: AVCapturePhotoOutput ,
585- didCapturePhotoFor resolvedSettings: AVCaptureResolvedPhotoSettings
586- ) {
587- AudioServicesDisposeSystemSoundID ( 1108 )
588- }
589-
590- }
591-
592572// MARK: - AVCaptureDevice
593573
594574@available ( macCatalyst 14 . 0 , * )
595575public extension AVCaptureDevice {
596-
576+
597577 /// This returns the Ultra Wide Camera on capable devices and the default Camera for Video otherwise.
598578 static var bestForVideo : AVCaptureDevice ? {
599579 let deviceHasUltraWideCamera = !AVCaptureDevice. DiscoverySession ( deviceTypes: [ . builtInUltraWideCamera] , mediaType: . video, position: . back) . devices. isEmpty
600580 return deviceHasUltraWideCamera ? AVCaptureDevice . default ( . builtInUltraWideCamera, for: . video, position: . back) : AVCaptureDevice . default ( for: . video)
601581 }
602-
582+
603583}
604584#endif
0 commit comments