Scanning QR code crashes often - AVCaptureSession [duplicate] - ios

This question already has an answer here:
Swift: Unable to detect linear type Barcodes
(1 answer)
Closed 4 years ago.
Am trying to scan QR code with the below code and it works fine. But sometimes it crashes with the following error.
Could not cast value of type 'AVMetadataFaceObject' (0x1b245bd28) to 'AVMetadataMachineReadableCodeObject' (0x1b245be68). Help much appreciated.
P.S: Am showing camera inside tabbar controller
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
// Check if the metadataObjects array is not nil and it contains at least one object.
if metadataObjects.count == 0 {
qrCodeFrameView?.frame = CGRect.zero
messageLabel.text = "No QR code is detected"
return
}
// Get the metadata object.
let metadataObj = metadataObjects[0] as! AVMetadataMachineReadableCodeObject
if supportedCodeTypes.contains(metadataObj.type) {
// If the found metadata is equal to the QR code metadata (or barcode) then update the status label's text and set the bounds
let barCodeObject = videoPreviewLayer?.transformedMetadataObject(for: metadataObj)
qrCodeFrameView?.frame = barCodeObject!.bounds
if metadataObj.stringValue != nil {
launchApp(decodedURL: metadataObj.stringValue!)
messageLabel.text = metadataObj.stringValue
captureSession?.stopRunning()
captureSession = nil
}
}
}

The use cases of a force cast are very limited, it is almost never a good idea. You should use:
guard let metadataObj = metadataObjects[0] as? AVMetadataMachineReadableCodeObject else{
// Display some sort of error message or handle it
return
}

Make sure you are detecting QR codes and not faces:
captureMetadataOutput.metadataObjectTypes = [AVMetadataObject.ObjectType.qr]

Related

AVMetadataMachineReadableCodeObject stringValue returns inconsistent results

I'm experiencing a weird issue when scanning barcodes on iOS. The data in the barcodes are railway tickets encoded using the UIC-918-3 standard.
To decode the tickets I need to get the contents of stringValue in AVMetadataMachineReadableCodeObject for further processing.
It works perfectly on iOS 16.x (tested on both iPhone 13 and 14), but when scanning the same barcode using an iPhone 6s running iOS 15.7.1 I get a truncated string in stringValue.
Any thoughts as to why this happens, and how i can fix this?
extension TicketScannerViewController: AVCaptureMetadataOutputObjectsDelegate {
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
if let metadataObject = metadataObjects.first, !shouldHaltTicketDecoding {
guard let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject else { return }
let payload = readableObject.stringValue
// ...
}
}
}

Error when converting to current syntax

Last year, I did a tutorial on how to create a camera app. Today I tried to run it but it needed to be converted to the current syntax. After fixing a majority of the errors, I'm now left with just one error that I can't fix. I'm not too familiar with AVFoundation or Swift 4 yet so I was hoping that I could receive some sort of assistance. The error that Im getting is on line 61/62:
Initializer for conditional binding must have optional type, not [AVCaptureDevice]
Here's the relevant code:
//capture devices
func configureCaptureDevices() throws {
let session = AVCaptureDevice.DiscoverySession(deviceTypes: [], mediaType: <#T##AVMediaType?#>, position: <#T##AVCaptureDevice.Position#>)
guard let cameras = (session.devices.flatMap { $0 }), !cameras.isEmpty else { throw CameraControllerError.noCamerasAvailable}
for camera in cameras {
if camera.position == .front {
self.frontCamera = camera
}
if camera.position == .back {
self.rearCamera = camera
try camera.lockForConfiguration()
camera.focusMode = .continuousAutoFocus
camera.unlockForConfiguration()
}
}
}
And finally, a link to the tutorial I was following: https://www.appcoda.com/avfoundation-swift-guide/
The problem is that you're using guard let syntax which is only for binding optionals. And you've correctly deduced that session is no longer optional (which is why you removed the ?). So pull the assignment of cameras out of the guard statement:
let session = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: .unspecified)
let cameras = session.devices.flatMap { $0 }
guard !cameras.isEmpty else { throw CameraControllerError.noCamerasAvailable }
Personally, now that we're no longer doing any optional binding, I'd replace that guard statement with a more intuitive if statement. I also don't believe that flatMap is needed (you use that when dealing with arrays of arrays or arrays of optionals, neither of which applies here):
let cameras = session.devices
if cameras.isEmpty { throw CameraControllerError.noCamerasAvailable }

how to make a value inside a function global

I would like to make the value of ItemID global so that I can access it out side the function. I have attempted to define it right below class but it doesn't work. How else can I make a value inside a function accessible by other functions?
func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) {
// Check if the metadataObjects array is not nil and it contains at least one object.
if metadataObjects == nil || metadataObjects.count == 0 {
qrCodeFrameView?.frame = CGRect.zero
messageLabel.text = "No QR/barcode is detected"
return
}
//Get metadata object
let metadataObj = metadataObjects[0] as! AVMetadataMachineReadableCodeObject
if supportedCodeTypes.contains(metadataObj.type) {
//if the found metadata is equal to the QR code metadata then update the status label's text and set the the bounds
let barCodeObject = videoPreviewLayer?.transformedMetadataObject(for: metadataObj)
qrCodeFrameView?.frame = barCodeObject!.bounds
if metadataObj.stringValue != nil {
messageLabel.text = metadataObj.stringValue
//Searches firebase for existing barcode
}
let itemID = metadataObj.stringValue!
let itemToSearchFor = itemID
guard let Description = productDescriptionTextField.text,
let price = priceTextField.text,
let location = productLocationTextField.text
else{
print("Fill basic product information")
return
}
let ref = FIRDatabase.database().reference(fromURL: "/")
// creating an item child node
let values = ["Item Description": Description, "Image": price, "Location": location, "Price": price]
let items = ref.child("Items").child(itemID)
items.updateChildValues(values, withCompletionBlock: { (err, ref) in
if err != nil {
print(err)
return
} })
FIRDatabase.database().reference().child("Items").child(itemToSearchFor).observeSingleEvent(of: .value, with:{(snap) in
print(snap)
})
}}
This is the function where I want to call itemID
func enterNewProduct() {
guard let Description = self.productDescriptionTextField.text,
let price = self.priceTextField.text,
let location = self.productLocationTextField.text
else{
print("Fill basic product information")
return
}
let ref = FIRDatabase.database().reference(fromURL: "/")
// creating an item child node
let values = ["Item Description": Description, "Image": price, "Location": location, "Price": price ]
let items = ref.child("Items").child(itemID)
items.updateChildValues(values, withCompletionBlock: { (err, ref) in
if err != nil {
print(err)
return
} })
self.dismiss(animated: true, completion: nil)
}
for some reason it is not recognizing the itemID from the previous function
Put it above the function somewhere like this.
var itemID:String!
func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) {
Then in your function you should be able to access it like this.
itemID = metadataObj.stringValue!
let itemToSearchFor = itemID
Make sure to use var so that you can modify it.
It sounds like you've done everything correctly. You forgot to give the code where you "attempted to define it right below class". In case that's the issue, here a template that works:
class ViewController: UIViewController {
var myID = "ID #1"
override func viewDidLoad() {
super.viewDidLoad()
changeID()
printID()
}
func changeID() {
printID()
myID = "ID #2"
}
func printID() {
print(myID)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
The console output is:
ID #1
ID #2

Unresolved Identifier "itemID"

How can I Globally define (barcode scanned) so that it can be accessible by all my functions. In order words how can I define “metadataObj” globally?
class ScanController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
var captureSession: AVCaptureSession?
var videoPreviewLayer: AVCaptureVideoPreviewLayer?
var qrCodeFrameView: UIView?
override func viewDidLoad() {
super.viewDidLoad()
//Get an instance of the AVCaptureDevice class a device object and provide the video as the media type parameter
let captureDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
do {
// Get an instance of the AVCaptureDeviceInput class using the previous device object.
let input = try AVCaptureDeviceInput(device: captureDevice)
// Initialize the captureSession object.
captureSession = AVCaptureSession()
// Set the input device on the capture session.
captureSession?.addInput(input)
let captureMetadataOutput = AVCaptureMetadataOutput()
captureSession?.addOutput(captureMetadataOutput)
// Set delegate and use the default dispatch queue to execute the call back
captureMetadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
captureMetadataOutput.metadataObjectTypes = supportedCodeTypes
// Initialize the video preview layer and add it as a sublayer to the viewPreview view's layer.
videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
videoPreviewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill
videoPreviewLayer?.frame = view.layer.bounds
view.layer.addSublayer(videoPreviewLayer!)
// Start video capture.
captureSession?.startRunning()
//initialize QR Code Frame to highlight the QR Code
qrCodeFrameView = UIView()
if let qrCodeFrameView = qrCodeFrameView {
qrCodeFrameView.layer.borderColor = UIColor.green.cgColor
qrCodeFrameView.layer.borderWidth = 2
view.addSubview(qrCodeFrameView)
}
} catch {
// If any error occurs, simply print it out and don't continue any more.
print(error)
return
}
}
func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) {
// Check if the metadataObjects array is not nil and it contains at least one object.
if metadataObjects == nil || metadataObjects.count == 0 {
qrCodeFrameView?.frame = CGRect.zero
messageLabel.text = "No QR/barcode is detected"
return
}
//Get metadata object
let metadataObj = metadataObjects[0] as! AVMetadataMachineReadableCodeObject
if supportedCodeTypes.contains(metadataObj.type) {
//if the found metadata is equal to the QR code metadata then update the status label's text and set the the bounds
let barCodeObject = videoPreviewLayer?.transformedMetadataObject(for: metadataObj)
qrCodeFrameView?.frame = barCodeObject!.bounds
if metadataObj.stringValue != nil {
messageLabel.text = metadataObj.stringValue
//Searches firebase for existing barcode
}
let itemToSearchFor = metadataObj.stringValue
let itemID = metadataObj.stringValue
guard let Description = productDescriptionTextField.text,
let price = priceTextField.text,
let location = productLocationTextField.text
else{
print("Fill basic product information")
return
}
let ref = FIRDatabase.database().reference(fromURL: " /")
// creating an item child node
let values = ["Item Description": Description, "Image": price, "Location": location, "Price": price ]
let items = ref.child("Items").child(itemID!)
items.updateChildValues(values, withCompletionBlock: { (err, ref) in
if err != nil {
print(err)
return
} })
FIRDatabase.database().reference().child("Items").child(itemToSearchFor!).observeSingleEvent(of: .value, with:{(snap) in
print(snap)
})
self.setupNewProductEntry()
self.setupenterNewProductButton()
}
}
I have one error when I tried to use ItemID within another function. I think this is because it is not globally defined, it is only defined within func captureOutput. Any ideas on how to globally define the barcode string values that I get from my barcode scanner?
You can move it beyond your class scopes, so it'll become global and visible through all your module
Declare it up here (where the comment is) to make it accessible.
class ScanController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
var captureSession: AVCaptureSession?
var videoPreviewLayer: AVCaptureVideoPreviewLayer?
var qrCodeFrameView: UIView?
//DECLARE itemID here
override func viewDidLoad() {
super.viewDidLoad()
//Get an instance of the AVCaptureDevice class a device object and provide the video as the media type parameter
let captureDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
Just put "metadataObj" outside the class in the global scope (beginning of the file after the import section, I mean, outside the class declaration), so that it isn't a member of the class anymore (and initialize it with a value of your choice). If you declare it in a framework and want to use it in your app, then add the public access modifier, or you won't be able to access it.
If you want to put it in the scope of the class but accessible from everywhere in your code, add the static modifier (or static public if you declare it in a framework). Using static you'll create a single instance of the variable, not related to any particular instance of your class (one and only one instance of metadataObj shared among all instances of your class). Be careful if you access metadataObj from different threads... you could face very annoing faults, and difficult to fix. In this latter case consider thread synchronization (not exactly an easy subject to cope with)... I would discourage that if you haven't a very clear idea about what you need to do. If you, instead, need it only within the class scope (I mean, to be used by all the member functions... and only them), put it where Alec told you.

How to handle Fatal Error in Tesseract OCR 4.0 iOS Swift

I am using Tesseract OCR in my app. For gallery images it's working fine if I use a very clear image such as this one
5 text image
But for some images it gives me an error and the app crashes in my iPhone. App almost always crashes when I use a camera image as well. Now I want to know how to handle FATAL Error in Swift 2.2 . How can I show an alert that image was unreadable instead of crashing my app. I tried do {} catch {} but it doesn't work.
func checkWithOCR() throws{
let ocrSample = myImageView.image //image picker from gallery
//FATAL ERROR ON THIS LINE.
tesseract.image = ocrSample!.fixOrientation().g8_blackAndWhite()
if(tesseract.recognize()){
let recognizedText = tesseract.recognizedText
if recognizedText != nil{
print("recognizedText: \(recognizedText)")
let trimmedText = String(recognizedText.characters.filter { !" \n\t\r,".characters.contains($0) })
myImageView.image = tesseract.image
convertCurrency(Float(trimmedText)!) //convert the tesseract text
}
}
SwiftSpinner.hide()
}
HERE IS THE ERROR:
recognizedText:
fatal error: unexpectedly found nil while unwrapping an Optional value
I know why this error occurs as there was no value on the line I mentioned above. How can I show an alert box if this error occurs instead of a crash.
P.S: I tried if ( ocrSample!.fixOrientation().g8_blackAndWhite() != nil ) {}
It doesn't work
The image on a UIImageView is an UIImage optional, meaning that it can have a value (contain an image) or it can be nil.
So, when you're saying:
let ocrSample = myImageView.image
your ocrSample is now an UIImage optional, which you then have to unwrap before you use it.
When you then say:
tesseract.image = ocrSample!.fixOrientation().g8_blackAndWhite()
you are force unwrapping your ocrSample by using !, meaning that you are telling the compiler to just unwrap and use the optional, regardless of it being nil or not. This causes a crash when you then try to use that unwrapped optional if it contains nil.
What you can do is unwrap ocrSample using an if let like so:
func checkWithOCR() throws{
if let ocrSample = myImageView.image {
tesseract.image = ocrSample.fixOrientation().g8_blackAndWhite()
if(tesseract.recognize()){
let recognizedText = tesseract.recognizedText
if recognizedText != nil{
print("recognizedText: \(recognizedText)")
let trimmedText = String(recognizedText.characters.filter { !" \n\t\r,".characters.contains($0) })
myImageView.image = tesseract.image
convertCurrency(Float(trimmedText)!) //convert the tesseract text
}
}
SwiftSpinner.hide()
} else {
//No value could be found, do your error handling here
}
}
Here:
if let ocrSample = myImageView.image
you are trying to unwrap the value of myImageView.image into ocrSample, if that succeeds, then you know for sure that ocrSample is not nil and can use it onwards. If it fails, then you can do your error handling, show an alert view and whatever else you need to do.
Hope that helps you.

Resources