Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3abed693d4 | |||
| 02b9c1b3b9 | |||
| a80b08a3eb | |||
| d398597796 |
@@ -43,7 +43,7 @@ playground.xcworkspace
|
||||
# you should judge for yourself, the pros and cons are mentioned at:
|
||||
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
||||
#
|
||||
# Pods/
|
||||
Pods/
|
||||
|
||||
# Carthage
|
||||
#
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'DocumentsOCR'
|
||||
s.version = '1.0.2'
|
||||
s.version = '1.0.4'
|
||||
s.summary = 'A Swift framework for machine readable documents recognition'
|
||||
|
||||
# This description is used to generate tags and improve search results.
|
||||
@@ -42,6 +42,4 @@ Pod::Spec.new do |s|
|
||||
s.dependency 'TesseractOCRiOS', '~> 4.0.0'
|
||||
s.dependency 'PodAsset'
|
||||
s.dependency 'GPUImage'
|
||||
|
||||
s.pod_target_xcconfig = { 'ENABLE_BITCODE' => 'NO' }
|
||||
end
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
@@ -13,7 +13,7 @@ protocol CameraViewDelegate {
|
||||
func startTakingPictures()
|
||||
}
|
||||
|
||||
open class CameraOverlayView: UIView {
|
||||
public class CameraOverlayView: UIView {
|
||||
|
||||
@IBOutlet weak var codeBorder: UIView!
|
||||
@IBOutlet weak var takePhotoButton: UIButton!
|
||||
@@ -24,14 +24,14 @@ open class CameraOverlayView: UIView {
|
||||
|
||||
var delegate: CameraViewDelegate!
|
||||
|
||||
@IBAction func cancelButtonClicked(_ sender: UIButton) {
|
||||
@IBAction func cancelButtonClicked(sender: UIButton) {
|
||||
resetViews()
|
||||
delegate.stopTakingPictures()
|
||||
}
|
||||
|
||||
@IBAction func scanButtonClicked(_ sender: UIButton) {
|
||||
takePhotoButton.isHidden = true
|
||||
progressViewContainer.isHidden = false
|
||||
@IBAction func scanButtonClicked(sender: UIButton) {
|
||||
takePhotoButton.hidden = true
|
||||
progressViewContainer.hidden = false
|
||||
delegate.startTakingPictures()
|
||||
}
|
||||
|
||||
@@ -41,13 +41,13 @@ open class CameraOverlayView: UIView {
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)!
|
||||
self.isOpaque = false
|
||||
self.opaque = false
|
||||
}
|
||||
|
||||
func resetViews() {
|
||||
takePhotoButton.isHidden = false
|
||||
takePhotoButton.hidden = false
|
||||
progressLabel.text = ""
|
||||
progressView.progress = 0
|
||||
progressViewContainer.isHidden = true
|
||||
progressViewContainer.hidden = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,25 +8,25 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
let simpleFormatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
let simpleFormatter: NSDateFormatter = {
|
||||
let formatter = NSDateFormatter()
|
||||
formatter.dateFormat = "yyyy-MM-dd"
|
||||
formatter.timeZone = TimeZone(secondsFromGMT: 0)
|
||||
formatter.timeZone = NSTimeZone(forSecondsFromGMT: 0)
|
||||
|
||||
return formatter
|
||||
}()
|
||||
|
||||
extension Date {
|
||||
static func dateFromPassportDateCode(_ code: String) -> Date? {
|
||||
extension NSDate {
|
||||
static func dateFromPassportDateCode(_ code: String) -> NSDate? {
|
||||
|
||||
let year = code.substring(from: 0, to: 3)
|
||||
let month = code.substring(from: 4, to: 5)
|
||||
let day = code.substring(from: 6, to: 7)
|
||||
let year = code.substring(0, to: 3)
|
||||
let month = code.substring(4, to: 5)
|
||||
let day = code.substring(6, to: 7)
|
||||
|
||||
return simpleFormatter.date(from: "\(year)-\(month)-\(day)")
|
||||
return simpleFormatter.dateFromString("\(year)-\(month)-\(day)")
|
||||
}
|
||||
|
||||
var stringDate: String {
|
||||
return simpleFormatter.string(from: self)
|
||||
return simpleFormatter.stringFromDate(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ import Foundation
|
||||
|
||||
extension NSTextCheckingResult {
|
||||
func group(atIndex index: Int, fromSource source: String) -> String {
|
||||
let range = self.rangeAt(index)
|
||||
return (source as NSString).substring(with: range).trimmingCharacters(in: CharacterSet(charactersIn: "<"))
|
||||
let range = self.rangeAtIndex(index)
|
||||
return (source as NSString).substringWithRange(range).stringByTrimmingCharactersInSet(NSCharacterSet(charactersInString: "<"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,32 +3,51 @@ import Foundation
|
||||
extension String {
|
||||
|
||||
subscript (i: Int) -> Character {
|
||||
let index = self.index(self.startIndex, offsetBy: i)
|
||||
let index = self.startIndex.advancedBy(i)
|
||||
return self.characters[index]
|
||||
}
|
||||
|
||||
func substring(from: Int, to: Int) -> String {
|
||||
let fromIndex = self.index(self.startIndex, offsetBy: from)
|
||||
let toIndex = self.index(self.startIndex, offsetBy: to + 1)
|
||||
return self.substring(with: fromIndex ..< toIndex)
|
||||
let fromIndex = self.startIndex.advancedBy(from)
|
||||
let toIndex = self.startIndex.advancedBy(to + 1)
|
||||
return self.substringWithRange(fromIndex ..< toIndex)
|
||||
}
|
||||
|
||||
func replaceNumbers() -> String {
|
||||
var result = ""
|
||||
for char in self.characters {
|
||||
switch char {
|
||||
case "0": result.append("O")
|
||||
case "1": result.append("I")
|
||||
case "2": result.append("S")
|
||||
case "3": result.append("S")
|
||||
case "4": result.append("A")
|
||||
case "5": result.append("S")
|
||||
case "6": result.append("G")
|
||||
case "7": result.append("Z")
|
||||
case "0":
|
||||
result.appendContentsOf("O")
|
||||
break;
|
||||
case "1":
|
||||
result.appendContentsOf("I")
|
||||
break;
|
||||
case "2":
|
||||
result.appendContentsOf("S")
|
||||
break;
|
||||
case "3":
|
||||
result.appendContentsOf("S")
|
||||
break;
|
||||
case "4":
|
||||
result.appendContentsOf("A")
|
||||
break;
|
||||
case "5":
|
||||
result.appendContentsOf("S")
|
||||
break;
|
||||
case "6":
|
||||
result.appendContentsOf("G")
|
||||
break;
|
||||
case "7":
|
||||
result.appendContentsOf("Z")
|
||||
break;
|
||||
case "8",
|
||||
"9": result.append("B")
|
||||
"9":
|
||||
result.appendContentsOf("B")
|
||||
break;
|
||||
|
||||
default: result.append(char)
|
||||
default:
|
||||
result.append(char)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,16 +59,34 @@ extension String {
|
||||
for char in self.characters {
|
||||
switch char {
|
||||
case "O",
|
||||
"D": result.append("0")
|
||||
"D":
|
||||
result.appendContentsOf("0")
|
||||
break;
|
||||
|
||||
case "I",
|
||||
"L": result.append("1")
|
||||
"L":
|
||||
result.appendContentsOf("1")
|
||||
break;
|
||||
|
||||
case "S": result.append("5")
|
||||
case "A": result.append("4")
|
||||
case "G": result.append("6")
|
||||
case "Z": result.append("7")
|
||||
case "B": result.append("8")
|
||||
case "S":
|
||||
result.appendContentsOf("5")
|
||||
break;
|
||||
|
||||
case "A":
|
||||
result.appendContentsOf("4")
|
||||
break;
|
||||
|
||||
case "G":
|
||||
result.appendContentsOf("6")
|
||||
break;
|
||||
|
||||
case "Z":
|
||||
result.appendContentsOf("7")
|
||||
break;
|
||||
|
||||
case "B":
|
||||
result.appendContentsOf("8")
|
||||
break;
|
||||
|
||||
default: result.append(char)
|
||||
}
|
||||
|
||||
@@ -12,20 +12,18 @@ import GPUImage
|
||||
|
||||
extension UIImage {
|
||||
func croppedImageWithSize(_ rect: CGRect) -> UIImage {
|
||||
let imageRef = CGImageCreateWithImageInRect(self.CGImage!, rect)
|
||||
|
||||
let imageRef: CGImage! = self.cgImage!.cropping(to: rect)
|
||||
|
||||
let croppedImage: UIImage = UIImage(cgImage: imageRef, scale: self.scale, orientation: self.imageOrientation)
|
||||
let croppedImage: UIImage = UIImage(CGImage: imageRef!, scale: self.scale, orientation: self.imageOrientation)
|
||||
|
||||
let selectedFilter = GPUImageTransformFilter()
|
||||
selectedFilter.setInputRotation(kGPUImageNoRotation, at: 0)
|
||||
let image: UIImage = selectedFilter.image(byFilteringImage: croppedImage)
|
||||
selectedFilter.setInputRotation(kGPUImageNoRotation, atIndex: 0)
|
||||
let image = selectedFilter.imageByFilteringImage(croppedImage)
|
||||
|
||||
return image
|
||||
}
|
||||
|
||||
func save(_ path: String) {
|
||||
let png = UIImagePNGRepresentation(self)
|
||||
try? png?.write(to: URL(fileURLWithPath: path), options: [.atomic])
|
||||
var recognitionImage: UIImage {
|
||||
return UIImage(CGImage: self.CGImage!, scale: self.scale, orientation: UIImageOrientation.Left)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ public struct DOConstants {
|
||||
var result = ""
|
||||
|
||||
for i: UInt32 in (0 ..< 26) {
|
||||
result.append(Character(UnicodeScalar(aCode + i)!))
|
||||
result.append(Character(UnicodeScalar(aCode + i)))
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
@@ -10,90 +10,91 @@ import Foundation
|
||||
import TesseractOCR
|
||||
import PodAsset
|
||||
|
||||
open class Utils {
|
||||
public class Utils {
|
||||
|
||||
fileprivate static let bundle = PodAsset.bundle(forPod: "DocumentsOCR")!
|
||||
private static let bundle = PodAsset.bundleForPod("DocumentsOCR")!
|
||||
static let passportPattern: String! = Utils.stringFromTxtFile("passportPattern", inBundle: bundle)
|
||||
fileprivate static let tesseract = createTesseract()
|
||||
private static let tesseract = Utils.createTesseract()
|
||||
|
||||
open static func stringFromTxtFile(_ fileName: String, inBundle bundle: Bundle = Bundle.main) -> String? {
|
||||
let filePath = bundle.path(forResource: fileName, ofType: "txt")
|
||||
let contentData = FileManager.default.contents(atPath: filePath!)
|
||||
return NSString(data: contentData!, encoding: String.Encoding.utf8.rawValue) as? String
|
||||
public static func stringFromTxtFile(_ fileName: String, inBundle bundle: NSBundle = NSBundle.mainBundle()) -> String? {
|
||||
let filePath = bundle.pathForResource(fileName, ofType: "txt")
|
||||
let contentData = NSFileManager.defaultManager().contentsAtPath(filePath!)
|
||||
return String(data: contentData!, encoding: NSUTF8StringEncoding)
|
||||
}
|
||||
|
||||
static func mrCodeFrom(image: UIImage, tesseractDelegate: G8TesseractDelegate? = nil) -> String? {
|
||||
let path = bundle.path(forResource: "eng", ofType: "traineddata")
|
||||
|
||||
tesseract.delegate = tesseractDelegate!
|
||||
tesseract.image = image
|
||||
tesseract.image = image.recognitionImage
|
||||
|
||||
tesseract.recognize()
|
||||
|
||||
if let recognizedText = tesseract.recognizedText {
|
||||
NSLog("Recognized: \(recognizedText)")
|
||||
|
||||
let text = recognizedText.replacingOccurrences(of: " ", with: "")
|
||||
let text = recognizedText.stringByReplacingOccurrencesOfString(" ", withString: "")
|
||||
let regex = try? NSRegularExpression(pattern: passportPattern, options: [])
|
||||
let range = NSRange(location: 0, length: text.characters.count)
|
||||
if let result = regex!.firstMatch(in: text, options: [], range: range) {
|
||||
if let result = regex!.firstMatchInString(text, options: [], range: range) {
|
||||
|
||||
let code = (text as NSString).substring(with: result.range)
|
||||
let code = (text as NSString).substringWithRange(result.range)
|
||||
|
||||
return fixFirstRowIn(code: code)
|
||||
return fixFirstRowIn(code)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
fileprivate static func fixFirstRowIn(code: String) -> String {
|
||||
private static func fixFirstRowIn(code: String) -> String {
|
||||
|
||||
let pattern = "(?<FirstLine>(?<Passport>[A-Z0-9])(?<PassportType>.)(?<IssuingCountry>[A-Z0-9]{3})(?<PassportOwner>(?<Surname>[A-Z0-9]+)<<(?<GivenName>(?:[A-Z0-9]+<)+)){1})"
|
||||
let regex = try? NSRegularExpression(pattern: pattern, options: [])
|
||||
|
||||
let range = NSRange(location: 0, length: code.characters.count)
|
||||
let result = regex!.matches(in: code, options: [], range: range)
|
||||
var resultFirstRow = (code as NSString).substring(with: result[0].range)
|
||||
let result = regex!.matchesInString(code, options: [], range: range)
|
||||
var resultFirstRow = (code as NSString).substringWithRange(result[0].range)
|
||||
while resultFirstRow.characters.count != 44 {
|
||||
resultFirstRow.append("<")
|
||||
resultFirstRow.appendContentsOf("<")
|
||||
}
|
||||
|
||||
let secondRow = code.characters.split(separator: "\n", maxSplits: 2, omittingEmptySubsequences: true)[1]
|
||||
let secondRow = code.characters.split("\n", maxSplit: 2, allowEmptySlices: true)[1]
|
||||
|
||||
return "\(resultFirstRow)\n\(String(secondRow))\n"
|
||||
}
|
||||
|
||||
fileprivate static func createTesseract() -> G8Tesseract {
|
||||
let trainDataPath = bundle.path(forResource: "eng", ofType: "traineddata")
|
||||
private static func createTesseract() -> G8Tesseract {
|
||||
let trainDataPath = bundle.pathForResource("eng", ofType: "traineddata")
|
||||
|
||||
let cacheURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
|
||||
let cacheURL = NSFileManager.defaultManager().URLsForDirectory(.CachesDirectory, inDomains: .UserDomainMask).first!
|
||||
|
||||
let tessdataURL = cacheURL.appendingPathComponent("tesseract", isDirectory: true).appendingPathComponent("tessdata", isDirectory: true)
|
||||
let destinationURL = tessdataURL.appendingPathComponent("eng.traineddata")
|
||||
let tessdataURL = cacheURL.URLByAppendingPathComponent("tessetact", isDirectory: true)?.URLByAppendingPathComponent("tessdata", isDirectory: true)
|
||||
|
||||
if !FileManager.default.fileExists(atPath: destinationURL.path) {
|
||||
createTessdataFrom(trainDataPath!, toDirectoryURL: tessdataURL, withDestinationURL: destinationURL)
|
||||
let destinationURL = tessdataURL!.URLByAppendingPathComponent("eng.traineddata")
|
||||
|
||||
if !NSFileManager.defaultManager().fileExistsAtPath(destinationURL!.path!) {
|
||||
createTessdataFrom(trainDataPath!, toDirectoryURL: tessdataURL!, withDestinationURL: destinationURL!)
|
||||
}
|
||||
|
||||
let tesseract = G8Tesseract(language: "eng", configDictionary: [:], configFileNames: [], absoluteDataPath: tessdataURL.path, engineMode: .tesseractOnly, copyFilesFromResources: false)
|
||||
let tesseract = G8Tesseract(language: "eng", configDictionary: [:], configFileNames: [], absoluteDataPath: tessdataURL!.path, engineMode: .TesseractOnly)
|
||||
|
||||
var whiteList = DOConstants.alphabet.uppercased()
|
||||
whiteList.append("<>1234567890")
|
||||
var whiteList = DOConstants.alphabet.uppercaseString
|
||||
whiteList.appendContentsOf("<>1234567890")
|
||||
tesseract?.charWhitelist = whiteList
|
||||
|
||||
tesseract?.setVariableValue("FALSE", forKey: "x_ht_quality_check")
|
||||
|
||||
return tesseract!
|
||||
}
|
||||
|
||||
|
||||
fileprivate static func createTessdataFrom(_ filePath: String, toDirectoryURL tessdataURL: URL, withDestinationURL destinationURL: URL) {
|
||||
private static func createTessdataFrom(_ filePath: String, toDirectoryURL tessdataURL: NSURL, withDestinationURL destinationURL: NSURL) {
|
||||
do {
|
||||
let fileManager = FileManager.default
|
||||
try fileManager.createDirectory(atPath: tessdataURL.path,
|
||||
let fileManager = NSFileManager.defaultManager()
|
||||
try fileManager.createDirectoryAtPath(tessdataURL.path!,
|
||||
withIntermediateDirectories: true, attributes: nil)
|
||||
|
||||
try fileManager.copyItem(atPath: filePath, toPath: destinationURL.path)
|
||||
try fileManager.copyItemAtPath(filePath, toPath: destinationURL.path!)
|
||||
}
|
||||
catch let error as NSError {
|
||||
assertionFailure("There is no tessdata directory in cache (TesseractOCR traineddata). \(error.localizedDescription)")
|
||||
|
||||
@@ -35,13 +35,13 @@ public struct DocumentInfo {
|
||||
public let nationalityCode: String
|
||||
|
||||
/// Date of birth
|
||||
public let dateOfBirth: Date?
|
||||
public let dateOfBirth: NSDate?
|
||||
|
||||
/// Gender
|
||||
public let gender: Gender
|
||||
|
||||
/// Expiration date of passport
|
||||
public let expirationDate: Date?
|
||||
public let expirationDate: NSDate?
|
||||
|
||||
/// Personal number (may be used by the issuing country as it desires)
|
||||
public let personalNumber: String
|
||||
@@ -56,18 +56,19 @@ public struct DocumentInfo {
|
||||
let regex = try! NSRegularExpression(pattern: Utils.passportPattern, options: [])
|
||||
|
||||
let range = NSRange(location: 0, length: text.characters.count)
|
||||
if let result = regex.firstMatch(in: text, options: [], range: range) {
|
||||
|
||||
if let result = regex.firstMatchInString(text, options: [], range: range) {
|
||||
|
||||
mrCode = (text as NSString).substring(with: result.range)
|
||||
mrCode = (text as NSString).substringWithRange(result.range)
|
||||
|
||||
issuingCountryCode = result.group(atIndex: 4, fromSource: text).replaceNumbers()
|
||||
lastname = result.group(atIndex: 6, fromSource: text).replaceNumbers()
|
||||
name = result.group(atIndex: 7, fromSource: text).replacingOccurrences(of: "<", with: " ").replaceNumbers()
|
||||
name = result.group(atIndex: 7, fromSource: text).stringByReplacingOccurrencesOfString("<", withString: " ").replaceNumbers()
|
||||
passportNumber = result.group(atIndex: 9, fromSource: text)
|
||||
nationalityCode = result.group(atIndex: 11, fromSource: text).replaceNumbers()
|
||||
|
||||
let dayOfBirthCode = result.group(atIndex: 12, fromSource: text).replaceLetters()
|
||||
dateOfBirth = Date.dateFromPassportDateCode("19" + dayOfBirthCode)
|
||||
dateOfBirth = NSDate.dateFromPassportDateCode("19" + dayOfBirthCode)
|
||||
|
||||
let genderLetter = result.group(atIndex: 17, fromSource: text)
|
||||
switch genderLetter {
|
||||
@@ -80,7 +81,7 @@ public struct DocumentInfo {
|
||||
}
|
||||
|
||||
let expiralDateCode = result.group(atIndex: 18, fromSource: text).replaceLetters()
|
||||
expirationDate = Date.dateFromPassportDateCode("20" + expiralDateCode)
|
||||
expirationDate = NSDate.dateFromPassportDateCode("20" + expiralDateCode)
|
||||
|
||||
personalNumber = result.group(atIndex: 23, fromSource: text)
|
||||
|
||||
@@ -98,7 +99,7 @@ public struct DocumentInfo {
|
||||
}
|
||||
|
||||
init?(image: UIImage, tesseractDelegate: G8TesseractDelegate? = nil) {
|
||||
if let mrCode = Utils.mrCodeFrom(image: image, tesseractDelegate: tesseractDelegate) {
|
||||
if let mrCode = Utils.mrCodeFrom(image, tesseractDelegate: tesseractDelegate) {
|
||||
NSLog("Recognized: \(mrCode)")
|
||||
self.init(recognizedText: mrCode)
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ public protocol DocumentScannerDelegate: G8TesseractDelegate {
|
||||
/// - parameter scanner: The document scanner object informing the delegate of this event
|
||||
/// - parameter progress: progress value from 0.0 to 1.0
|
||||
func documentScanner(_ scanner: DocumentScanner, recognitionProgress progress: Double)
|
||||
|
||||
|
||||
/// Tells the delegate that scanner finished to recognize machine readable code from camera image and translate it into DocumentInfo struct
|
||||
///
|
||||
/// - parameter scanner: The document scanner object informing the delegate of this event
|
||||
@@ -38,33 +38,33 @@ public protocol DocumentScannerDelegate: G8TesseractDelegate {
|
||||
func documentScanner(_ scanner: DocumentScanner, didFailWithError error: NSError)
|
||||
}
|
||||
|
||||
open class DocumentScanner: NSObject {
|
||||
public class DocumentScanner: NSObject {
|
||||
|
||||
var imagePicker = UIImagePickerController()
|
||||
|
||||
/// View controller, which will present camera image picker for document machine readable code
|
||||
open var containerViewController: UIViewController!
|
||||
public var containerViewController: UIViewController!
|
||||
|
||||
/// The object that acts as the delegate of the document scanner
|
||||
open var delegate: DocumentScannerDelegate!
|
||||
public var delegate: DocumentScannerDelegate!
|
||||
|
||||
/// Number of photos to recognize
|
||||
open var photosCount: UInt8 = 5
|
||||
public var photosCount: UInt8 = 5
|
||||
|
||||
/// Time interval between taking photos
|
||||
open var takePhotoInterval = 0.2
|
||||
public var takePhotoInterval = 0.2
|
||||
|
||||
/// Recognized document information from RecognitionOperation
|
||||
open var recognizedDocumentInfo: DocumentInfo? = nil
|
||||
|
||||
var timer: Timer!
|
||||
public var recognizedDocumentInfo: DocumentInfo? = nil
|
||||
|
||||
var timer: NSTimer!
|
||||
var codes = [String]()
|
||||
var images = [UIImage]()
|
||||
var recognizedInfo: DocumentInfo? = nil
|
||||
|
||||
fileprivate let queue: OperationQueue = {
|
||||
let queue = OperationQueue()
|
||||
queue.qualityOfService = .userInitiated
|
||||
let queue: NSOperationQueue = {
|
||||
let queue = NSOperationQueue()
|
||||
queue.qualityOfService = .UserInitiated
|
||||
queue.name = "ScannerOperationQueue"
|
||||
return queue
|
||||
}()
|
||||
@@ -85,12 +85,13 @@ open class DocumentScanner: NSObject {
|
||||
|
||||
/// Present view controller with camera and border for document machine readable code
|
||||
|
||||
open func presentCameraViewController() {
|
||||
if UIImagePickerController.availableCaptureModes(for: .rear) != nil {
|
||||
public func presentCameraViewController() {
|
||||
|
||||
if UIImagePickerController.availableCaptureModesForCameraDevice(.Rear) != nil {
|
||||
imagePicker = UIImagePickerController()
|
||||
imagePicker.allowsEditing = false
|
||||
imagePicker.sourceType = .camera
|
||||
imagePicker.cameraCaptureMode = .photo
|
||||
imagePicker.sourceType = .Camera
|
||||
imagePicker.cameraCaptureMode = .Photo
|
||||
imagePicker.showsCameraControls = false
|
||||
|
||||
imagePicker.delegate = self
|
||||
@@ -101,11 +102,11 @@ open class DocumentScanner: NSObject {
|
||||
let frame = CGRect(x: 0, y: 0, width: width, height: height)
|
||||
overlayView.frame = frame
|
||||
|
||||
imagePicker.modalPresentationStyle = .fullScreen
|
||||
containerViewController.present(imagePicker, animated: true, completion: {
|
||||
imagePicker.modalPresentationStyle = .FullScreen
|
||||
containerViewController.presentViewController(imagePicker, animated: true, completion: {
|
||||
|
||||
overlayView.codeBorder.layer.borderWidth = 5.0
|
||||
overlayView.codeBorder.layer.borderColor = UIColor.red.cgColor
|
||||
//overlayView.codeBorder.layer.borderWidth = 5.0
|
||||
//overlayView.codeBorder.layer.borderColor = UIColor.redColor().CGColor
|
||||
|
||||
overlayView.delegate = self
|
||||
overlayView.resetViews()
|
||||
@@ -121,12 +122,12 @@ open class DocumentScanner: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
open func cancelRecognizeOperation() {
|
||||
public func cancelRecognizeOperation() {
|
||||
queue.cancelAllOperations()
|
||||
}
|
||||
|
||||
fileprivate var cameraOverlayView: CameraOverlayView = {
|
||||
let bundle = PodAsset.bundle(forPod: "DocumentsOCR")
|
||||
private var cameraOverlayView: CameraOverlayView = {
|
||||
let bundle = PodAsset.bundleForPod("DocumentsOCR")
|
||||
let cameraVC = CameraOverlayViewController(nibName: NibNames.cameraOverlayViewController, bundle: bundle!)
|
||||
let overlayView = cameraVC.view as! CameraOverlayView
|
||||
|
||||
@@ -137,25 +138,27 @@ open class DocumentScanner: NSObject {
|
||||
extension DocumentScanner: CameraViewDelegate {
|
||||
|
||||
func stopTakingPictures() {
|
||||
timer.invalidate()
|
||||
containerViewController.dismiss(animated: true, completion: nil)
|
||||
if timer != nil {
|
||||
timer.invalidate()
|
||||
}
|
||||
containerViewController.dismissViewControllerAnimated(true, completion: nil)
|
||||
}
|
||||
|
||||
func startTakingPictures() {
|
||||
codes = [String]()
|
||||
images = [UIImage]()
|
||||
|
||||
timer = Timer.scheduledTimer(timeInterval: takePhotoInterval, target: self, selector: #selector(self.timerTick(sender:)), userInfo: nil, repeats: true)
|
||||
timer = NSTimer.scheduledTimerWithTimeInterval(takePhotoInterval, target: self, selector: #selector(DocumentScanner.timerTick(_:)), userInfo: nil, repeats: true)
|
||||
}
|
||||
|
||||
func timerTick(sender: Timer) {
|
||||
func timerTick(sender: NSTimer) {
|
||||
imagePicker.takePicture()
|
||||
}
|
||||
}
|
||||
|
||||
extension DocumentScanner: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
|
||||
|
||||
public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
|
||||
|
||||
public func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
|
||||
|
||||
let editedImage = info[UIImagePickerControllerEditedImage] as? UIImage
|
||||
let originalImage = info[UIImagePickerControllerOriginalImage] as! UIImage
|
||||
@@ -179,7 +182,7 @@ extension DocumentScanner: UIImagePickerControllerDelegate, UINavigationControll
|
||||
let recognizeOperation = RecognizeOperation(scanner: self)
|
||||
|
||||
recognizeOperation.completionBlock = {
|
||||
DispatchQueue.main.async {
|
||||
dispatch_async(dispatch_get_main_queue()) {
|
||||
if let info = self.recognizedInfo {
|
||||
self.delegate.documentScanner(self, didFinishScanningWithInfo: info)
|
||||
}
|
||||
@@ -195,8 +198,8 @@ extension DocumentScanner: UIImagePickerControllerDelegate, UINavigationControll
|
||||
queue.addOperation(recognizeOperation)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func cropImage(_ image: UIImage) -> UIImage {
|
||||
|
||||
func cropImage(image: UIImage) -> UIImage {
|
||||
|
||||
let viewControllerSize = containerViewController.view.frame.size
|
||||
let vcWidth = viewControllerSize.width
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class RecognizeOperation: Operation {
|
||||
class RecognizeOperation: NSOperation {
|
||||
let scanner: DocumentScanner
|
||||
var codes = [String]()
|
||||
|
||||
@@ -22,16 +22,16 @@ class RecognizeOperation: Operation {
|
||||
for index in 0 ..< images.count {
|
||||
let image = images[index]
|
||||
|
||||
if isCancelled {
|
||||
if cancelled {
|
||||
return
|
||||
}
|
||||
|
||||
if let code = Utils.mrCodeFrom(image: image, tesseractDelegate: scanner.delegate) {
|
||||
if let code = Utils.mrCodeFrom(image, tesseractDelegate: scanner.delegate) {
|
||||
codes.append(code)
|
||||
}
|
||||
let progress = Double(index + 1) / Double(images.count)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
dispatch_async(dispatch_get_main_queue()) {
|
||||
self.scanner.delegate.documentScanner(self.scanner, recognitionProgress: progress)
|
||||
}
|
||||
}
|
||||
@@ -44,15 +44,15 @@ class RecognizeOperation: Operation {
|
||||
|
||||
let count = codes.first!.characters.count
|
||||
for index in 0 ..< count {
|
||||
if isCancelled {
|
||||
if cancelled {
|
||||
return
|
||||
}
|
||||
|
||||
let winnerCharacter = chooseCharacterByVotesOn(index: index)
|
||||
let winnerCharacter = chooseCharacterByVotesOn(index)
|
||||
resultCode.append(winnerCharacter)
|
||||
}
|
||||
|
||||
if isCancelled {
|
||||
if cancelled {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ class RecognizeOperation: Operation {
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func chooseCharacterByVotesOn(index: Int) -> Character {
|
||||
private func chooseCharacterByVotesOn(index: Int) -> Character {
|
||||
let characters = codes.map({ $0[index] })
|
||||
|
||||
var voting = [Character : Int]()
|
||||
@@ -74,7 +74,7 @@ class RecognizeOperation: Operation {
|
||||
}
|
||||
}
|
||||
|
||||
let max = voting.values.max()!
|
||||
let max = voting.values.maxElement()!
|
||||
for (character, count) in voting {
|
||||
if count == max {
|
||||
return character
|
||||
|
||||
@@ -541,7 +541,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE = "";
|
||||
SWIFT_VERSION = 3.0;
|
||||
SWIFT_VERSION = 2.3;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -561,7 +561,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE = "";
|
||||
SWIFT_VERSION = 3.0;
|
||||
SWIFT_VERSION = 2.3;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -579,7 +579,7 @@
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 3.0;
|
||||
SWIFT_VERSION = 2.3;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -593,7 +593,7 @@
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 3.0;
|
||||
SWIFT_VERSION = 2.3;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
||||
@@ -1,23 +1,30 @@
|
||||
{
|
||||
"DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "F58CA8BF9748A2452CAED5EE1AD1443C7F6654F4+++35C8851",
|
||||
"DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "B3A2403691ADE80CF46872AA6E39C39881A146F4+++4805E2C",
|
||||
"DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : {
|
||||
|
||||
},
|
||||
"DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : {
|
||||
"F58CA8BF9748A2452CAED5EE1AD1443C7F6654F4+++35C8851" : 9223372036854775807,
|
||||
"F58CA8BF9748A2452CAED5EE1AD1443C7F6654F4+++39A176C" : 9223372036854775807,
|
||||
"F58CA8BF9748A2452CAED5EE1AD1443C7F6654F4+++665569A" : 9223372036854775807
|
||||
"F58CA8BF9748A2452CAED5EE1AD1443C7F6654F4+++665569A" : 9223372036854775807,
|
||||
"B3A2403691ADE80CF46872AA6E39C39881A146F4+++4805E2C" : 9223372036854775807,
|
||||
"F58CA8BF9748A2452CAED5EE1AD1443C7F6654F4+++35C8851" : 9223372036854775807
|
||||
},
|
||||
"DVTSourceControlWorkspaceBlueprintIdentifierKey" : "9E0F3260-D4E0-4DA4-8DD8-16B33507544C",
|
||||
"DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
|
||||
"F58CA8BF9748A2452CAED5EE1AD1443C7F6654F4+++35C8851" : "DocumentsOCR\/",
|
||||
"F58CA8BF9748A2452CAED5EE1AD1443C7F6654F4+++39A176C" : "",
|
||||
"F58CA8BF9748A2452CAED5EE1AD1443C7F6654F4+++665569A" : ""
|
||||
"F58CA8BF9748A2452CAED5EE1AD1443C7F6654F4+++665569A" : "",
|
||||
"B3A2403691ADE80CF46872AA6E39C39881A146F4+++4805E2C" : "documents-ocr-ios-master\/",
|
||||
"F58CA8BF9748A2452CAED5EE1AD1443C7F6654F4+++35C8851" : "DocumentsOCR\/"
|
||||
},
|
||||
"DVTSourceControlWorkspaceBlueprintNameKey" : "DocumentsOCR",
|
||||
"DVTSourceControlWorkspaceBlueprintVersion" : 204,
|
||||
"DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "Example\/DocumentsOCR.xcworkspace",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [
|
||||
{
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/appintheair\/documents-ocr-ios",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "B3A2403691ADE80CF46872AA6E39C39881A146F4+++4805E2C"
|
||||
},
|
||||
{
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/appintheair\/passport-ocr-ios",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
||||
|
||||
@@ -15,31 +15,29 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
var window: UIWindow?
|
||||
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
|
||||
|
||||
// Override point for customization after application launch.
|
||||
return true
|
||||
func applicationDidFinishLaunching(application: UIApplication) {
|
||||
|
||||
}
|
||||
|
||||
func applicationWillResignActive(_ application: UIApplication) {
|
||||
func applicationWillResignActive(application: UIApplication) {
|
||||
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
|
||||
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
|
||||
}
|
||||
|
||||
func applicationDidEnterBackground(_ application: UIApplication) {
|
||||
func applicationDidEnterBackground(application: UIApplication) {
|
||||
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
|
||||
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
|
||||
}
|
||||
|
||||
func applicationWillEnterForeground(_ application: UIApplication) {
|
||||
func applicationWillEnterForeground(application: UIApplication) {
|
||||
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
|
||||
}
|
||||
|
||||
func applicationDidBecomeActive(_ application: UIApplication) {
|
||||
func applicationDidBecomeActive(application: UIApplication) {
|
||||
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ application: UIApplication) {
|
||||
func applicationWillTerminate(application: UIApplication) {
|
||||
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
||||
}
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 566 KiB After Width: | Height: | Size: 566 KiB |
|
Before Width: | Height: | Size: 406 KiB After Width: | Height: | Size: 406 KiB |
|
Before Width: | Height: | Size: 572 KiB After Width: | Height: | Size: 572 KiB |
|
Before Width: | Height: | Size: 209 KiB After Width: | Height: | Size: 209 KiB |
@@ -8,25 +8,25 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
let simpleFormatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
let simpleFormatter: NSDateFormatter = {
|
||||
let formatter = NSDateFormatter()
|
||||
formatter.dateFormat = "yyyy-MM-dd"
|
||||
formatter.timeZone = TimeZone(secondsFromGMT: 0)
|
||||
formatter.timeZone = NSTimeZone(forSecondsFromGMT: 0)
|
||||
|
||||
return formatter
|
||||
}()
|
||||
|
||||
extension Date {
|
||||
static func dateFromPassportDateCode(_ code: String) -> Date? {
|
||||
extension NSDate {
|
||||
static func dateFromPassportDateCode(code: String) -> NSDate? {
|
||||
|
||||
let year = code.substring(from: 0, to: 3)
|
||||
let month = code.substring(from: 4, to: 5)
|
||||
let day = code.substring(from: 6, to: 7)
|
||||
let year = code.substring(0, to: 3)
|
||||
let month = code.substring(4, to: 5)
|
||||
let day = code.substring(6, to: 7)
|
||||
|
||||
return simpleFormatter.date(from: "\(year)-\(month)-\(day)")
|
||||
return simpleFormatter.dateFromString("\(year)-\(month)-\(day)")
|
||||
}
|
||||
|
||||
var stringDate: String {
|
||||
return simpleFormatter.string(from: self)
|
||||
return simpleFormatter.stringFromDate(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ enum TextFieldType {
|
||||
class PassportTextField: UITextField {
|
||||
var editType: TextFieldType {
|
||||
set {
|
||||
editTypeValue = String(describing: newValue)
|
||||
editTypeValue = String(newValue)
|
||||
}
|
||||
get {
|
||||
switch editTypeValue {
|
||||
|
||||
@@ -29,7 +29,7 @@ class PassportViewController: UITableViewController {
|
||||
|
||||
@IBOutlet weak var cameraImageView: UIImageView!
|
||||
|
||||
let countries = Utils.stringFromTxtFile("CountryCodes")!.components(separatedBy: "\n")
|
||||
let countries = Utils.stringFromTxtFile("CountryCodes")!.componentsSeparatedByString("\n")
|
||||
|
||||
func dismissKeyboard() {
|
||||
self.view.endEditing(true)
|
||||
@@ -38,7 +38,7 @@ class PassportViewController: UITableViewController {
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first! as NSString
|
||||
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first! as NSString
|
||||
NSLog("\(paths)")
|
||||
|
||||
scanner = DocumentScanner(containerVC: self, withDelegate: self)
|
||||
@@ -46,7 +46,7 @@ class PassportViewController: UITableViewController {
|
||||
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.dismissKeyboard))
|
||||
self.view.addGestureRecognizer(tapGesture)
|
||||
|
||||
self.cameraImageView.contentMode = .scaleAspectFit
|
||||
self.cameraImageView.contentMode = .ScaleAspectFit
|
||||
|
||||
// Set up country fields:
|
||||
|
||||
@@ -66,8 +66,8 @@ class PassportViewController: UITableViewController {
|
||||
// Set up date fields:
|
||||
|
||||
let datePicker = UIDatePicker()
|
||||
datePicker.addTarget(self, action: #selector(PassportViewController.datePickerValueChanged(_:)), for: .valueChanged)
|
||||
datePicker.datePickerMode = UIDatePickerMode.date
|
||||
datePicker.addTarget(self, action: #selector(PassportViewController.datePickerValueChanged(_:)), forControlEvents: .ValueChanged)
|
||||
datePicker.datePickerMode = UIDatePickerMode.Date
|
||||
|
||||
dobField.delegate = self
|
||||
expiredDateField.delegate = self
|
||||
@@ -76,31 +76,31 @@ class PassportViewController: UITableViewController {
|
||||
expiredDateField.inputView = datePicker
|
||||
}
|
||||
|
||||
func datePickerValueChanged(_ sender: UIDatePicker) {
|
||||
func datePickerValueChanged(sender: UIDatePicker) {
|
||||
selectedTextField.text = sender.date.stringDate
|
||||
}
|
||||
|
||||
@IBAction func cameraClicked(_ sender: UIBarButtonItem) {
|
||||
@IBAction func cameraClicked(sender: UIBarButtonItem) {
|
||||
scanner.presentCameraViewController()
|
||||
}
|
||||
|
||||
override var supportedInterfaceOrientations : UIInterfaceOrientationMask {
|
||||
return .portrait
|
||||
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
|
||||
return .Portrait
|
||||
}
|
||||
}
|
||||
|
||||
extension PassportViewController: DocumentScannerDelegate {
|
||||
|
||||
func documentScanner(_ scanner: DocumentScanner, willBeginScanningImages images: [UIImage]) {
|
||||
func documentScanner(scanner: DocumentScanner, willBeginScanningImages images: [UIImage]) {
|
||||
self.cameraImageView.image = images.first!
|
||||
SVProgressHUD.showProgress(0, status: "Scanning")
|
||||
}
|
||||
|
||||
func documentScanner(_ scanner: DocumentScanner, recognitionProgress progress: Double) {
|
||||
func documentScanner(scanner: DocumentScanner, recognitionProgress progress: Double) {
|
||||
SVProgressHUD.showProgress(Float(progress), status: "Scanning")
|
||||
}
|
||||
|
||||
func documentScanner(_ scanner: DocumentScanner, didFinishScanningWithInfo info: DocumentInfo) {
|
||||
func documentScanner(scanner: DocumentScanner, didFinishScanningWithInfo info: DocumentInfo) {
|
||||
NSLog("Info: \(info)")
|
||||
|
||||
countryField.text = info.issuingCountryCode
|
||||
@@ -128,7 +128,7 @@ extension PassportViewController: DocumentScannerDelegate {
|
||||
SVProgressHUD.dismiss()
|
||||
}
|
||||
|
||||
func documentScanner(_ scanner: DocumentScanner, didFailWithError error: NSError) {
|
||||
func documentScanner(scanner: DocumentScanner, didFailWithError error: NSError) {
|
||||
NSLog("Ошибка \(error.localizedDescription)")
|
||||
SVProgressHUD.dismiss()
|
||||
self.showErrorAlert(withMessage: error.localizedDescription)
|
||||
@@ -137,7 +137,7 @@ extension PassportViewController: DocumentScannerDelegate {
|
||||
|
||||
extension PassportViewController: UITextFieldDelegate {
|
||||
|
||||
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
|
||||
func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
|
||||
guard let passportField = textField as? PassportTextField else {
|
||||
NSLog("Ошибка: неверный тип текстового поля")
|
||||
return true
|
||||
@@ -151,20 +151,20 @@ extension PassportViewController: UITextFieldDelegate {
|
||||
|
||||
extension PassportViewController: UIPickerViewDelegate, UIPickerViewDataSource {
|
||||
|
||||
func numberOfComponents(in pickerView: UIPickerView) -> Int {
|
||||
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
|
||||
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
|
||||
return countries.count
|
||||
}
|
||||
|
||||
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
|
||||
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
|
||||
return countries[row]
|
||||
}
|
||||
|
||||
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
|
||||
selectedTextField?.text = countries[row].substring(from: 0, to: 2)
|
||||
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
|
||||
selectedTextField?.text = countries[row].substring(0, to: 2)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,13 @@ import Foundation
|
||||
extension String {
|
||||
|
||||
subscript (i: Int) -> Character {
|
||||
let index = self.index(self.startIndex, offsetBy: i)
|
||||
let index = self.startIndex.advancedBy(i)
|
||||
return self.characters[index]
|
||||
}
|
||||
|
||||
func substring(from: Int, to: Int) -> String {
|
||||
let fromIndex = self.index(self.startIndex, offsetBy: from)
|
||||
let toIndex = self.index(self.startIndex, offsetBy: to + 1)
|
||||
return self.substring(with: fromIndex ..< toIndex)
|
||||
let fromIndex = self.startIndex.advancedBy(from)
|
||||
let toIndex = self.startIndex.advancedBy(to + 1)
|
||||
return self.substringWithRange(fromIndex ..< toIndex)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,10 @@ import UIKit
|
||||
|
||||
extension UIViewController {
|
||||
func showErrorAlert(withMessage message: String) {
|
||||
let alertVC = UIAlertController(title: "Ошибка", message: message, preferredStyle: .alert)
|
||||
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
|
||||
let alertVC = UIAlertController(title: "Ошибка", message: message, preferredStyle: .Alert)
|
||||
let okAction = UIAlertAction(title: "OK", style: .Default, handler: nil)
|
||||
|
||||
alertVC.addAction(okAction)
|
||||
present(alertVC, animated: true, completion: nil)
|
||||
presentViewController(alertVC, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use_frameworks!
|
||||
|
||||
target 'DocumentsOCR_Example' do
|
||||
|
||||
|
||||
pod 'TesseractOCRiOS', :git => 'https://github.com/appintheair/Tesseract-OCR-iOS'
|
||||
pod 'DocumentsOCR', :path => '../'
|
||||
pod 'SVProgressHUD'
|
||||
|
||||
|
||||
@@ -1,28 +1,36 @@
|
||||
PODS:
|
||||
- DocumentsOCR (1.0.0):
|
||||
- DocumentsOCR (1.0.3):
|
||||
- GPUImage
|
||||
- PodAsset
|
||||
- TesseractOCRiOS (~> 4.0.0)
|
||||
- GPUImage (0.1.7)
|
||||
- PodAsset (0.12.0)
|
||||
- SVProgressHUD (2.0.3)
|
||||
- TesseractOCRiOS (4.0.0)
|
||||
- TesseractOCRiOS (4.0.1)
|
||||
|
||||
DEPENDENCIES:
|
||||
- DocumentsOCR (from `../`)
|
||||
- SVProgressHUD
|
||||
- TesseractOCRiOS (from `https://github.com/appintheair/Tesseract-OCR-iOS`)
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
DocumentsOCR:
|
||||
:path: ../
|
||||
TesseractOCRiOS:
|
||||
:git: https://github.com/appintheair/Tesseract-OCR-iOS
|
||||
|
||||
CHECKOUT OPTIONS:
|
||||
TesseractOCRiOS:
|
||||
:commit: a482f067e93e6670d6ea7753c36b6fac90d2c44c
|
||||
:git: https://github.com/appintheair/Tesseract-OCR-iOS
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
DocumentsOCR: 10e00faee73177a4b2899fe480ac67971b455d06
|
||||
DocumentsOCR: 6c84b18b230402fa12d7a5f9fea7eca564eca893
|
||||
GPUImage: 733a5f0fab92df9de1c37ba9df520a833ccb406d
|
||||
PodAsset: d6e724524e63725ef0c579354819ccc2dcbcf920
|
||||
SVProgressHUD: b0830714205bea1317ea1a2ebc71e5633af334d4
|
||||
TesseractOCRiOS: 90638afbe43d082433f2e76b384c7901d23956df
|
||||
TesseractOCRiOS: 30545db87623f3f643280db372d318fc7355463c
|
||||
|
||||
PODFILE CHECKSUM: cd9f61bbc718914f9f971ce0f0248a7f58e7038b
|
||||
PODFILE CHECKSUM: 18dff8c7466af2f5ef701624329ece0921fcf3b7
|
||||
|
||||
COCOAPODS: 1.1.0.rc.2
|
||||
|
||||
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 198 KiB After Width: | Height: | Size: 198 KiB |
|
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 126 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |