iOS zxing scanner, iOS localization
This commit is contained in:
@@ -1,250 +1,308 @@
|
||||
//
|
||||
// ScannerViewController.swift
|
||||
// Runner
|
||||
//
|
||||
// Created by Ivan Murashov on 13/07/2017.
|
||||
// Copyright © 2017 The Chromium Authors. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Flutter
|
||||
|
||||
extension ZBarSymbolSet: Sequence {
|
||||
//public typealias Element = ZBarSymbol
|
||||
//public typealias Iterator = NSFastEnumerationIterator
|
||||
|
||||
public func makeIterator() -> NSFastEnumerationIterator {
|
||||
return NSFastEnumerationIterator(self)
|
||||
}
|
||||
}
|
||||
|
||||
@objc class ScannerViewController: UIViewController, ZBarReaderDelegate, UITextFieldDelegate {
|
||||
|
||||
@objc class ScannerViewController: UIViewController, ZXCaptureDelegate, UITextFieldDelegate {
|
||||
|
||||
enum AppLocale {
|
||||
case ru
|
||||
case en
|
||||
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")!
|
||||
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"
|
||||
case .card: return "card"
|
||||
case .phone: return "phone"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var appLocale: AppLocale
|
||||
|
||||
// @IBOutlet weak var decodedLabel: UILabel!
|
||||
|
||||
private var captureSizeTransform: CGAffineTransform?
|
||||
|
||||
var buttonState: ButtonState = .card
|
||||
|
||||
|
||||
var platformChannel: FlutterMethodChannel?
|
||||
let readerViewController = ZBarReaderViewController()
|
||||
|
||||
|
||||
|
||||
// Квадрат для наведения на цель (надеюсь)
|
||||
let scanRectView = UIView()
|
||||
|
||||
//Вьюшка для верхнего меню
|
||||
let topView = UIView()
|
||||
|
||||
|
||||
//Окно ввода кода
|
||||
let textField = UITextField()
|
||||
|
||||
|
||||
//Кнопка настроек
|
||||
var settingButton: UIButton!
|
||||
|
||||
|
||||
var strings = [String:String]()
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
self.appLocale = .en
|
||||
super.init(coder: aDecoder)
|
||||
}
|
||||
|
||||
init(locale: String) {
|
||||
switch locale {
|
||||
case "ru": self.appLocale = .ru; break
|
||||
default: self.appLocale = .en
|
||||
}
|
||||
|
||||
init(strings: [String: String]) {
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
strings.forEach { (k,v) in self.strings[k] = v }
|
||||
}
|
||||
|
||||
func getCardHint() -> String {
|
||||
switch self.appLocale {
|
||||
case .ru: return "Ввести номер карты"
|
||||
default: return "Enter the barcode"
|
||||
}
|
||||
}
|
||||
|
||||
func getPhoneHint() -> String {
|
||||
switch self.appLocale {
|
||||
case .ru: return "Телефон 79XXXXXXXXX"
|
||||
default: return "Phone 79XXXXXXXXX"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func getInputHint() -> String {
|
||||
switch self.buttonState {
|
||||
case .card: return self.getCardHint()
|
||||
case .phone: return self.getPhoneHint()
|
||||
case .card: return strings["enter_manual"]!
|
||||
case .phone: return strings["enter_phone"]!
|
||||
}
|
||||
}
|
||||
|
||||
func getErrorTitle() -> String {
|
||||
switch self.appLocale {
|
||||
case .ru: return "Ошибка"
|
||||
default: return "Error"
|
||||
}
|
||||
}
|
||||
|
||||
func getCardErrorText() -> String {
|
||||
switch self.appLocale {
|
||||
case .ru: return "Пользователь с номером карты %@ не найден"
|
||||
default: return "User with card number %@ not found"
|
||||
}
|
||||
}
|
||||
|
||||
func getPhoneErrorText() -> String {
|
||||
switch self.appLocale {
|
||||
case .ru: return "Пользователь с номером телефона %@ не найден"
|
||||
default: return "User with phone number %@ not found"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func getErrorText() -> String {
|
||||
switch self.buttonState {
|
||||
case .card: return self.getCardErrorText()
|
||||
case .phone: return self.getPhoneErrorText()
|
||||
case .card: return strings["user_card_not_found"]!
|
||||
case .phone: return strings["user_phone_not_found"]!
|
||||
}
|
||||
}
|
||||
|
||||
func getDismissText() -> String {
|
||||
switch self.appLocale {
|
||||
case .ru: return "Скрыть"
|
||||
default: return "Dismiss"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let capture: ZXCapture = ZXCapture()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
readerViewController.readerDelegate = self
|
||||
readerViewController.readerView.zoom = 1.0
|
||||
readerViewController.showsZBarControls = false
|
||||
|
||||
capture.camera = capture.back()
|
||||
capture.focusMode = .continuousAutoFocus
|
||||
view.layer.addSublayer((capture.layer)!)
|
||||
view.addSubview(scanRectView)
|
||||
|
||||
settingButton = UIButton(type: .system)
|
||||
settingButton.addTarget(self, action: #selector(ScannerViewController.buttonTouch), for: .touchUpInside)
|
||||
|
||||
self.addChildViewController(readerViewController)
|
||||
self.view.addSubview(readerViewController.view)
|
||||
readerViewController.didMove(toParentViewController: self)
|
||||
|
||||
readerViewController.view.addSubview(topView)
|
||||
|
||||
topView.addSubview(textField)
|
||||
topView.addSubview(settingButton)
|
||||
|
||||
view.addSubview(topView)
|
||||
view.bringSubview(toFront: topView)
|
||||
|
||||
textField.delegate = self
|
||||
//textField.keyboardType = .numberPad
|
||||
|
||||
//Looks for single or multiple taps.
|
||||
|
||||
let tap = UITapGestureRecognizer(target: self, action: #selector(ScannerViewController.hideKeyboard))
|
||||
readerViewController.view.addGestureRecognizer(tap)
|
||||
|
||||
view.addGestureRecognizer(tap)
|
||||
|
||||
settingButton.setImage(self.buttonState.icon, for: .normal)
|
||||
textField.placeholder = self.getInputHint()
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
topView.backgroundColor = UIColor.white
|
||||
textField.borderStyle = .roundedRect
|
||||
capture.delegate = self
|
||||
applyOrientation()
|
||||
}
|
||||
|
||||
override func viewWillLayoutSubviews() {
|
||||
scanRectView.frame = view.bounds
|
||||
topView.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: 56)
|
||||
settingButton.frame = CGRect(x: 8, y: 26, width: 20, height: 20)
|
||||
textField.frame = CGRect(x: settingButton.frame.maxX + 8, y: 21,
|
||||
width: view.frame.size.width - settingButton.frame.maxX - 16, height: 30)
|
||||
}
|
||||
|
||||
func hideKeyboard() {
|
||||
view.endEditing(false)
|
||||
}
|
||||
|
||||
|
||||
func buttonTouch(){
|
||||
|
||||
|
||||
switch self.buttonState {
|
||||
case .card: self.buttonState = .phone
|
||||
case .phone: self.buttonState = .card
|
||||
case .card: self.buttonState = .phone
|
||||
case .phone: self.buttonState = .card
|
||||
}
|
||||
settingButton.setImage(self.buttonState.icon, for: .normal)
|
||||
textField.placeholder = self.getInputHint()
|
||||
}
|
||||
|
||||
|
||||
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||
|
||||
|
||||
sendResult(textField.text!)
|
||||
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
func sendResult(_ str: String) {
|
||||
platformChannel?.invokeMethod("findUser", arguments: [str, buttonState.searchType], result: { (result: Any?) in
|
||||
if result is FlutterError {
|
||||
self.showErrorAlert(str)
|
||||
} else {
|
||||
|
||||
|
||||
self.dismiss(animated: true) {
|
||||
self.platformChannel?.invokeMethod("purchase", arguments: [result, str])
|
||||
}
|
||||
}
|
||||
print("result: \(result.debugDescription )")
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
func showErrorAlert(_ str: String) {
|
||||
let alertController = UIAlertController(
|
||||
title: self.getErrorTitle(),
|
||||
title: strings["error"]!,
|
||||
message: String(format: self.getErrorText(), str),
|
||||
preferredStyle: UIAlertControllerStyle.alert
|
||||
)
|
||||
alertController.addAction(UIAlertAction(title: self.getDismissText(), style: UIAlertActionStyle.default,handler: nil))
|
||||
|
||||
// if let appDelegate = UIApplication.shared.delegate as? FlutterAppDelegate {
|
||||
// appDelegate.window.rootViewController?.present(alertController, animated: true, completion: nil)
|
||||
// }
|
||||
alertController.addAction(UIAlertAction(title: strings["dismiss"]!, style: UIAlertActionStyle.default,handler: nil))
|
||||
self.present(alertController, animated: true, completion: nil)
|
||||
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
topView.backgroundColor = UIColor.white
|
||||
textField.borderStyle = .roundedRect
|
||||
super.viewWillAppear(animated)
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
}
|
||||
|
||||
override func viewWillLayoutSubviews() {
|
||||
|
||||
readerViewController.view.frame = view.bounds
|
||||
topView.frame = CGRect(x: 0, y: 0, width: readerViewController.view.frame.size.width, height: 40)
|
||||
textField.frame = CGRect(x: settingButton.frame.maxX + 8 + 20 + 5, y: 5, width: readerViewController.view.frame.size.width - 50, height: 30)
|
||||
settingButton.frame = CGRect(x: 8, y: 10, width: 20, height: 20)
|
||||
|
||||
}
|
||||
|
||||
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
|
||||
guard let symbols = info[ZBarReaderControllerResults] as? ZBarSymbolSet else { return }
|
||||
|
||||
for symbol in symbols {
|
||||
|
||||
if let symbol = symbol as? ZBarSymbol, let data = symbol.data {
|
||||
sendResult(data)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
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)
|
||||
}
|
||||
// We got a result. Display information about the result onscreen.
|
||||
let formatString: String = barcodeFormat(toString: result.barcodeFormat)
|
||||
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()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user