Skip to content

Commit

Permalink
Update MRZScannerExample
Browse files Browse the repository at this point in the history
  • Loading branch information
romanmazeev committed Feb 2, 2025
1 parent 1d07870 commit 4197021
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 134 deletions.
10 changes: 7 additions & 3 deletions Example/MRZScannerExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1420;
LastUpgradeCheck = 1420;
LastUpgradeCheck = 1600;
TargetAttributes = {
0E472C71295D23770033AA9E = {
CreatedOnToolsVersion = 14.2;
Expand Down Expand Up @@ -188,6 +188,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
Expand Down Expand Up @@ -221,6 +222,7 @@
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
Expand All @@ -247,6 +249,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
Expand Down Expand Up @@ -280,6 +283,7 @@
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
Expand Down Expand Up @@ -321,7 +325,7 @@
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
IPHONEOS_DEPLOYMENT_TARGET = 16.2;
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 13.1;
Expand Down Expand Up @@ -365,7 +369,7 @@
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
IPHONEOS_DEPLOYMENT_TARGET = 16.2;
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 13.1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/romanmazeev/MRZParser.git",
"state" : {
"branch" : "master",
"revision" : "a39f93e35e4d8de2dceeb6103ca4089856e3c566"
"revision" : "f6a0b10395e7cac783189ef2a0c8e1a55296ff37",
"version" : "1.1.4"
}
},
{
Expand All @@ -36,13 +36,22 @@
"version" : "1.1.0"
}
},
{
"identity" : "swift-custom-dump",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-custom-dump.git",
"state" : {
"revision" : "f01efb26f3a192a0e88dcdb7c3c391ec2fc25d9c",
"version" : "1.3.0"
}
},
{
"identity" : "swift-dependencies",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-dependencies",
"state" : {
"revision" : "9783b58167f7618cb86011156e741cbc6f4cc864",
"version" : "1.1.2"
"revision" : "00bc30ca03f98881329fab7f1bebef8eba472596",
"version" : "1.3.1"
}
},
{
Expand All @@ -59,8 +68,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/xctest-dynamic-overlay.git",
"state" : {
"revision" : "23cbf2294e350076ea4dbd7d5d047c1e76b03631",
"version" : "1.0.2"
"revision" : "6f30bdba373bbd7fbfe241dddd732651f2fbd1e2",
"version" : "1.1.2"
}
}
],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1420"
LastUpgradeVersion = "1600"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
82 changes: 22 additions & 60 deletions Example/MRZScannerExample/Camera.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,86 +5,53 @@
// Created by Roman Mazeev on 01/01/2023.
//

import AVFoundation
@preconcurrency import AVFoundation
import CoreImage

final class Camera: NSObject {
let captureSession = AVCaptureSession()
actor Camera {
nonisolated let captureSession = AVCaptureSession()
private let outputSampleBufferDelegate = OutputSampleBufferDelegate()

var imageStream: AsyncStream<CIImage> {
AsyncStream { continuation in
imageStreamCallback = { ciImage in
continuation.yield(ciImage)
}
var imageStream: AsyncStream<CIImage>? {
.init { continuation in
outputSampleBufferDelegate.continuation = continuation
}
}

private var imageStreamCallback: ((CIImage) -> Void)?
private let captureDevice = AVCaptureDevice.default(for: .video)

// TODO: Refactor to use Swift Concurrency
private let sessionQueue = DispatchQueue(label: "Session queue")

private var isCaptureSessionConfigured = false
private var deviceInput: AVCaptureDeviceInput?
private var videoOutput: AVCaptureVideoDataOutput?

func start() async {
let authorized = await checkAuthorization()
guard authorized else {
func startCamera() async throws {
guard await checkAuthorization() else {
fatalError("You need to give access")
}

if isCaptureSessionConfigured {
if !captureSession.isRunning {
sessionQueue.async { [self] in
self.captureSession.startRunning()
}
}
return
}

sessionQueue.async { [self] in
try? self.configureCaptureSession { success in
guard success else { return }
self.captureSession.startRunning()
}
}
try await configureCaptureSession()
}

private func checkAuthorization() async -> Bool {
func requestCameraAccess() async -> Bool {
sessionQueue.suspend()
let status = await AVCaptureDevice.requestAccess(for: .video)
sessionQueue.resume()
return status
}

switch AVCaptureDevice.authorizationStatus(for: .video) {
case .authorized:
return true
case .notDetermined, .restricted, .denied:
return await requestCameraAccess()
return await AVCaptureDevice.requestAccess(for: .video)
@unknown default:
return false
}
}

private func configureCaptureSession(completionHandler: (_ success: Bool) -> Void) throws {
var success = false

private func configureCaptureSession() async throws {
self.captureSession.beginConfiguration()

defer {
self.captureSession.commitConfiguration()
completionHandler(success)
if !captureSession.isRunning {
captureSession.startRunning()
}
}

guard let captureDevice else { fatalError("Unable to create capture device") }
guard let captureDevice = AVCaptureDevice.default(for: .video) else { fatalError("Unable to create capture device") }
let deviceInput = try AVCaptureDeviceInput(device: captureDevice)

let videoOutput = AVCaptureVideoDataOutput()
videoOutput.setSampleBufferDelegate(self, queue: .init(label: "VideoDataOutput queue"))
videoOutput.setSampleBufferDelegate(outputSampleBufferDelegate, queue: .init(label: "com.MRZScannerExample.OutputSampleBufferDelegate"))

guard captureSession.canAddInput(deviceInput) else {
fatalError("Unable to add device input to capture session.")
Expand All @@ -97,21 +64,16 @@ final class Camera: NSObject {
captureSession.addInput(deviceInput)
captureSession.addOutput(videoOutput)

self.deviceInput = deviceInput
self.videoOutput = videoOutput

videoOutput.connection(with: .video)?.videoOrientation = .portrait

isCaptureSessionConfigured = true

success = true
videoOutput.connection(with: .video)?.videoRotationAngle = 90
}
}

extension Camera: AVCaptureVideoDataOutputSampleBufferDelegate {
final private class OutputSampleBufferDelegate: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate {
var continuation: AsyncStream<CIImage>.Continuation?

func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
guard let pixelBuffer = sampleBuffer.imageBuffer else { return }

imageStreamCallback?(CIImage(cvPixelBuffer: pixelBuffer))
continuation?.yield(CIImage(cvPixelBuffer: pixelBuffer))
}
}
2 changes: 1 addition & 1 deletion Example/MRZScannerExample/CameraView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ final class CameraViewController: UIViewController {
override func viewDidLoad() {
previewLayer.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height)
previewLayer.videoGravity = .resizeAspectFill
previewLayer.connection?.videoOrientation = .portrait
previewLayer.connection?.videoRotationAngle = 90
view.layer.addSublayer(previewLayer)
}
}
Expand Down
74 changes: 40 additions & 34 deletions Example/MRZScannerExample/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,10 @@ struct ContentView: View {
size: .init(width: proxy.size.width - 40, height: proxy.size.width / 5))
}
}
.alert(isPresented: .init(get: { viewModel.mrzResult != nil }, set: { _ in viewModel.mrzResult = nil })) {
.alert(isPresented: .init(get: { viewModel.result != nil }, set: { _ in viewModel.result = nil })) {
Alert(
title: Text("Important message"),
message: Text(createAlertMessage(mrzResult: viewModel.mrzResult!)),
dismissButton: .default(Text("Got it!")) {
Task {
guard let cameraRect, let mrzRect else { return }

await viewModel.startMRZScanning(cameraRect: cameraRect, mrzRect: mrzRect)
}
}
title: Text(createAlertTitle(result: viewModel.result!)),
message: Text(createAlertMessage(result: viewModel.result!))
)
}
.task {
Expand All @@ -88,35 +81,49 @@ struct ContentView: View {
.position(rect.origin)
}

private func createAlertMessage(mrzResult: ParserResult) -> String {
var birthdateString: String?
var expiryDateString: String?

if let birthdate = mrzResult.birthdate {
birthdateString = dateFormatter.string(from: birthdate)
private func createAlertTitle(result: Result<ParserResult, Error>) -> String {
switch result {
case .success:
return "Scanned successfully"
case .failure:
return "Error"
}
}

if let expiryDate = mrzResult.expiryDate {
expiryDateString = dateFormatter.string(from: expiryDate)
}
private func createAlertMessage(result: Result<ParserResult, Error>) -> String {
switch result {
case .success(let mrzResult):
var birthdateString: String?
var expiryDateString: String?

return """
Document type: \(mrzResult.documentType)
Country code: \(mrzResult.countryCode)
Surnames: \(mrzResult.surnames)
Given names: \(mrzResult.givenNames)
Document number: \(mrzResult.documentNumber ?? "-")
nationalityCountryCode: \(mrzResult.nationalityCountryCode)
birthdate: \(birthdateString ?? "-")
sex: \(mrzResult.sex)
expiryDate: \(expiryDateString ?? "-")
personalNumber: \(mrzResult.optionalData ?? "-")
personalNumber2: \(mrzResult.optionalData2 ?? "-")
"""
if let birthdate = mrzResult.birthdate {
birthdateString = dateFormatter.string(from: birthdate)
}

if let expiryDate = mrzResult.expiryDate {
expiryDateString = dateFormatter.string(from: expiryDate)
}

return """
Document type: \(mrzResult.documentType)
Country code: \(mrzResult.countryCode)
Surnames: \(mrzResult.surnames)
Given names: \(mrzResult.givenNames)
Document number: \(mrzResult.documentNumber ?? "-")
nationalityCountryCode: \(mrzResult.nationalityCountryCode)
birthdate: \(birthdateString ?? "-")
sex: \(mrzResult.sex)
expiryDate: \(expiryDateString ?? "-")
personalNumber: \(mrzResult.optionalData ?? "-")
personalNumber2: \(mrzResult.optionalData2 ?? "-")
"""
case .failure(let error):
return error.localizedDescription
}
}
}

extension CGRect: Hashable {
extension CGRect: @retroactive Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(origin.x)
hasher.combine(origin.y)
Expand All @@ -125,7 +132,6 @@ extension CGRect: Hashable {
}
}


struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
Expand Down
Loading

0 comments on commit 4197021

Please sign in to comment.