// // QRView.swift // flutter_qr // // Created by Julius Canute on 21/12/18. // import Foundation import MTBBarcodeScanner public class QRView:NSObject,FlutterPlatformView { @IBOutlet var previewView: UIView! var scanner: MTBBarcodeScanner? var registrar: FlutterPluginRegistrar var channel: FlutterMethodChannel var cameraFacing: MTBCamera // Codabar, maxicode, rss14 & rssexpanded not supported. Replaced with qr. // UPCa uses ean13 object. var QRCodeTypes = [ 0: AVMetadataObject.ObjectType.aztec, 1: AVMetadataObject.ObjectType.qr, 2: AVMetadataObject.ObjectType.code39, 3: AVMetadataObject.ObjectType.code93, 4: AVMetadataObject.ObjectType.code128, 5: AVMetadataObject.ObjectType.dataMatrix, 6: AVMetadataObject.ObjectType.ean8, 7: AVMetadataObject.ObjectType.ean13, 8: AVMetadataObject.ObjectType.interleaved2of5, 9: AVMetadataObject.ObjectType.qr, 10: AVMetadataObject.ObjectType.pdf417, 11: AVMetadataObject.ObjectType.qr, 12: AVMetadataObject.ObjectType.qr, 13: AVMetadataObject.ObjectType.qr, 14: AVMetadataObject.ObjectType.ean13, 15: AVMetadataObject.ObjectType.upce ] public init(withFrame frame: CGRect, withRegistrar registrar: FlutterPluginRegistrar, withId id: Int64, params: Dictionary){ self.registrar = registrar previewView = UIView(frame: frame) cameraFacing = MTBCamera.init(rawValue: UInt(Int(params["cameraFacing"] as! Double))) ?? MTBCamera.back channel = FlutterMethodChannel(name: "net.touchcapture.qr.flutterqr/qrview_\(id)", binaryMessenger: registrar.messenger()) } deinit { scanner?.stopScanning() } public func view() -> UIView { channel.setMethodCallHandler({ [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in switch(call.method){ case "setDimensions": let arguments = call.arguments as! Dictionary self?.setDimensions(result, width: arguments["width"] ?? 0, height: arguments["height"] ?? 0, scanAreaWidth: arguments["scanAreaWidth"] ?? 0, scanAreaHeight: arguments["scanAreaHeight"] ?? 0, scanAreaOffset: arguments["scanAreaOffset"] ?? 0) case "startScan": self?.startScan(call.arguments as! Array, result) case "flipCamera": self?.flipCamera(result) case "toggleFlash": self?.toggleFlash(result) case "pauseCamera": self?.pauseCamera(result) case "stopCamera": self?.stopCamera(result) case "resumeCamera": self?.resumeCamera(result) case "getCameraInfo": self?.getCameraInfo(result) case "getFlashInfo": self?.getFlashInfo(result) case "getSystemFeatures": self?.getSystemFeatures(result) default: result(FlutterMethodNotImplemented) return } }) return previewView } func setDimensions(_ result: @escaping FlutterResult, width: Double, height: Double, scanAreaWidth: Double, scanAreaHeight: Double, scanAreaOffset: Double) { // Then set the size of the preview area. previewView.frame = CGRect(x: 0, y: 0, width: width, height: height) // Then set the size of the scan area. let midX = self.view().bounds.midX let midY = self.view().bounds.midY if let sc: MTBBarcodeScanner = scanner { // Set the size of the preview if preview is already created. if let previewLayer = sc.previewLayer { previewLayer.frame = self.previewView.bounds } } else { // Create new preview. scanner = MTBBarcodeScanner(previewView: previewView) } // Set scanArea if provided. if (scanAreaWidth != 0 && scanAreaHeight != 0) { scanner?.didStartScanningBlock = { self.scanner?.scanRect = CGRect(x: Double(midX) - (scanAreaWidth / 2), y: Double(midY) - (scanAreaHeight / 2), width: scanAreaWidth, height: scanAreaHeight) // Set offset if provided. if (scanAreaOffset != 0) { let reversedOffset = -scanAreaOffset self.scanner?.scanRect = (self.scanner?.scanRect.offsetBy(dx: 0, dy: CGFloat(reversedOffset)))! } } } return result(width) } func startScan(_ arguments: Array, _ result: @escaping FlutterResult) { // Check for allowed barcodes var allowedBarcodeTypes: Array = [] arguments.forEach { arg in allowedBarcodeTypes.append( QRCodeTypes[arg]!) } MTBBarcodeScanner.requestCameraPermission(success: { [weak self] permissionGranted in guard let self = self else { return } self.channel.invokeMethod("onPermissionSet", arguments: permissionGranted) if permissionGranted { do { try self.scanner?.startScanning(with: self.cameraFacing, resultBlock: { [weak self] codes in if let codes = codes { for code in codes { var typeString: String; switch(code.type) { case AVMetadataObject.ObjectType.aztec: typeString = "AZTEC" case AVMetadataObject.ObjectType.code39: typeString = "CODE_39" case AVMetadataObject.ObjectType.code93: typeString = "CODE_93" case AVMetadataObject.ObjectType.code128: typeString = "CODE_128" case AVMetadataObject.ObjectType.dataMatrix: typeString = "DATA_MATRIX" case AVMetadataObject.ObjectType.ean8: typeString = "EAN_8" case AVMetadataObject.ObjectType.ean13: typeString = "EAN_13" case AVMetadataObject.ObjectType.itf14, AVMetadataObject.ObjectType.interleaved2of5: typeString = "ITF" case AVMetadataObject.ObjectType.pdf417: typeString = "PDF_417" case AVMetadataObject.ObjectType.qr: typeString = "QR_CODE" case AVMetadataObject.ObjectType.upce: typeString = "UPC_E" default: return } let bytes = { () -> Data? in if #available(iOS 11.0, *) { switch (code.descriptor) { case let qrDescriptor as CIQRCodeDescriptor: return qrDescriptor.errorCorrectedPayload case let aztecDescriptor as CIAztecCodeDescriptor: return aztecDescriptor.errorCorrectedPayload case let pdf417Descriptor as CIPDF417CodeDescriptor: return pdf417Descriptor.errorCorrectedPayload case let dataMatrixDescriptor as CIDataMatrixCodeDescriptor: return dataMatrixDescriptor.errorCorrectedPayload default: return nil } } else { return nil } }() let result = { () -> [String : Any]? in guard let stringValue = code.stringValue else { guard let safeBytes = bytes else { return nil } return ["type": typeString, "rawBytes": safeBytes] } guard let safeBytes = bytes else { return ["code": stringValue, "type": typeString] } return ["code": stringValue, "type": typeString, "rawBytes": safeBytes] }() guard result != nil else { continue } if allowedBarcodeTypes.count == 0 || allowedBarcodeTypes.contains(code.type) { self?.channel.invokeMethod("onRecognizeQR", arguments: result) } } } }) } catch { let scanError = FlutterError(code: "unknown-error", message: "Unable to start scanning", details: error) result(scanError) } } }) } func stopCamera(_ result: @escaping FlutterResult) { if let sc: MTBBarcodeScanner = self.scanner { if sc.isScanning() { sc.stopScanning() } } } func getCameraInfo(_ result: @escaping FlutterResult) { result(self.cameraFacing.rawValue) } func flipCamera(_ result: @escaping FlutterResult) { if let sc: MTBBarcodeScanner = self.scanner { if sc.hasOppositeCamera() { sc.flipCamera() self.cameraFacing = sc.camera } return result(sc.camera.rawValue) } return result(FlutterError(code: "404", message: "No barcode scanner found", details: nil)) } func getFlashInfo(_ result: @escaping FlutterResult) { if let sc: MTBBarcodeScanner = self.scanner { result(sc.torchMode.rawValue != 0) } else { let error = FlutterError(code: "cameraInformationError", message: "Could not get flash information", details: nil) result(error) } } func toggleFlash(_ result: @escaping FlutterResult){ if let sc: MTBBarcodeScanner = self.scanner { if sc.hasTorch() { sc.toggleTorch() return result(sc.torchMode == MTBTorchMode(rawValue: 1)) } return result(FlutterError(code: "404", message: "This device doesn\'t support flash", details: nil)) } return result(FlutterError(code: "404", message: "No barcode scanner found", details: nil)) } func pauseCamera(_ result: @escaping FlutterResult) { if let sc: MTBBarcodeScanner = self.scanner { if sc.isScanning() { sc.freezeCapture() } return result(true) } return result(FlutterError(code: "404", message: "No barcode scanner found", details: nil)) } func resumeCamera(_ result: @escaping FlutterResult) { if let sc: MTBBarcodeScanner = self.scanner { if !sc.isScanning() { sc.unfreezeCapture() } return result(true) } return result(FlutterError(code: "404", message: "No barcode scanner found", details: nil)) } func getSystemFeatures(_ result: @escaping FlutterResult) { if let sc: MTBBarcodeScanner = scanner { var hasBackCameraVar = false var hasFrontCameraVar = false let camera = sc.camera if(camera == MTBCamera(rawValue: 0)){ hasBackCameraVar = true if sc.hasOppositeCamera() { hasFrontCameraVar = true } }else{ hasFrontCameraVar = true if sc.hasOppositeCamera() { hasBackCameraVar = true } } return result([ "hasFrontCamera": hasFrontCameraVar, "hasBackCamera": hasBackCameraVar, "hasFlash": sc.hasTorch(), "activeCamera": camera.rawValue ]) } return result(FlutterError(code: "404", message: nil, details: nil)) } }