Skip to content

Commit fd3c6b2

Browse files
committed
apply changes from twostraws#156
1 parent cb7a063 commit fd3c6b2

File tree

1 file changed

+55
-75
lines changed

1 file changed

+55
-75
lines changed

Sources/CodeScanner/ScannerViewController.swift

Lines changed: 55 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,17 @@ import UIKit
1212

1313
@available(macCatalyst 14.0, *)
1414
extension 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, *)
595575
public 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

Comments
 (0)