Microblink: getting values after scanning - ios

I've set up the Microblink card reader to read only one side, and not give the user the ability to edit the scan results:
func didTapScan() {
/** Create BlinkCard recognizer */
blinkCardRecognizer = MBCBlinkCardRecognizer()
blinkCardRecognizer?.extractCvv = false
blinkCardRecognizer?.extractIban = false
blinkCardRecognizer?.extractExpiryDate = false
/** Create BlinkCard settings */
let settings : MBCBlinkCardOverlaySettings = MBCBlinkCardOverlaySettings()
settings.enableEditScreen = false
/** Crate recognizer collection */
let recognizerList = [blinkCardRecognizer!]
let recognizerCollection : MBCRecognizerCollection = MBCRecognizerCollection(recognizers: recognizerList)
/** Create your overlay view controller */
let blinkCardOverlayViewController = MBCBlinkCardOverlayViewController(settings: settings, recognizerCollection: recognizerCollection, delegate: self)
/** Create recognizer view controller with wanted overlay view controller */
// NOTE that I put a bang on the end of this - not good
let recognizerRunneViewController : UIViewController = MBCViewControllerFactory.recognizerRunnerViewController(withOverlayViewController: blinkCardOverlayViewController)!
/** Present the recognizer runner view controller. You can use other presentation methods as well (instead of presentViewController) */
self.present(recognizerRunneViewController, animated: true, completion: nil)
}
After scanning, I check in the delegate callback for a valid state, and then I try to retrieve the values from the cardRecognizer, but I'm getting nothing but crashes:
func blinkCardOverlayViewControllerDidFinishScanning(_ blinkCardOverlayViewController: MBCBlinkCardOverlayViewController, state: MBCRecognizerResultState) {
// this is done on background thread
// check for valid state
if state == .valid {
CRASHES HERE
guard let result = blinkCardRecognizer?.combinedResult else {
return
}
// CRASHES HERE
print (result)
// first, pause scanning until we process all the results
blinkCardOverlayViewController.recognizerRunnerViewController?.pauseScanning()
DispatchQueue.main.async(execute: {() -> Void in
print(self.blinkCardRecognizer)
// self.dismiss(animated: true, completion: nil)
})
}
}
What am I missing here?

You've done everything correctly regarding the first block of code.
Regarding the second part (in the blinkCardOverlayViewControllerDidFinishScanning method), the .combinedResult is the parent of the result object, so you can use the blinkCardRecognizer.result instead.
Also, there seems to be an issue with the recognizer's description method (blinkCardRecognizer.result), so you would need to specify which information you want to extract.
An example code would be:
extension ViewController: MBCBlinkCardOverlayViewControllerDelegate {
func blinkCardOverlayViewControllerDidFinishScanning(_ blinkCardOverlayViewController: MBCBlinkCardOverlayViewController, state: MBCRecognizerResultState) {
/** This is done on background thread */
if state == .valid {
guard let result = blinkCardRecognizer?.result else {
return
}
blinkCardOverlayViewController.recognizerRunnerViewController?.pauseScanning()
DispatchQueue.main.async(execute: {() -> Void in
print(result.cardNumber)
})
}
}

Related

Microblink : Scanning both side of ID card

I am trying to scan both side of national card with mircoblink, based on their documentation for scanning both side you have to use MBDocumentVerificationOverlayViewController for controller and MBBlinkIdCombinedRecognizer for recognizer. but only my front side scanning works well.
I am using demo serial key, I don't know is it related to to my serial key or not.
here is my code:
/** Create BlinkID recognizer */
blinkIdRecognizer = MBBlinkIdCombinedRecognizer()
/** Create BlinkID settings */
let settings : MBDocumentVerificationOverlaySettings = MBDocumentVerificationOverlaySettings()
/** Crate recognizer collection */
let recognizerCollection : MBRecognizerCollection = MBRecognizerCollection(recognizers: [blinkIdRecognizer!])
/** Create your overlay view controller */
let documentOverlayViewController : MBDocumentVerificationOverlayViewController = MBDocumentVerificationOverlayViewController(settings: settings, recognizerCollection: recognizerCollection, delegate: self)
/** Create recognizer view controller with wanted overlay view controller */
let recognizerRunneViewController : UIViewController = MBViewControllerFactory.recognizerRunnerViewController(withOverlayViewController: documentOverlayViewController)
/** Present the recognizer runner view controller. You can use other presentation methods as well (instead of presentViewController) */
present(recognizerRunneViewController, animated: true, completion: nil)
This is my delegate code:
extension MyVC: MBDocumentVerificationOverlayViewControllerDelegate {
func documentVerificationOverlayViewControllerDidFinishScanningFirstSide(_ documentVerificationOverlayViewController: MBDocumentVerificationOverlayViewController) {
print("First Side Scanned")
}
func documentVerificationOverlayViewControllerDidFinishScanning(_ documentVerificationOverlayViewController: MBDocumentVerificationOverlayViewController, state: MBRecognizerResultState) {
if (self.blinkIdRecognizer?.combinedResult.resultState == MBRecognizerResultState.valid) {
guard let result = blinkIdRecognizer?.combinedResult else {
return
}
DispatchQueue.main.async {
if self.blinkIdRecognizer?.combinedResult.scanningFirstSideDone == true {
} else {
documentVerificationOverlayViewController.dismiss(animated: true, completion: nil)
}
}
}
}
func documentVerificationOverlayViewControllerDidTapClose(_ documentVerificationOverlayViewController: MBDocumentVerificationOverlayViewController) {
self.dismiss(animated: true, completion: nil)
}
}
And scanning first side delegate never get called, but I see response in DidFinish
thanks for any help
What version of the SDK are you using?
In version 5.2, we have added scanning for both the front and backside of the German ID.
You can download the latest release here:
https://github.com/BlinkID/blinkid-ios/releases
Can you please test it now and let us know if that worked?
Milan
Last time I've worked with microblink was over a year ago but if I recall correctly documentVerificationOverlayViewControllerDidFinishScanningFirstSide is only available for the supported id cards.
If you're scanning an ID card from another country you'll need to implement that yourself.
For example:
func documentVerificationOverlayViewControllerDidFinishScanning(_ documentVerificationOverlayViewController: MBDocumentVerificationOverlayViewController, state: MBRecognizerResultState) {
if step == .first {
// Present another ViewController for the back
showBackScanner()
} else {
processData()
}
}

Trigger UIAlertViewController Based on Time

I have UITable to display different animals. When you select a cell in the table, a new view controller with a large UIImage is pushed. Currently, when you zoom in on the image, a UIAlertView is triggered that asks the user if they would like to download hi res images. If they click yes, the "hi-res-flag" is set to "yes" in user defaults and they no longer see the pop up. However, if they select no, the hi-res-flag will continue to pop up each time they zoom in on a photo.
Instead, if they answer no, I would like to have this flag pop up occasionally. Not every time the click a cell in the species table, nor every time they open the app. Something more like once or twice a month. Is there a way to use time in the logic of an iOS app? For instance, erase the value set for "high-res-flag" (if already equals 'no') in user defaults, once a month?
Store the time you showed the alert last in the user preferences, and then check that value every time before you present the alert whether a certain time has passed.
I have written a time checker class that does the job. The code is in Swift. You can use it from your Objective-C code as well. You can find this code in gist here.
Solution
Below, you use the viewWillAppear delegate method to see if the hiResFlag is existing. If it is present and false, then you check to see if you can display the popup:
import UIKit
class ImageViewController: UIViewController {
//Whenever you enter the Image View Controller, you check whether to show popup or not
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let hiResFlag = hiResFlag {
if hiResFlag == false {
if PopUpTimeChecker.shouldShowPopUp() {
self.presentAlert()
}
}
}
}
func presentAlert() {
let alert = UIAlertController.init(title: nil, message: "Show Pop up", preferredStyle: .alert)
let action = UIAlertAction.init(title: "Yeahh!", style: .default, handler: nil)
alert.addAction(action)
self.present(alert, animated: true, completion: nil)
}
}
The following code implements the time-checking algorithm. Edit popUpTimeInterval below for setting your minimum time. Right now, it is set to be 15 days (in seconds). Once in every 15 days the pop-up will be shown when you call the shouldShowPopUp method.
import UIKit
//Below 4 variables, I have made them Global. No need to make them global in your case
#objc var popUpTimeInterval: UInt64 = 1296000 //15 days in seconds
#objc var hiResFlag: Bool? {
get {
return UserDefaults.standard.object(forKey: "HiResFlag") as? Bool
}
set {
UserDefaults.standard.setValue(newValue, forKey: "HiResFlag")
}
}
#objc var isFirstTimePopUp: Bool {
get {
let value = UserDefaults.standard.object(forKey: "IsFirstTimePopUp")
return value == nil ? true : value as! Bool
}
set {
UserDefaults.standard.setValue(newValue, forKey: "IsFirstTimePopUp")
}
}
#objc var lastDateOfPopUp: Date? {
get {
return UserDefaults.standard.object(forKey: "LastDateOfPopUp") as? Date
}
set {
UserDefaults.standard.setValue(newValue, forKey: "LastDateOfPopUp")
}
}
#objc class PopUpTimeChecker {
#objc static fileprivate func setLastPopUpDate() {
//Setting current date to last shown pop up date
lastDateOfPopUp = Date()
}
#objc static fileprivate func timeIntervalSinceLastPopUp() -> UInt64 {
//Returning how much time (in seconds) has passed from last popup date until now
return UInt64(Date().timeIntervalSince(lastDateOfPopUp!))
}
#objc static func shouldShowPopUp() -> Bool {
//We proceed further only if we have the last date when pop up was displayed, else we create and set it as the current date
if let _ = lastDateOfPopUp {
let timeInterval = timeIntervalSinceLastPopUp()
if timeInterval > popUpTimeInterval {
self.setLastPopUpDate()
return true //Show pop up
} else {
if isFirstTimePopUp {
//If this is the first time, you just allow the pop up to show, don't allow otherwise
isFirstTimePopUp = false
return true
} else {
return false
}
}
} else {
self.setLastPopUpDate() //Since we don't have a last date, we set it here for starting off
return self.shouldShowPopUp() //Recursively call method
}
}
}

Swift 3 pass data through completion with userID

I'm trying to run a check on Firebase to see if a user exists, then I need to check for specific vales before continuing. I currently have this:
func myFirebaseNetworkDataRequest(finished: () -> Void) {
if let user = FIRAuth.auth()?.currentUser {
self.userUUID = (user.uid)
getUser(userUUID: self.userUUID)
finished()
}
}
In my view did load:
myFirebaseNetworkDataRequest {
// perform further operations here after data is fetched
if AppState.sharedInstance.user == true {
//present 1st view controller
} else {
//present 2nd view controller
}
In my "getUser" function:
func getUser(userUUID: String) {
let userFacebookRef = FIRDatabase.database().reference(withPath: "users").child(userUUID)
//The rest of the Firebase function.
AppState.sharedInstance.user == results.active
//active is = to true
What currently happens is that if presents the 2nd view controller because firebase hasent finished yet. I realize I need a block because firebase is already asnyc but how do I send userUUID through the closure/block?
You can have your closure parameter have an parameter of it's own. Something like,
func myFirebaseNetworkDataRequest(finished: (_ isAuthenticated: Bool) -> Void)
Then in your viewDidLoad, you would make your request...
open override func viewDidLoad() {
self.myFirebaseNetworkDataRequest( (isAuthenticated) -> {
if isAuthenticated {
// Present VC1
} else {
// Present VC 2
}
}
}

how can I handle properly the behavior of camera in my Swift app?

In my swift app I'm allowing users to take photos.
For that purpose I've decided to use CameraManager from here: https://github.com/imaginary-cloud/CameraManager
When user opens my app, he sees a button - when he presses it, the camera view appears and he can take a photo. He also can dismiss the camera view and later on, at some point, press the button one more time to open camera view again.
If I understand it correctly from the plugin docs, I need to add a camera view to my view during first usage, then - in case of dismiss - invoke stopCaptureSession(), and during every next usage call resumeCaptureSession().
Currently in my swift code I have three methods:
let cameraManager = CameraManager()
fileprivate func addCameraToView()
{
cameraManager.addPreviewLayerToView(cameraView, newCameraOutputMode: CameraOutputMode.stillImage)
}
fileprivate func stopCaptureSession() {
cameraManager.stopCaptureSession()
}
fileprivate func resumeCaptreSession() {
cameraManager.resumeCaptureSession()
}
The IBAction for the button has the following code:
let currentCameraState = cameraManager.currentCameraStatus()
if currentCameraState == .notDetermined {
cameraManager.askUserForCameraPermission({ permissionGranted in
if permissionGranted {
self.resumeCaptreSession()
}
})
} else if (currentCameraState == .ready) {
self.resumeCaptreSession()
} else {
print("we do not have access to camera")
}
and in the IBAction for the dismiss button I had:
print("cancelling camera")
stopCaptureSession()
To make it work properly, I need to call addCameraToView() somewhere earlier - until now I was adding it in viewDidLoad, but I realized that I cannot do that because while doing so - the camera stays active until user presses the dismiss button.
So I thought about changing my code in IBAction for the camera button and add a camera from there. However, I have to add it only in case it wasn't add before - in the other case I need to call resumeCaptureSession().
The problem is that in CameraManager the function responsible for adding camera to the view is declared like this:
open func addPreviewLayerToView(_ view: UIView, newCameraOutputMode: CameraOutputMode) -> CameraState {
return addLayerPreviewToView(view, newCameraOutputMode: newCameraOutputMode, completion: nil)
}
open func addLayerPreviewToView(_ view: UIView, newCameraOutputMode: CameraOutputMode, completion: ((Void) -> Void)?) -> CameraState {
if _canLoadCamera() {
if let _ = embeddingView {
if let validPreviewLayer = previewLayer {
validPreviewLayer.removeFromSuperlayer()
}
}
if cameraIsSetup {
_addPreviewLayerToView(view)
cameraOutputMode = newCameraOutputMode
if let validCompletion = completion {
validCompletion()
}
} else {
_setupCamera({ Void -> Void in
self._addPreviewLayerToView(view)
self.cameraOutputMode = newCameraOutputMode
if let validCompletion = completion {
validCompletion()
}
})
}
}
return _checkIfCameraIsAvailable()
}
and resumeCaptureSession() is defined like this:
open func resumeCaptureSession() {
if let validCaptureSession = captureSession {
if !validCaptureSession.isRunning && cameraIsSetup {
validCaptureSession.startRunning()
_startFollowingDeviceOrientation()
}
} else {
if _canLoadCamera() {
if cameraIsSetup {
stopAndRemoveCaptureSession()
}
_setupCamera({Void -> Void in
if let validEmbeddingView = self.embeddingView {
self._addPreviewLayerToView(validEmbeddingView)
}
self._startFollowingDeviceOrientation()
})
}
}
}
So my question is - when user opens camera view, how can I check if camera was added to the view before, and if it was added - call resumeCaptureSession(), otherwise do not call it and just leave it with calling addCameraToView?

Label does not update using Swift

I'm trying to improve a GitHub project I forked (https://github.com/giacmarangoni/Swift-Radio-Pro/tree/xcode8).
After some fixes and changes everything seems to work good but suddenly I noticed a really strange behavior.
When I open "NowPlayingViewController" for the first time and station starts to stream, everything is working and AVPlayer delegate updates user interface as expected (songLabel, titleLabel and albumArtwork).
After that, without stopping radio streaming, I tried to go back to "StationsViewController" and immediately to reopen "NowPlayingViewController" using "Now playing" button.
At this point delegation is still active, streaming is going on, but when song changes all variables in this view controller are updated but I can't say the same for the user interface. I tried to debug and I noticed that labels are populated but not updated. UI updates in the main thread and setNeedDisplay didn't help.
NowPlayingViewController
AVPlayer setup:
func setUpPlayer(){
radioPlayer = Player.radio
radioPlayer.rate = 1
NotificationCenter.default.addObserver(
self,
selector: #selector(self.playerItemDidReachEnd),
name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
object: self.radioPlayer.currentItem
)
}
Here you can find func onMetaData(_ metaData: [AVMetadataItem]?)).
//*****************************************************************
// MARK: - AVPlayerItem Delegate (for metadata)
//*****************************************************************
extension NowPlayingViewController: CustomAVPlayerItemDelegate {
func onMetaData(_ metaData: [AVMetadataItem]?) {
if let metaDatas = metaData{
startNowPlayingAnimation()
let firstMeta: AVMetadataItem = metaDatas.first!
let metaData = firstMeta.value as! String
var stringParts = [String]()
if metaData.range(of: " - ") != nil {
stringParts = metaData.components(separatedBy: " - ")
} else {
stringParts = metaData.components(separatedBy: "-")
}
// Set artist & songvariables
let currentSongName = track.title
track.artist = stringParts[0].decodeAllChars()
track.title = stringParts[0].decodeAllChars()
if stringParts.count > 1 {
track.title = stringParts[1].decodeAllChars()
}
if track.artist == "" && track.title == "" {
track.artist = currentStation.stationDesc
track.title = currentStation.stationName
}
DispatchQueue.main.async {
if currentSongName != self.track.title {
if kDebugLog {
print("METADATA artist: \(self.track.artist) | title: \(self.track.title)")
}
// Update Labels
self.artistLabel.text = self.track.artist
self.songLabel.text = self.track.title
self.updateUserActivityState(self.userActivity!)
// songLabel animation
self.songLabel.animation = "zoomIn"
self.songLabel.duration = 1.5
self.songLabel.damping = 1
self.songLabel.animate()
// Update Stations Screen
self.delegate?.songMetaDataDidUpdate(self.track)
// Query API for album art
self.resetAlbumArtwork()
self.queryAlbumArt()
}
}
}
}
}
This method is observed in "CustomAVPlayerItem" according to timedMetaData key path; It's fired every time AVPlayer metadatas change. This class is a subclass of AVPlayerItem:
import MediaPlayer
import Foundation
protocol CustomAVPlayerItemDelegate {
func onMetaData(_ metaData:[AVMetadataItem]?)
}
//*****************************************************************
// Makes sure that observers are removed before deallocation
//*****************************************************************
class CustomAVPlayerItem: AVPlayerItem {
var delegate : CustomAVPlayerItemDelegate?
init(url URL:URL)
{
if kDebugLog {print("CustomAVPlayerItem.init")}
super.init(asset: AVAsset(url: URL) , automaticallyLoadedAssetKeys:[])
addObserver(self, forKeyPath: "timedMetadata", options: NSKeyValueObservingOptions.new, context: nil)
}
deinit{
if kDebugLog {print("CustomAVPlayerItem.deinit")}
removeObserver(self, forKeyPath: "timedMetadata")
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let avpItem: AVPlayerItem = object as? AVPlayerItem {
if keyPath == "timedMetadata" {
delegate?.onMetaData(avpItem.timedMetadata)
}
}
}
}
The following is my AVPlayer:
import MediaPlayer
//*****************************************************************
// This is a singleton struct using Swift
//*****************************************************************
struct Player {
static var radio = AVPlayer()
}
This is the segue function I use to open to "NowPlayingViewController". StationsViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "NowPlaying" {
self.title = ""
firstTime = false
let nowPlayingVC = segue.destination as! NowPlayingViewController
nowPlayingVC.delegate = self
if let indexPath = (sender as? IndexPath) {
// User clicked on row, load/reset station
if searchController.isActive {
currentStation = searchedStations[indexPath.row]
} else {
currentStation = stations[indexPath.row]
}
nowPlayingVC.currentStation = currentStation
nowPlayingVC.newStation = true
} else {
// User clicked on a now playing button
if let currentTrack = currentTrack {
// Return to NowPlaying controller without reloading station
nowPlayingVC.track = currentTrack
nowPlayingVC.currentStation = currentStation
nowPlayingVC.newStation = false
} else {
// Issue with track, reload station
nowPlayingVC.currentStation = currentStation
nowPlayingVC.newStation = true
}
}
}
}
Here's what I think you're not understanding and what's actually going on.
Normally, when you "go back" from a pushed view controller, the pushed view controller is popped and destroyed. Your pushed view controller is a NowPlayingViewController. It should be destroyed when you "go back" from it to the StationsViewController. Thus, when you show the NowPlayingViewController again, you would have to create a new, different NowPlayingViewController.
Okay, so far so good, provided you understand all of that. But in your case there is a further complication: you have a leak! Your old NowPlayingViewController is not being destroyed. Thus, when you "go back" to the StationsViewController and show the NowPlayingViewController for a second time, there are now two NowPlayingViewControllers — the new one that you see, and the old one that is leaking.
Okay, so your logging continues to show the old NowPlayingViewController, which is still observing and updating. But your eyes are seeing the new NowPlayingViewController, which is doing nothing. And that explains the phenomena you have described.
If this is right — and, from what you've said, I'm pretty sure it is — then you need to reorganize your architecture either so that you don't get this leak or so that when you show the NowPlayingViewController the second time you show the same NowPlayingViewController rather than creating a different one. (The first approach would be better.)

Resources