Skip to content

Instantly share code, notes, and snippets.

@Zlobrynya
Created January 15, 2024 10:20
Show Gist options
  • Save Zlobrynya/953c54c3be1b912d894fd528fa8c1625 to your computer and use it in GitHub Desktop.
Save Zlobrynya/953c54c3be1b912d894fd528fa8c1625 to your computer and use it in GitHub Desktop.
import Foundation
import AVFoundation
import UIKit
import Vision
final class VNScannerController: UIViewController, AVCaptureVideoDataOutputSampleBufferDelegate {
private let session = AVCaptureSession()
private let seqHandler = VNSequenceRequestHandler()
private let captureDevice = AVCaptureDevice.default(for: AVMediaType.video)
private var request: VNDetectBarcodesRequest?
private var previewLayer: AVCaptureVideoPreviewLayer!
private var originalHeight: CGFloat = .zero
override func viewDidLoad() {
super.viewDidLoad()
setUpScanner()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
#if targetEnvironment(simulator)
#else
if previewLayer == nil {
setUpPreviewLayer()
}
#endif
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
start()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
stop()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
previewLayer?.frame = view.bounds
}
func turnLightOn(_ on: Bool) {
#if targetEnvironment(simulator)
#else
let torchLevel = on ? min(1, AVCaptureDevice.maxAvailableTorchLevel) : 0
guard let captureDevice = captureDevice,
UIApplication.shared.applicationState == .active,
captureDevice.hasFlash,
captureDevice.torchLevel != torchLevel
else { return }
do {
try captureDevice.lockForConfiguration()
if on {
try captureDevice.setTorchModeOn(level: torchLevel)
} else {
captureDevice.torchMode = .off
}
captureDevice.unlockForConfiguration()
} catch {}
#endif
}
private func start() {
guard session.isRunning == false else {
return
}
DispatchQueue.global(qos: .userInitiated).async {
self.session.startRunning()
}
}
private func stop() {
guard session.isRunning else {
return
}
turnLightOn(false)
DispatchQueue.global(qos: .userInitiated).async {
self.session.stopRunning()
}
}
private func setUpPreviewLayer() {
previewLayer = AVCaptureVideoPreviewLayer(session: session)
previewLayer.frame = view.layer.bounds
previewLayer.videoGravity = .resizeAspectFill
view.layer.addSublayer(previewLayer)
}
private func setUpScanner() {
session.sessionPreset = .high
try? captureDevice?.lockForConfiguration()
captureDevice?.focusMode = .continuousAutoFocus
try? captureDevice?.unlockForConfiguration()
do {
try setUpSession()
} catch {
print(error)
}
request = VNDetectBarcodesRequest { request, _ in
guard let observations = request.results,
observations.isEmpty == false else {
return
}
observations
.compactMap { $0 as? VNBarcodeObservation }
.forEach { barcodeObservation in
print(barcodeObservation.payloadStringValue)
}
}
}
private func setUpSession() throws {
guard let captureDevice else {
return
}
let deviceInput: AVCaptureDeviceInput
deviceInput = try AVCaptureDeviceInput(device: captureDevice)
let deviceOutput = AVCaptureVideoDataOutput()
deviceOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_32BGRA)]
deviceOutput.setSampleBufferDelegate(self, queue: DispatchQueue.global(qos: .default))
session.addInput(deviceInput)
if session.canAddOutput(deviceOutput) {
session.addOutput(deviceOutput)
} else {
throw ScanError.badOutput
}
}
// MARK: Implementing AVCaptureVideoDataOutputSampleBufferDelegate
func captureOutput(_: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from _: AVCaptureConnection) {
// https://stackoverflow.com/questions/51214586/value-of-type-cmsamplebuffer-has-no-member-imagebuffer
guard let pixelBuffer: CVPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
return
}
guard let request = request else {
return
}
try? seqHandler.perform([request], on: pixelBuffer)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment