import UIKit import Flutter import DropDown import ZXingObjC @objc class ScannerViewController: UIViewController, ZXCaptureDelegate, UITextFieldDelegate { enum AppLocale { case ru case en } enum ButtonState { case card case phone var icon: UIImage { switch self { case .card: return UIImage(named: "card")! case .phone: return UIImage(named: "phone")! } } var searchType: String { switch self { case .card: return "card" case .phone: return "phone" } } } var captureSizeTransform: CGAffineTransform? var buttonState: ButtonState = .card var platformChannel: FlutterMethodChannel? var strings = [String:String]() let scanRectView = UIView() let header = UIView() let textField = UITextField() let settingsButton = UIButton(type: .system) let searchType = UIButton(type: .system) let dropDown = DropDown() required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } init(strings: [String: String]) { super.init(nibName: nil, bundle: nil) strings.forEach { (k,v) in self.strings[k] = v } } func getInputHint() -> String { switch self.buttonState { case .card: return strings["enter_manual"]! case .phone: return strings["enter_phone"]! } } func getErrorText() -> String { switch self.buttonState { case .card: return strings["user_card_not_found"]! case .phone: return strings["user_phone_not_found"]! } } func setButtonState() { switch self.buttonState { case .card: self.buttonState = .phone case .phone: self.buttonState = .card } } let capture: ZXCapture = ZXCapture() override func viewDidLoad() { super.viewDidLoad() view.layer.addSublayer((capture.layer)!) view.addSubview(scanRectView) view.addSubview(header) view.bringSubview(toFront: header) view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(ScannerViewController.hideKeyboard))) initCamera() initSearchTypeButton() initTextFIeld() initSettingsButton() initDropDown() initHeader() } private func initCamera() { capture.camera = capture.back() capture.focusMode = .continuousAutoFocus } private func initHeader() { header.addSubview(textField) header.addSubview(searchType) header.addSubview(settingsButton) } private func initTextFIeld() { textField.delegate = self textField.placeholder = self.getInputHint() } private func initSearchTypeButton() { searchType.setImage(self.buttonState.icon, for: .normal) searchType.addTarget(self, action: #selector(ScannerViewController.buttonTouch), for: .touchUpInside) } private func initDropDown() { dropDown.anchorView = settingsButton dropDown.dataSource = [strings["settings"]!, strings["faq"]!] dropDown.selectionAction = { (index: Int, item: String) in if index == 0 { self.platformChannel?.invokeMethod("settings", arguments: nil) } else if index == 1 { self.platformChannel?.invokeMethod("faq", arguments: nil) } self.dismiss(animated: false) } } private func initSettingsButton() { settingsButton.setImage(UIImage(named: "more")!, for: .normal) settingsButton.addTarget(self, action: #selector(ScannerViewController.settingsTouch), for: .touchUpInside) } func hideKeyboard() { view.endEditing(false) } func buttonTouch() { setButtonState() searchType.setImage(self.buttonState.icon, for: .normal) textField.placeholder = self.getInputHint() } func settingsTouch() { dropDown.show() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) header.backgroundColor = UIColor.white textField.borderStyle = .roundedRect capture.delegate = self applyOrientation() } // TODO: Вынести эту копипасту в методы, когда будет время override func viewWillLayoutSubviews() { // TODO: Надо бы уйти от констант, переписать на отступы какие-нибудь scanRectView.frame = view.bounds header.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: 56) searchType.frame = CGRect(x: 8, y: 26, width: 20, height: 20) textField.frame = CGRect(x: searchType.frame.maxX + 8, y: 21, width: view.frame.size.width - searchType.frame.maxX - 48, height: 30) settingsButton.frame = CGRect(x: view.frame.size.width - 30, y: 26, width: 20, height: 20) var path = UIBezierPath() path.move(to: CGPoint(x: 32, y: view.frame.size.height / 2)) path.addLine(to: CGPoint(x: view.frame.size.width - 32, y: view.frame.size.height / 2)) var shapeLayer = CAShapeLayer() shapeLayer.path = path.cgPath shapeLayer.strokeColor = UIColor.red.cgColor shapeLayer.fillColor = UIColor.clear.cgColor shapeLayer.lineWidth = 2 view.layer.addSublayer(shapeLayer) path = UIBezierPath() path.move(to: CGPoint(x: 32, y: (view.frame.size.height / 2) - 32)) path.addLine(to: CGPoint(x: 32, y: (view.frame.size.height / 2) - 64)) path.addLine(to: CGPoint(x: 64, y: (view.frame.size.height / 2) - 64)) path.move(to: CGPoint(x: view.frame.size.width - 64, y: (view.frame.size.height / 2) - 64)) path.addLine(to: CGPoint(x: view.frame.size.width - 32, y: (view.frame.size.height / 2) - 64)) path.addLine(to: CGPoint(x: view.frame.size.width - 32, y: (view.frame.size.height / 2) - 32)) path.move(to: CGPoint(x: 32, y: (view.frame.size.height / 2) + 32)) path.addLine(to: CGPoint(x: 32, y: (view.frame.size.height / 2) + 64)) path.addLine(to: CGPoint(x: 64, y: (view.frame.size.height / 2) + 64)) path.move(to: CGPoint(x: view.frame.size.width - 32, y: (view.frame.size.height / 2) + 32)) path.addLine(to: CGPoint(x: view.frame.size.width - 32, y: (view.frame.size.height / 2) + 64)) path.addLine(to: CGPoint(x: view.frame.size.width - 64, y: (view.frame.size.height / 2) + 64)) shapeLayer = CAShapeLayer() shapeLayer.path = path.cgPath shapeLayer.strokeColor = UIColor.green.cgColor shapeLayer.fillColor = UIColor.clear.cgColor shapeLayer.lineWidth = 2 view.layer.addSublayer(shapeLayer) path = UIBezierPath() path.move(to: CGPoint(x: 0, y: 56)) path.addLine(to: CGPoint(x: view.frame.size.width, y: 56)) path.addLine(to: CGPoint(x: view.frame.size.width, y: (view.frame.size.height / 2) - 64)) path.addLine(to: CGPoint(x: 0, y: (view.frame.size.height / 2) - 64)) path.addLine(to: CGPoint(x: 0, y: 56)) path.close() shapeLayer = CAShapeLayer() shapeLayer.path = path.cgPath shapeLayer.fillColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5).cgColor view.layer.addSublayer(shapeLayer) path = UIBezierPath() path.move(to: CGPoint(x: 0, y: (view.frame.size.height / 2) - 64)) path.addLine(to: CGPoint(x: 32, y: (view.frame.size.height / 2) - 64)) path.addLine(to: CGPoint(x: 32, y: (view.frame.size.height / 2) + 64)) path.addLine(to: CGPoint(x: 0, y: (view.frame.size.height / 2) + 64)) path.addLine(to: CGPoint(x: 0, y: (view.frame.size.height / 2) - 64)) path.close() shapeLayer = CAShapeLayer() shapeLayer.path = path.cgPath shapeLayer.fillColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5).cgColor view.layer.addSublayer(shapeLayer) path = UIBezierPath() path.move(to: CGPoint(x: view.frame.size.width, y: (view.frame.size.height / 2) - 64)) path.addLine(to: CGPoint(x: view.frame.size.width - 32, y: (view.frame.size.height / 2) - 64)) path.addLine(to: CGPoint(x: view.frame.size.width - 32, y: (view.frame.size.height / 2) + 64)) path.addLine(to: CGPoint(x: view.frame.size.width, y: (view.frame.size.height / 2) + 64)) path.addLine(to: CGPoint(x: view.frame.size.width, y: (view.frame.size.height / 2) - 64)) path.close() shapeLayer = CAShapeLayer() shapeLayer.path = path.cgPath shapeLayer.fillColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5).cgColor view.layer.addSublayer(shapeLayer) path = UIBezierPath() path.move(to: CGPoint(x: view.frame.size.width, y: (view.frame.size.height / 2) + 64)) path.addLine(to: CGPoint(x: 0, y: (view.frame.size.height / 2) + 64)) path.addLine(to: CGPoint(x: 0, y: view.frame.size.height)) path.addLine(to: CGPoint(x: view.frame.size.width, y: view.frame.size.height)) path.addLine(to: CGPoint(x: view.frame.size.width, y: (view.frame.size.height / 2) + 64)) path.close() shapeLayer = CAShapeLayer() shapeLayer.path = path.cgPath shapeLayer.fillColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5).cgColor view.layer.addSublayer(shapeLayer) } func textFieldShouldReturn(_ textField: UITextField) -> Bool { print("User from manual input: \(textField.text!)") sendResult(textField.text!) return true } func sendResult(_ str: String) { platformChannel?.invokeMethod("findUser", arguments: [str, buttonState.searchType], result: { (result: Any?) in if result is FlutterError { print("Result is nil (ios code)"); self.showErrorAlert(str) } else { print("Result is not nil (ios code)"); self.dismiss(animated: true) { self.platformChannel?.invokeMethod("scanSuccess", arguments: [result!, str]) } } }) } func showErrorAlert(_ str: String) { let alertController = UIAlertController( title: strings["error"]!, message: String(format: self.getErrorText(), str), preferredStyle: UIAlertControllerStyle.alert ) alertController.addAction(UIAlertAction(title: strings["dismiss"]!, style: UIAlertActionStyle.default,handler: nil)) self.present(alertController, animated: true, completion: nil) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } deinit { capture.layer.removeFromSuperlayer() } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) coordinator.animate(alongsideTransition: {(_ context: UIViewControllerTransitionCoordinatorContext) -> Void in }, completion: {(_ context: UIViewControllerTransitionCoordinatorContext) -> Void in self.applyOrientation() }) } func applyOrientation() { print("APPLY ORIENTATION") let orientation: UIInterfaceOrientation = UIApplication.shared.statusBarOrientation var scanRectRotation: Float var captureRotation: Float switch orientation { case .portrait: captureRotation = 0 scanRectRotation = 90 case .landscapeLeft: captureRotation = 90 scanRectRotation = 180 case .landscapeRight: captureRotation = 270 scanRectRotation = 0 case .portraitUpsideDown: captureRotation = 180 scanRectRotation = 270 default: captureRotation = 0 scanRectRotation = 90 } applyRectOfInterest(orientation) let transform = CGAffineTransform(rotationAngle: CGFloat(captureRotation / 180 * .pi)) capture.transform = transform capture.rotation = CGFloat(scanRectRotation) capture.layer.frame = view.frame } func applyRectOfInterest(_ orientation: UIInterfaceOrientation) { var scaleVideo: CGFloat var scaleVideoX: CGFloat var scaleVideoY: CGFloat var videoSizeX: CGFloat var videoSizeY: CGFloat var transformedVideoRect: CGRect = scanRectView.frame videoSizeX = 720 videoSizeY = 1280 if UIInterfaceOrientationIsPortrait(orientation) { scaleVideoX = view.frame.size.width / videoSizeX scaleVideoY = view.frame.size.height / videoSizeY scaleVideo = max(scaleVideoX, scaleVideoY) if scaleVideoX > scaleVideoY { transformedVideoRect.origin.y += (scaleVideo * videoSizeY - view.frame.size.height) / 2 } else { transformedVideoRect.origin.x += (scaleVideo * videoSizeX - view.frame.size.width) / 2 } } else { scaleVideoX = view.frame.size.width / videoSizeY scaleVideoY = view.frame.size.height / videoSizeX scaleVideo = max(scaleVideoX, scaleVideoY) if scaleVideoX > scaleVideoY { transformedVideoRect.origin.y += (scaleVideo * videoSizeX - view.frame.size.height) / 2 } else { transformedVideoRect.origin.x += (scaleVideo * videoSizeY - view.frame.size.width) / 2 } } captureSizeTransform = CGAffineTransform(scaleX: 1 / scaleVideo, y: 1 / scaleVideo) capture.scanRect = transformedVideoRect.applying(captureSizeTransform!) } // MARK: - Private Methods func barcodeFormat(toString format: ZXBarcodeFormat) -> String { switch format { case kBarcodeFormatAztec: return "Aztec" case kBarcodeFormatCodabar: return "CODABAR" case kBarcodeFormatCode39: return "Code 39" case kBarcodeFormatCode93: return "Code 93" case kBarcodeFormatCode128: return "Code 128" case kBarcodeFormatDataMatrix: return "Data Matrix" case kBarcodeFormatEan8: return "EAN-8" case kBarcodeFormatEan13: return "EAN-13" case kBarcodeFormatITF: return "ITF" case kBarcodeFormatPDF417: return "PDF417" case kBarcodeFormatQRCode: return "QR Code" case kBarcodeFormatRSS14: return "RSS 14" case kBarcodeFormatRSSExpanded: return "RSS Expanded" case kBarcodeFormatUPCA: return "UPCA" case kBarcodeFormatUPCE: return "UPCE" case kBarcodeFormatUPCEANExtension: return "UPC/EAN extension" default: return "Unknown" } } // MARK: - ZXCaptureDelegate Methods func captureResult(_ capture: ZXCapture, result: ZXResult) { let inverse: CGAffineTransform = captureSizeTransform!.inverted() var points = [AnyHashable]() var location = "" for resultPoint in result.resultPoints { let p = resultPoint as! ZXResultPoint let cgPoint = CGPoint(x: CGFloat(p.x), y: CGFloat(p.y)) var transformedPoint: CGPoint = cgPoint.applying(inverse) transformedPoint = scanRectView.convert(transformedPoint, to: scanRectView.window) let windowPointValue = NSValue(cgPoint: transformedPoint) location = "\(location) (\(transformedPoint.x), \(transformedPoint.y))" points.append(windowPointValue) } print("User from scanner: \(result.text)") sendResult(result.text) print(result.text) self.capture.stop() DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(2 * Double(NSEC_PER_SEC)) / Double(NSEC_PER_SEC), execute: {() -> Void in self.capture.start() }) } }