├── .gitignore ├── .swiftpm └── xcode │ └── package.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Examples └── SweetCardScannerExample │ ├── SweetCardScannerExample.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved │ ├── SweetCardScannerExample │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── ContentView.swift │ ├── Info.plist │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── ResultView.swift │ └── SweetCardScannerExampleApp.swift │ ├── SweetCardScannerExampleTests │ ├── Info.plist │ └── SweetCardScannerExampleTests.swift │ └── SweetCardScannerExampleUITests │ ├── Info.plist │ └── SweetCardScannerExampleUITests.swift ├── LICENSE ├── Package.resolved ├── Package.swift ├── README.md ├── Sources └── SweetCardScanner │ ├── CreditCardScanner │ ├── CameraView.swift │ ├── CreditCard.swift │ ├── CreditCardScannerError.swift │ ├── CreditCardScannerViewController.swift │ ├── CreditCardUtils.swift │ ├── ImageAnalyzer.swift │ ├── ImageRatio.swift │ └── String+Extensions.swift │ └── SweetCardScanner.swift ├── SweetCardScanner.xcodeproj ├── Reg_Info.plist ├── SweetCardScannerTests_Info.plist ├── SweetCardScanner_Info.plist ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── xcshareddata │ └── xcschemes │ ├── SweetCardScanner.xcscheme │ └── SweetCardScannerTests.xcscheme ├── Tests ├── LinuxMain.swift └── SweetCardScannerTests │ ├── SweetCardScannerTests.swift │ └── XCTestManifests.swift └── preview.gif /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | xcuserdata/ 5 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Examples/SweetCardScannerExample/SweetCardScannerExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 52; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | A4B5C62F255FB0F10099F695 /* SweetCardScannerExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4B5C62E255FB0F10099F695 /* SweetCardScannerExampleApp.swift */; }; 11 | A4B5C631255FB0F10099F695 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4B5C630255FB0F10099F695 /* ContentView.swift */; }; 12 | A4B5C633255FB0F30099F695 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A4B5C632255FB0F30099F695 /* Assets.xcassets */; }; 13 | A4B5C636255FB0F30099F695 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A4B5C635255FB0F30099F695 /* Preview Assets.xcassets */; }; 14 | A4B5C641255FB0F40099F695 /* SweetCardScannerExampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4B5C640255FB0F40099F695 /* SweetCardScannerExampleTests.swift */; }; 15 | A4B5C64C255FB0F40099F695 /* SweetCardScannerExampleUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4B5C64B255FB0F40099F695 /* SweetCardScannerExampleUITests.swift */; }; 16 | A4B5C668255FB2D10099F695 /* SweetCardScanner in Frameworks */ = {isa = PBXBuildFile; productRef = A4B5C667255FB2D10099F695 /* SweetCardScanner */; }; 17 | A4B5C669255FB2D10099F695 /* SweetCardScanner in Embed Frameworks */ = {isa = PBXBuildFile; productRef = A4B5C667255FB2D10099F695 /* SweetCardScanner */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 18 | A4B5C670255FB3030099F695 /* ResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4B5C66F255FB3030099F695 /* ResultView.swift */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXContainerItemProxy section */ 22 | A4B5C63D255FB0F40099F695 /* PBXContainerItemProxy */ = { 23 | isa = PBXContainerItemProxy; 24 | containerPortal = A4B5C623255FB0F10099F695 /* Project object */; 25 | proxyType = 1; 26 | remoteGlobalIDString = A4B5C62A255FB0F10099F695; 27 | remoteInfo = SweetCardScannerExample; 28 | }; 29 | A4B5C648255FB0F40099F695 /* PBXContainerItemProxy */ = { 30 | isa = PBXContainerItemProxy; 31 | containerPortal = A4B5C623255FB0F10099F695 /* Project object */; 32 | proxyType = 1; 33 | remoteGlobalIDString = A4B5C62A255FB0F10099F695; 34 | remoteInfo = SweetCardScannerExample; 35 | }; 36 | /* End PBXContainerItemProxy section */ 37 | 38 | /* Begin PBXCopyFilesBuildPhase section */ 39 | A4B5C66A255FB2D10099F695 /* Embed Frameworks */ = { 40 | isa = PBXCopyFilesBuildPhase; 41 | buildActionMask = 2147483647; 42 | dstPath = ""; 43 | dstSubfolderSpec = 10; 44 | files = ( 45 | A4B5C669255FB2D10099F695 /* SweetCardScanner in Embed Frameworks */, 46 | ); 47 | name = "Embed Frameworks"; 48 | runOnlyForDeploymentPostprocessing = 0; 49 | }; 50 | /* End PBXCopyFilesBuildPhase section */ 51 | 52 | /* Begin PBXFileReference section */ 53 | A4B5C62B255FB0F10099F695 /* SweetCardScannerExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SweetCardScannerExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | A4B5C62E255FB0F10099F695 /* SweetCardScannerExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SweetCardScannerExampleApp.swift; sourceTree = ""; }; 55 | A4B5C630255FB0F10099F695 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 56 | A4B5C632255FB0F30099F695 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 57 | A4B5C635255FB0F30099F695 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 58 | A4B5C637255FB0F30099F695 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 59 | A4B5C63C255FB0F40099F695 /* SweetCardScannerExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SweetCardScannerExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 60 | A4B5C640255FB0F40099F695 /* SweetCardScannerExampleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SweetCardScannerExampleTests.swift; sourceTree = ""; }; 61 | A4B5C642255FB0F40099F695 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 62 | A4B5C647255FB0F40099F695 /* SweetCardScannerExampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SweetCardScannerExampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 63 | A4B5C64B255FB0F40099F695 /* SweetCardScannerExampleUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SweetCardScannerExampleUITests.swift; sourceTree = ""; }; 64 | A4B5C64D255FB0F40099F695 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 65 | A4B5C662255FB2980099F695 /* SweetCardScanner */ = {isa = PBXFileReference; lastKnownFileType = folder; name = SweetCardScanner; path = ../..; sourceTree = ""; }; 66 | A4B5C66F255FB3030099F695 /* ResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultView.swift; sourceTree = ""; }; 67 | /* End PBXFileReference section */ 68 | 69 | /* Begin PBXFrameworksBuildPhase section */ 70 | A4B5C628255FB0F10099F695 /* Frameworks */ = { 71 | isa = PBXFrameworksBuildPhase; 72 | buildActionMask = 2147483647; 73 | files = ( 74 | A4B5C668255FB2D10099F695 /* SweetCardScanner in Frameworks */, 75 | ); 76 | runOnlyForDeploymentPostprocessing = 0; 77 | }; 78 | A4B5C639255FB0F40099F695 /* Frameworks */ = { 79 | isa = PBXFrameworksBuildPhase; 80 | buildActionMask = 2147483647; 81 | files = ( 82 | ); 83 | runOnlyForDeploymentPostprocessing = 0; 84 | }; 85 | A4B5C644255FB0F40099F695 /* Frameworks */ = { 86 | isa = PBXFrameworksBuildPhase; 87 | buildActionMask = 2147483647; 88 | files = ( 89 | ); 90 | runOnlyForDeploymentPostprocessing = 0; 91 | }; 92 | /* End PBXFrameworksBuildPhase section */ 93 | 94 | /* Begin PBXGroup section */ 95 | A4B5C622255FB0F10099F695 = { 96 | isa = PBXGroup; 97 | children = ( 98 | A4B5C62D255FB0F10099F695 /* SweetCardScannerExample */, 99 | A4B5C63F255FB0F40099F695 /* SweetCardScannerExampleTests */, 100 | A4B5C64A255FB0F40099F695 /* SweetCardScannerExampleUITests */, 101 | A4B5C62C255FB0F10099F695 /* Products */, 102 | A4B5C662255FB2980099F695 /* SweetCardScanner */, 103 | A4B5C666255FB2D10099F695 /* Frameworks */, 104 | ); 105 | sourceTree = ""; 106 | }; 107 | A4B5C62C255FB0F10099F695 /* Products */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | A4B5C62B255FB0F10099F695 /* SweetCardScannerExample.app */, 111 | A4B5C63C255FB0F40099F695 /* SweetCardScannerExampleTests.xctest */, 112 | A4B5C647255FB0F40099F695 /* SweetCardScannerExampleUITests.xctest */, 113 | ); 114 | name = Products; 115 | sourceTree = ""; 116 | }; 117 | A4B5C62D255FB0F10099F695 /* SweetCardScannerExample */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | A4B5C62E255FB0F10099F695 /* SweetCardScannerExampleApp.swift */, 121 | A4B5C630255FB0F10099F695 /* ContentView.swift */, 122 | A4B5C66F255FB3030099F695 /* ResultView.swift */, 123 | A4B5C632255FB0F30099F695 /* Assets.xcassets */, 124 | A4B5C637255FB0F30099F695 /* Info.plist */, 125 | A4B5C634255FB0F30099F695 /* Preview Content */, 126 | ); 127 | path = SweetCardScannerExample; 128 | sourceTree = ""; 129 | }; 130 | A4B5C634255FB0F30099F695 /* Preview Content */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | A4B5C635255FB0F30099F695 /* Preview Assets.xcassets */, 134 | ); 135 | path = "Preview Content"; 136 | sourceTree = ""; 137 | }; 138 | A4B5C63F255FB0F40099F695 /* SweetCardScannerExampleTests */ = { 139 | isa = PBXGroup; 140 | children = ( 141 | A4B5C640255FB0F40099F695 /* SweetCardScannerExampleTests.swift */, 142 | A4B5C642255FB0F40099F695 /* Info.plist */, 143 | ); 144 | path = SweetCardScannerExampleTests; 145 | sourceTree = ""; 146 | }; 147 | A4B5C64A255FB0F40099F695 /* SweetCardScannerExampleUITests */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | A4B5C64B255FB0F40099F695 /* SweetCardScannerExampleUITests.swift */, 151 | A4B5C64D255FB0F40099F695 /* Info.plist */, 152 | ); 153 | path = SweetCardScannerExampleUITests; 154 | sourceTree = ""; 155 | }; 156 | A4B5C666255FB2D10099F695 /* Frameworks */ = { 157 | isa = PBXGroup; 158 | children = ( 159 | ); 160 | name = Frameworks; 161 | sourceTree = ""; 162 | }; 163 | /* End PBXGroup section */ 164 | 165 | /* Begin PBXNativeTarget section */ 166 | A4B5C62A255FB0F10099F695 /* SweetCardScannerExample */ = { 167 | isa = PBXNativeTarget; 168 | buildConfigurationList = A4B5C650255FB0F40099F695 /* Build configuration list for PBXNativeTarget "SweetCardScannerExample" */; 169 | buildPhases = ( 170 | A4B5C627255FB0F10099F695 /* Sources */, 171 | A4B5C628255FB0F10099F695 /* Frameworks */, 172 | A4B5C629255FB0F10099F695 /* Resources */, 173 | A4B5C66A255FB2D10099F695 /* Embed Frameworks */, 174 | ); 175 | buildRules = ( 176 | ); 177 | dependencies = ( 178 | ); 179 | name = SweetCardScannerExample; 180 | packageProductDependencies = ( 181 | A4B5C667255FB2D10099F695 /* SweetCardScanner */, 182 | ); 183 | productName = SweetCardScannerExample; 184 | productReference = A4B5C62B255FB0F10099F695 /* SweetCardScannerExample.app */; 185 | productType = "com.apple.product-type.application"; 186 | }; 187 | A4B5C63B255FB0F40099F695 /* SweetCardScannerExampleTests */ = { 188 | isa = PBXNativeTarget; 189 | buildConfigurationList = A4B5C653255FB0F40099F695 /* Build configuration list for PBXNativeTarget "SweetCardScannerExampleTests" */; 190 | buildPhases = ( 191 | A4B5C638255FB0F40099F695 /* Sources */, 192 | A4B5C639255FB0F40099F695 /* Frameworks */, 193 | A4B5C63A255FB0F40099F695 /* Resources */, 194 | ); 195 | buildRules = ( 196 | ); 197 | dependencies = ( 198 | A4B5C63E255FB0F40099F695 /* PBXTargetDependency */, 199 | ); 200 | name = SweetCardScannerExampleTests; 201 | productName = SweetCardScannerExampleTests; 202 | productReference = A4B5C63C255FB0F40099F695 /* SweetCardScannerExampleTests.xctest */; 203 | productType = "com.apple.product-type.bundle.unit-test"; 204 | }; 205 | A4B5C646255FB0F40099F695 /* SweetCardScannerExampleUITests */ = { 206 | isa = PBXNativeTarget; 207 | buildConfigurationList = A4B5C656255FB0F40099F695 /* Build configuration list for PBXNativeTarget "SweetCardScannerExampleUITests" */; 208 | buildPhases = ( 209 | A4B5C643255FB0F40099F695 /* Sources */, 210 | A4B5C644255FB0F40099F695 /* Frameworks */, 211 | A4B5C645255FB0F40099F695 /* Resources */, 212 | ); 213 | buildRules = ( 214 | ); 215 | dependencies = ( 216 | A4B5C649255FB0F40099F695 /* PBXTargetDependency */, 217 | ); 218 | name = SweetCardScannerExampleUITests; 219 | productName = SweetCardScannerExampleUITests; 220 | productReference = A4B5C647255FB0F40099F695 /* SweetCardScannerExampleUITests.xctest */; 221 | productType = "com.apple.product-type.bundle.ui-testing"; 222 | }; 223 | /* End PBXNativeTarget section */ 224 | 225 | /* Begin PBXProject section */ 226 | A4B5C623255FB0F10099F695 /* Project object */ = { 227 | isa = PBXProject; 228 | attributes = { 229 | LastSwiftUpdateCheck = 1210; 230 | LastUpgradeCheck = 1210; 231 | TargetAttributes = { 232 | A4B5C62A255FB0F10099F695 = { 233 | CreatedOnToolsVersion = 12.1; 234 | }; 235 | A4B5C63B255FB0F40099F695 = { 236 | CreatedOnToolsVersion = 12.1; 237 | TestTargetID = A4B5C62A255FB0F10099F695; 238 | }; 239 | A4B5C646255FB0F40099F695 = { 240 | CreatedOnToolsVersion = 12.1; 241 | TestTargetID = A4B5C62A255FB0F10099F695; 242 | }; 243 | }; 244 | }; 245 | buildConfigurationList = A4B5C626255FB0F10099F695 /* Build configuration list for PBXProject "SweetCardScannerExample" */; 246 | compatibilityVersion = "Xcode 9.3"; 247 | developmentRegion = en; 248 | hasScannedForEncodings = 0; 249 | knownRegions = ( 250 | en, 251 | Base, 252 | ); 253 | mainGroup = A4B5C622255FB0F10099F695; 254 | productRefGroup = A4B5C62C255FB0F10099F695 /* Products */; 255 | projectDirPath = ""; 256 | projectRoot = ""; 257 | targets = ( 258 | A4B5C62A255FB0F10099F695 /* SweetCardScannerExample */, 259 | A4B5C63B255FB0F40099F695 /* SweetCardScannerExampleTests */, 260 | A4B5C646255FB0F40099F695 /* SweetCardScannerExampleUITests */, 261 | ); 262 | }; 263 | /* End PBXProject section */ 264 | 265 | /* Begin PBXResourcesBuildPhase section */ 266 | A4B5C629255FB0F10099F695 /* Resources */ = { 267 | isa = PBXResourcesBuildPhase; 268 | buildActionMask = 2147483647; 269 | files = ( 270 | A4B5C636255FB0F30099F695 /* Preview Assets.xcassets in Resources */, 271 | A4B5C633255FB0F30099F695 /* Assets.xcassets in Resources */, 272 | ); 273 | runOnlyForDeploymentPostprocessing = 0; 274 | }; 275 | A4B5C63A255FB0F40099F695 /* Resources */ = { 276 | isa = PBXResourcesBuildPhase; 277 | buildActionMask = 2147483647; 278 | files = ( 279 | ); 280 | runOnlyForDeploymentPostprocessing = 0; 281 | }; 282 | A4B5C645255FB0F40099F695 /* Resources */ = { 283 | isa = PBXResourcesBuildPhase; 284 | buildActionMask = 2147483647; 285 | files = ( 286 | ); 287 | runOnlyForDeploymentPostprocessing = 0; 288 | }; 289 | /* End PBXResourcesBuildPhase section */ 290 | 291 | /* Begin PBXSourcesBuildPhase section */ 292 | A4B5C627255FB0F10099F695 /* Sources */ = { 293 | isa = PBXSourcesBuildPhase; 294 | buildActionMask = 2147483647; 295 | files = ( 296 | A4B5C631255FB0F10099F695 /* ContentView.swift in Sources */, 297 | A4B5C62F255FB0F10099F695 /* SweetCardScannerExampleApp.swift in Sources */, 298 | A4B5C670255FB3030099F695 /* ResultView.swift in Sources */, 299 | ); 300 | runOnlyForDeploymentPostprocessing = 0; 301 | }; 302 | A4B5C638255FB0F40099F695 /* Sources */ = { 303 | isa = PBXSourcesBuildPhase; 304 | buildActionMask = 2147483647; 305 | files = ( 306 | A4B5C641255FB0F40099F695 /* SweetCardScannerExampleTests.swift in Sources */, 307 | ); 308 | runOnlyForDeploymentPostprocessing = 0; 309 | }; 310 | A4B5C643255FB0F40099F695 /* Sources */ = { 311 | isa = PBXSourcesBuildPhase; 312 | buildActionMask = 2147483647; 313 | files = ( 314 | A4B5C64C255FB0F40099F695 /* SweetCardScannerExampleUITests.swift in Sources */, 315 | ); 316 | runOnlyForDeploymentPostprocessing = 0; 317 | }; 318 | /* End PBXSourcesBuildPhase section */ 319 | 320 | /* Begin PBXTargetDependency section */ 321 | A4B5C63E255FB0F40099F695 /* PBXTargetDependency */ = { 322 | isa = PBXTargetDependency; 323 | target = A4B5C62A255FB0F10099F695 /* SweetCardScannerExample */; 324 | targetProxy = A4B5C63D255FB0F40099F695 /* PBXContainerItemProxy */; 325 | }; 326 | A4B5C649255FB0F40099F695 /* PBXTargetDependency */ = { 327 | isa = PBXTargetDependency; 328 | target = A4B5C62A255FB0F10099F695 /* SweetCardScannerExample */; 329 | targetProxy = A4B5C648255FB0F40099F695 /* PBXContainerItemProxy */; 330 | }; 331 | /* End PBXTargetDependency section */ 332 | 333 | /* Begin XCBuildConfiguration section */ 334 | A4B5C64E255FB0F40099F695 /* Debug */ = { 335 | isa = XCBuildConfiguration; 336 | buildSettings = { 337 | ALWAYS_SEARCH_USER_PATHS = NO; 338 | CLANG_ANALYZER_NONNULL = YES; 339 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 340 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 341 | CLANG_CXX_LIBRARY = "libc++"; 342 | CLANG_ENABLE_MODULES = YES; 343 | CLANG_ENABLE_OBJC_ARC = YES; 344 | CLANG_ENABLE_OBJC_WEAK = YES; 345 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 346 | CLANG_WARN_BOOL_CONVERSION = YES; 347 | CLANG_WARN_COMMA = YES; 348 | CLANG_WARN_CONSTANT_CONVERSION = YES; 349 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 350 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 351 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 352 | CLANG_WARN_EMPTY_BODY = YES; 353 | CLANG_WARN_ENUM_CONVERSION = YES; 354 | CLANG_WARN_INFINITE_RECURSION = YES; 355 | CLANG_WARN_INT_CONVERSION = YES; 356 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 357 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 358 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 359 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 360 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 361 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 362 | CLANG_WARN_STRICT_PROTOTYPES = YES; 363 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 364 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 365 | CLANG_WARN_UNREACHABLE_CODE = YES; 366 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 367 | COPY_PHASE_STRIP = NO; 368 | DEBUG_INFORMATION_FORMAT = dwarf; 369 | ENABLE_STRICT_OBJC_MSGSEND = YES; 370 | ENABLE_TESTABILITY = YES; 371 | GCC_C_LANGUAGE_STANDARD = gnu11; 372 | GCC_DYNAMIC_NO_PIC = NO; 373 | GCC_NO_COMMON_BLOCKS = YES; 374 | GCC_OPTIMIZATION_LEVEL = 0; 375 | GCC_PREPROCESSOR_DEFINITIONS = ( 376 | "DEBUG=1", 377 | "$(inherited)", 378 | ); 379 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 380 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 381 | GCC_WARN_UNDECLARED_SELECTOR = YES; 382 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 383 | GCC_WARN_UNUSED_FUNCTION = YES; 384 | GCC_WARN_UNUSED_VARIABLE = YES; 385 | IPHONEOS_DEPLOYMENT_TARGET = 14.1; 386 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 387 | MTL_FAST_MATH = YES; 388 | ONLY_ACTIVE_ARCH = YES; 389 | SDKROOT = iphoneos; 390 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 391 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 392 | }; 393 | name = Debug; 394 | }; 395 | A4B5C64F255FB0F40099F695 /* Release */ = { 396 | isa = XCBuildConfiguration; 397 | buildSettings = { 398 | ALWAYS_SEARCH_USER_PATHS = NO; 399 | CLANG_ANALYZER_NONNULL = YES; 400 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 401 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 402 | CLANG_CXX_LIBRARY = "libc++"; 403 | CLANG_ENABLE_MODULES = YES; 404 | CLANG_ENABLE_OBJC_ARC = YES; 405 | CLANG_ENABLE_OBJC_WEAK = YES; 406 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 407 | CLANG_WARN_BOOL_CONVERSION = YES; 408 | CLANG_WARN_COMMA = YES; 409 | CLANG_WARN_CONSTANT_CONVERSION = YES; 410 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 411 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 412 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 413 | CLANG_WARN_EMPTY_BODY = YES; 414 | CLANG_WARN_ENUM_CONVERSION = YES; 415 | CLANG_WARN_INFINITE_RECURSION = YES; 416 | CLANG_WARN_INT_CONVERSION = YES; 417 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 418 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 419 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 420 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 421 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 422 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 423 | CLANG_WARN_STRICT_PROTOTYPES = YES; 424 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 425 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 426 | CLANG_WARN_UNREACHABLE_CODE = YES; 427 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 428 | COPY_PHASE_STRIP = NO; 429 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 430 | ENABLE_NS_ASSERTIONS = NO; 431 | ENABLE_STRICT_OBJC_MSGSEND = YES; 432 | GCC_C_LANGUAGE_STANDARD = gnu11; 433 | GCC_NO_COMMON_BLOCKS = YES; 434 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 435 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 436 | GCC_WARN_UNDECLARED_SELECTOR = YES; 437 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 438 | GCC_WARN_UNUSED_FUNCTION = YES; 439 | GCC_WARN_UNUSED_VARIABLE = YES; 440 | IPHONEOS_DEPLOYMENT_TARGET = 14.1; 441 | MTL_ENABLE_DEBUG_INFO = NO; 442 | MTL_FAST_MATH = YES; 443 | SDKROOT = iphoneos; 444 | SWIFT_COMPILATION_MODE = wholemodule; 445 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 446 | VALIDATE_PRODUCT = YES; 447 | }; 448 | name = Release; 449 | }; 450 | A4B5C651255FB0F40099F695 /* Debug */ = { 451 | isa = XCBuildConfiguration; 452 | buildSettings = { 453 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 454 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 455 | CODE_SIGN_STYLE = Automatic; 456 | DEVELOPMENT_ASSET_PATHS = "\"SweetCardScannerExample/Preview Content\""; 457 | DEVELOPMENT_TEAM = 6U769Q36UT; 458 | ENABLE_PREVIEWS = YES; 459 | INFOPLIST_FILE = SweetCardScannerExample/Info.plist; 460 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 461 | LD_RUNPATH_SEARCH_PATHS = ( 462 | "$(inherited)", 463 | "@executable_path/Frameworks", 464 | ); 465 | PRODUCT_BUNDLE_IDENTIFIER = net.aaronlab.SweetCardScannerExample; 466 | PRODUCT_NAME = "$(TARGET_NAME)"; 467 | SWIFT_VERSION = 5.0; 468 | TARGETED_DEVICE_FAMILY = "1,2"; 469 | }; 470 | name = Debug; 471 | }; 472 | A4B5C652255FB0F40099F695 /* Release */ = { 473 | isa = XCBuildConfiguration; 474 | buildSettings = { 475 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 476 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 477 | CODE_SIGN_STYLE = Automatic; 478 | DEVELOPMENT_ASSET_PATHS = "\"SweetCardScannerExample/Preview Content\""; 479 | DEVELOPMENT_TEAM = 6U769Q36UT; 480 | ENABLE_PREVIEWS = YES; 481 | INFOPLIST_FILE = SweetCardScannerExample/Info.plist; 482 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 483 | LD_RUNPATH_SEARCH_PATHS = ( 484 | "$(inherited)", 485 | "@executable_path/Frameworks", 486 | ); 487 | PRODUCT_BUNDLE_IDENTIFIER = net.aaronlab.SweetCardScannerExample; 488 | PRODUCT_NAME = "$(TARGET_NAME)"; 489 | SWIFT_VERSION = 5.0; 490 | TARGETED_DEVICE_FAMILY = "1,2"; 491 | }; 492 | name = Release; 493 | }; 494 | A4B5C654255FB0F40099F695 /* Debug */ = { 495 | isa = XCBuildConfiguration; 496 | buildSettings = { 497 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 498 | BUNDLE_LOADER = "$(TEST_HOST)"; 499 | CODE_SIGN_STYLE = Automatic; 500 | DEVELOPMENT_TEAM = 6U769Q36UT; 501 | INFOPLIST_FILE = SweetCardScannerExampleTests/Info.plist; 502 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 503 | LD_RUNPATH_SEARCH_PATHS = ( 504 | "$(inherited)", 505 | "@executable_path/Frameworks", 506 | "@loader_path/Frameworks", 507 | ); 508 | PRODUCT_BUNDLE_IDENTIFIER = net.aaronlab.SweetCardScannerExampleTests; 509 | PRODUCT_NAME = "$(TARGET_NAME)"; 510 | SWIFT_VERSION = 5.0; 511 | TARGETED_DEVICE_FAMILY = "1,2"; 512 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SweetCardScannerExample.app/SweetCardScannerExample"; 513 | }; 514 | name = Debug; 515 | }; 516 | A4B5C655255FB0F40099F695 /* Release */ = { 517 | isa = XCBuildConfiguration; 518 | buildSettings = { 519 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 520 | BUNDLE_LOADER = "$(TEST_HOST)"; 521 | CODE_SIGN_STYLE = Automatic; 522 | DEVELOPMENT_TEAM = 6U769Q36UT; 523 | INFOPLIST_FILE = SweetCardScannerExampleTests/Info.plist; 524 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 525 | LD_RUNPATH_SEARCH_PATHS = ( 526 | "$(inherited)", 527 | "@executable_path/Frameworks", 528 | "@loader_path/Frameworks", 529 | ); 530 | PRODUCT_BUNDLE_IDENTIFIER = net.aaronlab.SweetCardScannerExampleTests; 531 | PRODUCT_NAME = "$(TARGET_NAME)"; 532 | SWIFT_VERSION = 5.0; 533 | TARGETED_DEVICE_FAMILY = "1,2"; 534 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SweetCardScannerExample.app/SweetCardScannerExample"; 535 | }; 536 | name = Release; 537 | }; 538 | A4B5C657255FB0F40099F695 /* Debug */ = { 539 | isa = XCBuildConfiguration; 540 | buildSettings = { 541 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 542 | CODE_SIGN_STYLE = Automatic; 543 | DEVELOPMENT_TEAM = 6U769Q36UT; 544 | INFOPLIST_FILE = SweetCardScannerExampleUITests/Info.plist; 545 | LD_RUNPATH_SEARCH_PATHS = ( 546 | "$(inherited)", 547 | "@executable_path/Frameworks", 548 | "@loader_path/Frameworks", 549 | ); 550 | PRODUCT_BUNDLE_IDENTIFIER = net.aaronlab.SweetCardScannerExampleUITests; 551 | PRODUCT_NAME = "$(TARGET_NAME)"; 552 | SWIFT_VERSION = 5.0; 553 | TARGETED_DEVICE_FAMILY = "1,2"; 554 | TEST_TARGET_NAME = SweetCardScannerExample; 555 | }; 556 | name = Debug; 557 | }; 558 | A4B5C658255FB0F40099F695 /* Release */ = { 559 | isa = XCBuildConfiguration; 560 | buildSettings = { 561 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 562 | CODE_SIGN_STYLE = Automatic; 563 | DEVELOPMENT_TEAM = 6U769Q36UT; 564 | INFOPLIST_FILE = SweetCardScannerExampleUITests/Info.plist; 565 | LD_RUNPATH_SEARCH_PATHS = ( 566 | "$(inherited)", 567 | "@executable_path/Frameworks", 568 | "@loader_path/Frameworks", 569 | ); 570 | PRODUCT_BUNDLE_IDENTIFIER = net.aaronlab.SweetCardScannerExampleUITests; 571 | PRODUCT_NAME = "$(TARGET_NAME)"; 572 | SWIFT_VERSION = 5.0; 573 | TARGETED_DEVICE_FAMILY = "1,2"; 574 | TEST_TARGET_NAME = SweetCardScannerExample; 575 | }; 576 | name = Release; 577 | }; 578 | /* End XCBuildConfiguration section */ 579 | 580 | /* Begin XCConfigurationList section */ 581 | A4B5C626255FB0F10099F695 /* Build configuration list for PBXProject "SweetCardScannerExample" */ = { 582 | isa = XCConfigurationList; 583 | buildConfigurations = ( 584 | A4B5C64E255FB0F40099F695 /* Debug */, 585 | A4B5C64F255FB0F40099F695 /* Release */, 586 | ); 587 | defaultConfigurationIsVisible = 0; 588 | defaultConfigurationName = Release; 589 | }; 590 | A4B5C650255FB0F40099F695 /* Build configuration list for PBXNativeTarget "SweetCardScannerExample" */ = { 591 | isa = XCConfigurationList; 592 | buildConfigurations = ( 593 | A4B5C651255FB0F40099F695 /* Debug */, 594 | A4B5C652255FB0F40099F695 /* Release */, 595 | ); 596 | defaultConfigurationIsVisible = 0; 597 | defaultConfigurationName = Release; 598 | }; 599 | A4B5C653255FB0F40099F695 /* Build configuration list for PBXNativeTarget "SweetCardScannerExampleTests" */ = { 600 | isa = XCConfigurationList; 601 | buildConfigurations = ( 602 | A4B5C654255FB0F40099F695 /* Debug */, 603 | A4B5C655255FB0F40099F695 /* Release */, 604 | ); 605 | defaultConfigurationIsVisible = 0; 606 | defaultConfigurationName = Release; 607 | }; 608 | A4B5C656255FB0F40099F695 /* Build configuration list for PBXNativeTarget "SweetCardScannerExampleUITests" */ = { 609 | isa = XCConfigurationList; 610 | buildConfigurations = ( 611 | A4B5C657255FB0F40099F695 /* Debug */, 612 | A4B5C658255FB0F40099F695 /* Release */, 613 | ); 614 | defaultConfigurationIsVisible = 0; 615 | defaultConfigurationName = Release; 616 | }; 617 | /* End XCConfigurationList section */ 618 | 619 | /* Begin XCSwiftPackageProductDependency section */ 620 | A4B5C667255FB2D10099F695 /* SweetCardScanner */ = { 621 | isa = XCSwiftPackageProductDependency; 622 | productName = SweetCardScanner; 623 | }; 624 | /* End XCSwiftPackageProductDependency section */ 625 | }; 626 | rootObject = A4B5C623255FB0F10099F695 /* Project object */; 627 | } 628 | -------------------------------------------------------------------------------- /Examples/SweetCardScannerExample/SweetCardScannerExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/SweetCardScannerExample/SweetCardScannerExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Examples/SweetCardScannerExample/SweetCardScannerExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "Reg", 6 | "repositoryURL": "https://github.com/yhkaplan/Reg.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "c8f1f51bc088708b3b3ecf04523b5bedd6914222", 10 | "version": "0.3.0" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /Examples/SweetCardScannerExample/SweetCardScannerExample/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Examples/SweetCardScannerExample/SweetCardScannerExample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Examples/SweetCardScannerExample/SweetCardScannerExample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/SweetCardScannerExample/SweetCardScannerExample/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // SweetCardScannerExample 4 | // 5 | // Created by Aaron Lee on 2020-11-14. 6 | // 7 | 8 | import SwiftUI 9 | import SweetCardScanner 10 | 11 | struct ContentView: View { 12 | // MARK: - PROPERTIES 13 | 14 | @State var navigationStatus: NavigationStatus? = .ready 15 | @State var card: CreditCard? 16 | 17 | // MARK: - BODY 18 | 19 | var body: some View { 20 | 21 | NavigationView { 22 | 23 | GeometryReader { geometry in 24 | 25 | ZStack { 26 | 27 | NavigationLink( 28 | destination: ResultView(card: card) 29 | .onDisappear { 30 | /* 31 | You will be able to turn on the camera again 32 | when you come back to this view from the result view 33 | by changing your own customized navigation status. 34 | */ 35 | self.navigationStatus = .ready 36 | }, 37 | tag: NavigationStatus.pop, 38 | selection: $navigationStatus) { 39 | EmptyView() 40 | } 41 | 42 | /* 43 | You will be able to turn off the camera when you move to the result view 44 | with the `if` statement below. 45 | */ 46 | if navigationStatus == .ready { 47 | /* 48 | You can add some words "in lowercase" to try to skip in recognition to improve the performance like bank names, 49 | such as "td", "td banks", "cibc", and so on. 50 | Also you can try to add some words "in lowercase" for invalid names, such as "thru", "authorized", "signature". 51 | Or you can just simply usw liek "SweetCardScanner()" 52 | */ 53 | SweetCardScanner( 54 | wordsToSkip: ["td", "td bank", "cibc"], 55 | invalidNames: ["thru", "authorized", "signature"] 56 | ) 57 | .onError { err in 58 | print(err) 59 | } 60 | .onSuccess { card in 61 | self.card = card 62 | self.navigationStatus = .pop 63 | } 64 | } 65 | 66 | RoundedRectangle(cornerRadius: 16) 67 | .stroke() 68 | .foregroundColor(.white) 69 | .padding(16) 70 | .frame(width: geometry.size.width, height: geometry.size.width * 0.63, alignment: .center) 71 | 72 | } //: ZSTACK 73 | 74 | } //: GEOMETRY 75 | 76 | } //: NAVIGATION 77 | 78 | } 79 | } 80 | 81 | // MARK: - NavigationStatus 82 | 83 | enum NavigationStatus { 84 | case ready, pop 85 | } 86 | 87 | struct ContentView_Previews: PreviewProvider { 88 | static var previews: some View { 89 | ContentView() 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Examples/SweetCardScannerExample/SweetCardScannerExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSCameraUsageDescription 6 | This app needs the camera permission to read your creditcard. 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UIApplicationSceneManifest 26 | 27 | UIApplicationSupportsMultipleScenes 28 | 29 | 30 | UIApplicationSupportsIndirectInputEvents 31 | 32 | UILaunchScreen 33 | 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UISupportedInterfaceOrientations~ipad 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationPortraitUpsideDown 48 | UIInterfaceOrientationLandscapeLeft 49 | UIInterfaceOrientationLandscapeRight 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /Examples/SweetCardScannerExample/SweetCardScannerExample/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/SweetCardScannerExample/SweetCardScannerExample/ResultView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ResultView.swift 3 | // SweetCardScannerExample 4 | // 5 | // Created by Aaron Lee on 2020-11-14. 6 | // 7 | 8 | import SwiftUI 9 | import struct SweetCardScanner.CreditCard 10 | 11 | struct ResultView: View { 12 | // MARK: - PROPERTIES 13 | 14 | let card: CreditCard? 15 | 16 | // MARK: - BODY 17 | 18 | var body: some View { 19 | 20 | VStack { 21 | Text("Card Holder Name: \(card?.name ?? "N/A")") 22 | Text("Number: \(card?.number ?? "N/A")") 23 | Text("Expire Year: \(String(card?.year ?? 00))") 24 | Text("Expire Month: \(String(card?.month ?? 00))") 25 | Text("Card Vendor: \(card?.vendor.rawValue ?? "Unknown")") 26 | 27 | if let isNotExpired = card?.isNotExpired { 28 | isNotExpired ? Text("Expired: Not Expired") : Text("Expired: Expired") 29 | } 30 | 31 | } 32 | 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /Examples/SweetCardScannerExample/SweetCardScannerExample/SweetCardScannerExampleApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SweetCardScannerExampleApp.swift 3 | // SweetCardScannerExample 4 | // 5 | // Created by Aaron Lee on 2020-11-14. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct SweetCardScannerExampleApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Examples/SweetCardScannerExample/SweetCardScannerExampleTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Examples/SweetCardScannerExample/SweetCardScannerExampleTests/SweetCardScannerExampleTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SweetCardScannerExampleTests.swift 3 | // SweetCardScannerExampleTests 4 | // 5 | // Created by Aaron Lee on 2020-11-14. 6 | // 7 | 8 | import XCTest 9 | @testable import SweetCardScannerExample 10 | 11 | class SweetCardScannerExampleTests: XCTestCase { 12 | 13 | override func setUpWithError() throws { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | } 16 | 17 | override func tearDownWithError() throws { 18 | // Put teardown code here. This method is called after the invocation of each test method in the class. 19 | } 20 | 21 | func testExample() throws { 22 | // This is an example of a functional test case. 23 | // Use XCTAssert and related functions to verify your tests produce the correct results. 24 | } 25 | 26 | func testPerformanceExample() throws { 27 | // This is an example of a performance test case. 28 | self.measure { 29 | // Put the code you want to measure the time of here. 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /Examples/SweetCardScannerExample/SweetCardScannerExampleUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Examples/SweetCardScannerExample/SweetCardScannerExampleUITests/SweetCardScannerExampleUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SweetCardScannerExampleUITests.swift 3 | // SweetCardScannerExampleUITests 4 | // 5 | // Created by Aaron Lee on 2020-11-14. 6 | // 7 | 8 | import XCTest 9 | 10 | class SweetCardScannerExampleUITests: XCTestCase { 11 | 12 | override func setUpWithError() throws { 13 | // Put setup code here. This method is called before the invocation of each test method in the class. 14 | 15 | // In UI tests it is usually best to stop immediately when a failure occurs. 16 | continueAfterFailure = false 17 | 18 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 19 | } 20 | 21 | override func tearDownWithError() throws { 22 | // Put teardown code here. This method is called after the invocation of each test method in the class. 23 | } 24 | 25 | func testExample() throws { 26 | // UI tests must launch the application that they test. 27 | let app = XCUIApplication() 28 | app.launch() 29 | 30 | // Use recording to get started writing UI tests. 31 | // Use XCTAssert and related functions to verify your tests produce the correct results. 32 | } 33 | 34 | func testLaunchPerformance() throws { 35 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) { 36 | // This measures how long it takes to launch your application. 37 | measure(metrics: [XCTApplicationLaunchMetric()]) { 38 | XCUIApplication().launch() 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Aaron Lee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | 24 | - yhkaplan-credit-card-scanner 25 | 26 | Copyright (c) 2020 Joshua Kaplan 27 | 28 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 29 | 30 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 31 | 32 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 33 | 34 | Copyright © 2019 Apple Inc. 35 | 36 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 37 | 38 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 39 | 40 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "Reg", 6 | "repositoryURL": "https://github.com/yhkaplan/Reg.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "c8f1f51bc088708b3b3ecf04523b5bedd6914222", 10 | "version": "0.3.0" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "SweetCardScanner", 8 | 9 | platforms: [.iOS(.v13)], 10 | 11 | products: [ 12 | .library( 13 | name: "SweetCardScanner", 14 | targets: ["SweetCardScanner"]), 15 | ], 16 | 17 | dependencies: [ 18 | .package(url: "https://github.com/yhkaplan/Reg.git", from: "0.3.0"), 19 | ], 20 | 21 | targets: [ 22 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 23 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 24 | .target( 25 | name: "SweetCardScanner", 26 | dependencies: ["Reg"]), 27 | .testTarget( 28 | name: "SweetCardScannerTests", 29 | dependencies: ["SweetCardScanner"]), 30 | ] 31 | ) 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Language: Swift 5](https://img.shields.io/badge/language-Swift5-orange?style=flat&logo=swift)](https://developer.apple.com/swift) 2 | ![Platform: iOS 13+](https://img.shields.io/badge/platform-iOS%2013%2B-blue?style=flat&logo=apple) 3 | ![SwiftPM compatible](https://img.shields.io/badge/SPM-compatible-brightgreen?style=flat&logo=swift) 4 | [![License: MIT](https://img.shields.io/badge/license-MIT-lightgrey?style=flat)](https://github.com/aaronLab/SweetCardScanner/blob/main/LICENSE) 5 | [![Release version](https://img.shields.io/badge/release-v1.0.3-blue)](https://github.com/aaronLab/SweetCardScanner/releases) 6 | 7 | # SweetCardScanner 8 | 9 | SweetCardScanner is a fast and simple Card Scanner library written in Swift, based on [CreditCardScanner](https://github.com/yhkaplan/credit-card-scanner) and [Reg](https://github.com/yhkaplan/Reg) libraries by [@yhkaplan](https://github.com/yhkaplan) so that users can pay much more easily by capturing their credit/debit card with the rear camera. 10 | 11 |
12 | 13 |
14 | 15 | ## Requirements 16 | 17 | - iOS 13.0+ (due to SwiftUI, Vision Framework) 18 | - Tested on iOS 14.1 with iPhone X 19 | 20 | ## Installation 21 | 22 | - In Xcode, add the URL of this repository in SwiftPM: 23 | 24 | ```http 25 | https://github.com/aaronLab/SweetCardScanner.git 26 | ``` 27 | 28 | ## Usage 29 | 30 | 1. Add `NSCameraUsageDescription` into `Info.plist` for Camera Useage Description. 31 | 2. `import SweetCardScanner` on top of the `ContentView.swift`. 32 | 3. Now, you can use like `SweetCardScanner()` or `SweetCardScanner(wordsToSkip: Array?, invalidNames: Array?)` inside of the body. 33 | 4. With `wordsToSkip: Array?`, you can add some words "in lowercase" to try to skip in recognition to improve the performance like bank names, such as "td", "td banks", "cibc", and so on. 34 | 5. The default value of `wordsToSkip` is `["mastercard", "jcb", "visa", "express", "bank", "card", "platinum", "reward"]` 35 | 6. With `invalidNames: Array?`, you can try to add some words "in lowercase" for invalid names, such as "thru", "authorized", "signature". 36 | 7. The default value of `invalidNames: Array?` is `["expiration", "valid", "since", "from", "until", "month", "year"]` 37 | 8. Also, you can use completion clousures, such as `.onDismiss`, `.onError`, `.onSuccess` right after `SweetCardScanner()` like below. 38 | 9. If you want to turn off the camera when you move to the result view, you will need to use your own customized navigation status trick. [(Check the example below)](#example) 39 | 40 | ```Swift 41 | var body: some View { 42 | /* 43 | You can add some words "in lowercase" to try to skip in recognition to improve the performance like bank names, 44 | such as "td", "td banks", "cibc", and so on. 45 | Also you can try to add some words "in lowercase" for invalid names, such as "thru", "authorized", "signature". 46 | Or you can just simply usw liek "SweetCardScanner()" 47 | */ 48 | SweetCardScanner( 49 | wordsToSkip: ["td", "td bank", "cibc"], 50 | invalidNames: ["thru", "authorized", "signature"] 51 | ) 52 | .onDismiss { 53 | // Do something when the view dismissed. 54 | } 55 | .onError { error in 56 | // The 'error' above gives you 'CreditCardScannerError' struct below. 57 | print(error) 58 | } 59 | .onSuccess { card in 60 | // The card above gives you 'CreditCard' struct below. 61 | print(card) 62 | } 63 | } 64 | ``` 65 | 66 | ## CreditCardScannerError 67 | 68 | ```Swift 69 | public struct CreditCardScannerError: LocalizedError { 70 | public enum Kind { case cameraSetup, photoProcessing, authorizationDenied, capture } 71 | public var kind: Kind 72 | public var underlyingError: Error? 73 | public var errorDescription: String? { (underlyingError as? LocalizedError)?.errorDescription } 74 | } 75 | ``` 76 | 77 | ## CreditCard 78 | 79 | ```Swift 80 | public struct CreditCard { 81 | public var number: String? 82 | public var name: String? 83 | public var expireDate: DateComponents? 84 | public var year: Int { expireDate?.year ?? 0 } // This returns "yyyy" 85 | public var month: Int { expireDate?.month ?? 0 } // This returns "MM" 86 | /* 87 | CardVender below returns an element of an enum: 88 | Unknown, Amex, Visa, MasterCard, Diners, Discover, JCB, Elo, Hipercard, UnionPay 89 | */ 90 | public var vendor: CardVendor { CreditCardUtil.getVendor(candidate: self.number) } 91 | public var isNotExpired: Bool? { CreditCardUtil.isValid(candidate: self.expireDate) } 92 | } 93 | ``` 94 | 95 | ## CardVendor 96 | 97 | ```Swift 98 | public enum CardVendor: String { 99 | case Unknown, Amex, Visa, MasterCard, Diners, Discover, JCB, Elo, Hipercard, UnionPay 100 | } 101 | ``` 102 | 103 | ## Example 104 | 105 | You can customize your own view with SweetCardScanner, and SwiftUI like below. 106 | 107 | ```Swift 108 | // ContentView.swift 109 | 110 | import SwiftUI 111 | import SweetCardScanner 112 | 113 | struct ContentView: View { 114 | // MARK: - PROPERTIES 115 | 116 | @State var navigationStatus: NavigationStatus? = .ready 117 | @State var card: CreditCard? 118 | 119 | // MARK: - BODY 120 | 121 | var body: some View { 122 | 123 | NavigationView { 124 | 125 | GeometryReader { geometry in 126 | 127 | ZStack { 128 | 129 | NavigationLink( 130 | destination: ResultView(card: card) 131 | .onDisappear { 132 | /* 133 | You will be able to turn on the camera again 134 | when you come back to this view from the result view 135 | by changing your own customized navigation status. 136 | */ 137 | self.navigationStatus = .ready 138 | }, 139 | tag: NavigationStatus.pop, 140 | selection: $navigationStatus) { 141 | EmptyView() 142 | } 143 | 144 | /* 145 | You will be able to turn off the camera when you move to the result view 146 | with the `if` statement below. 147 | */ 148 | if navigationStatus == .ready { 149 | /* 150 | You can add some words "in lowercase" to try to skip in recognition to improve the performance like bank names, 151 | such as "td", "td banks", "cibc", and so on. 152 | Also you can try to add some words "in lowercase" for invalid names, such as "thru", "authorized", "signature". 153 | Or you can just simply usw liek "SweetCardScanner()" 154 | */ 155 | SweetCardScanner( 156 | wordsToSkip: ["td", "td bank", "cibc"], 157 | invalidNames: ["thru", "authorized", "signature"] 158 | ) 159 | .onError { err in 160 | print(err) 161 | } 162 | .onSuccess { card in 163 | self.card = card 164 | self.navigationStatus = .pop 165 | } 166 | } 167 | 168 | RoundedRectangle(cornerRadius: 16) 169 | .stroke() 170 | .foregroundColor(.white) 171 | .padding(16) 172 | .frame(width: geometry.size.width, height: geometry.size.width * 0.63, alignment: .center) 173 | 174 | } //: ZSTACK 175 | 176 | } //: GEOMETRY 177 | 178 | } //: NAVIGATION 179 | 180 | } 181 | } 182 | 183 | // MARK: - NavigationStatus 184 | enum NavigationStatus { 185 | case ready, pop 186 | } 187 | ``` 188 | 189 | ```Swift 190 | // ResultView.swift 191 | import SwiftUI 192 | import struct SweetCardScanner.CreditCard 193 | 194 | struct ResultView: View { 195 | // MARK: - PROPERTIES 196 | 197 | let card: CreditCard? 198 | 199 | // MARK: - BODY 200 | 201 | var body: some View { 202 | 203 | VStack { 204 | Text("Card Holder Name: \(card?.name ?? "N/A")") 205 | Text("Number: \(card?.number ?? "N/A")") 206 | Text("Expire Year: \(String(card?.year ?? 00))") 207 | Text("Expire Month: \(String(card?.month ?? 00))") 208 | Text("Card Vendor: \(card?.vendor.rawValue ?? "Unknown")") 209 | 210 | if let isNotExpired = card?.isNotExpired { 211 | isNotExpired ? Text("Expired: Not Expired") : Text("Expired: Expired") 212 | } 213 | 214 | } 215 | 216 | } 217 | 218 | } 219 | ``` 220 | 221 | ## License 222 | 223 | Licensed under [MIT](https://github.com/aaronLab/SweetCardScanner/blob/main/LICENSE) license. 224 | -------------------------------------------------------------------------------- /Sources/SweetCardScanner/CreditCardScanner/CameraView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CameraView.swift 3 | // CreditCardScannerPackageDescription 4 | // 5 | // Created by josh on 2020/07/23. 6 | // 7 | 8 | import AVFoundation 9 | import UIKit 10 | import VideoToolbox 11 | 12 | protocol CameraViewDelegate: AnyObject { 13 | func didCapture(image: CGImage) 14 | func didError(with: CreditCardScannerError) 15 | } 16 | 17 | final class CameraView: UIView { 18 | weak var delegate: CameraViewDelegate? 19 | 20 | // MARK: - Capture related 21 | 22 | private let captureSessionQueue = DispatchQueue( 23 | label: "com.yhkaplan.credit-card-scanner.captureSessionQueue" 24 | ) 25 | 26 | // MARK: - Capture related 27 | 28 | private let sampleBufferQueue = DispatchQueue( 29 | label: "com.yhkaplan.credit-card-scanner.sampleBufferQueue" 30 | ) 31 | 32 | init(delegate: CameraViewDelegate) { 33 | self.delegate = delegate 34 | super.init(frame: .zero) 35 | } 36 | 37 | @available(*, unavailable) 38 | required init?(coder: NSCoder) { 39 | fatalError("init(coder:) has not been implemented") 40 | } 41 | 42 | private let imageRatio: ImageRatio = .vga640x480 43 | 44 | // MARK: - Region of interest and text orientation 45 | 46 | /// Region of video data output buffer that recognition should be run on. 47 | /// Gets recalculated once the bounds of the preview layer are known. 48 | private var regionOfInterest: CGRect? 49 | 50 | var videoPreviewLayer: AVCaptureVideoPreviewLayer { 51 | guard let layer = layer as? AVCaptureVideoPreviewLayer else { 52 | fatalError("Expected `AVCaptureVideoPreviewLayer` type for layer. Check PreviewView.layerClass implementation.") 53 | } 54 | 55 | return layer 56 | } 57 | 58 | private var videoSession: AVCaptureSession? { 59 | get { 60 | videoPreviewLayer.session 61 | } 62 | set { 63 | videoPreviewLayer.session = newValue 64 | } 65 | } 66 | 67 | let semaphore = DispatchSemaphore(value: 1) 68 | 69 | override class var layerClass: AnyClass { 70 | AVCaptureVideoPreviewLayer.self 71 | } 72 | 73 | func stopSession() { 74 | videoSession?.stopRunning() 75 | } 76 | 77 | func startSession() { 78 | videoSession?.startRunning() 79 | } 80 | 81 | func setupCamera() { 82 | captureSessionQueue.async { [weak self] in 83 | self?._setupCamera() 84 | } 85 | } 86 | 87 | private func _setupCamera() { 88 | let session = AVCaptureSession() 89 | session.beginConfiguration() 90 | session.sessionPreset = imageRatio.preset 91 | 92 | guard let videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera, 93 | for: .video, 94 | position: .back) else { 95 | delegate?.didError(with: CreditCardScannerError(kind: .cameraSetup)) 96 | return 97 | } 98 | 99 | do { 100 | let deviceInput = try AVCaptureDeviceInput(device: videoDevice) 101 | session.canAddInput(deviceInput) 102 | session.addInput(deviceInput) 103 | } catch { 104 | delegate?.didError(with: CreditCardScannerError(kind: .cameraSetup, underlyingError: error)) 105 | } 106 | 107 | let videoOutput = AVCaptureVideoDataOutput() 108 | videoOutput.alwaysDiscardsLateVideoFrames = true 109 | videoOutput.setSampleBufferDelegate(self, queue: sampleBufferQueue) 110 | 111 | guard session.canAddOutput(videoOutput) else { 112 | delegate?.didError(with: CreditCardScannerError(kind: .cameraSetup)) 113 | return 114 | } 115 | 116 | session.addOutput(videoOutput) 117 | session.connections.forEach { 118 | $0.videoOrientation = .portrait 119 | } 120 | session.commitConfiguration() 121 | 122 | DispatchQueue.main.async { [weak self] in 123 | self?.videoPreviewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill 124 | self?.videoSession = session 125 | self?.startSession() 126 | } 127 | } 128 | 129 | func setupRegionOfInterest() { 130 | guard regionOfInterest == nil else { return } 131 | /// Mask layer that covering area around camera view 132 | let backLayer = CALayer() 133 | backLayer.frame = bounds 134 | 135 | // culcurate cutoutted frame 136 | let cuttedWidth: CGFloat = bounds.width - 40.0 137 | let cuttedHeight: CGFloat = cuttedWidth * CreditCard.heightRatioAgainstWidth 138 | 139 | let centerVertical = (bounds.height / 2.0) 140 | let cuttedY: CGFloat = centerVertical - (cuttedHeight / 2.0) 141 | let cuttedX: CGFloat = 20.0 142 | 143 | let cuttedRect = CGRect(x: cuttedX, 144 | y: cuttedY, 145 | width: cuttedWidth, 146 | height: cuttedHeight) 147 | 148 | let maskLayer = CAShapeLayer() 149 | let path = UIBezierPath(roundedRect: cuttedRect, cornerRadius: 10.0) 150 | 151 | path.append(UIBezierPath(rect: bounds)) 152 | maskLayer.path = path.cgPath 153 | maskLayer.fillRule = .evenOdd 154 | backLayer.mask = maskLayer 155 | layer.addSublayer(backLayer) 156 | 157 | let strokeLayer = CAShapeLayer() 158 | strokeLayer.lineWidth = 3.0 159 | strokeLayer.path = UIBezierPath(roundedRect: cuttedRect, cornerRadius: 10.0).cgPath 160 | strokeLayer.fillColor = nil 161 | layer.addSublayer(strokeLayer) 162 | 163 | let imageHeight: CGFloat = imageRatio.imageHeight 164 | let imageWidth: CGFloat = imageRatio.imageWidth 165 | 166 | let acutualImageRatioAgainstVisibleSize = imageWidth / bounds.width 167 | let interestX = cuttedRect.origin.x * acutualImageRatioAgainstVisibleSize 168 | let interestWidth = cuttedRect.width * acutualImageRatioAgainstVisibleSize 169 | let interestHeight = interestWidth * CreditCard.heightRatioAgainstWidth 170 | let interestY = (imageHeight / 2.0) - (interestHeight / 2.0) 171 | regionOfInterest = CGRect(x: interestX, 172 | y: interestY, 173 | width: interestWidth, 174 | height: interestHeight) 175 | } 176 | } 177 | 178 | extension CameraView: AVCaptureVideoDataOutputSampleBufferDelegate { 179 | func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { 180 | semaphore.wait() 181 | defer { semaphore.signal() } 182 | 183 | guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { 184 | delegate?.didError(with: CreditCardScannerError(kind: .capture)) 185 | delegate = nil 186 | return 187 | } 188 | 189 | var cgImage: CGImage? 190 | VTCreateCGImageFromCVPixelBuffer(pixelBuffer, options: nil, imageOut: &cgImage) 191 | 192 | guard let regionOfInterest = regionOfInterest else { 193 | return 194 | } 195 | 196 | guard let fullCameraImage = cgImage, 197 | let croppedImage = fullCameraImage.cropping(to: regionOfInterest) else { 198 | delegate?.didError(with: CreditCardScannerError(kind: .capture)) 199 | delegate = nil 200 | return 201 | } 202 | 203 | delegate?.didCapture(image: croppedImage) 204 | } 205 | } 206 | 207 | extension CreditCard { 208 | // The aspect ratio of credit-card is Golden-ratio 209 | static let heightRatioAgainstWidth: CGFloat = 0.6180469716 210 | } 211 | -------------------------------------------------------------------------------- /Sources/SweetCardScanner/CreditCardScanner/CreditCard.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CreditCard.swift 3 | // 4 | // 5 | // Created by josh on 2020/07/26. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct CreditCard { 11 | public init(number: String? = nil, name: String? = nil, expireDate: DateComponents? = nil) { 12 | self.number = number 13 | self.name = name 14 | self.expireDate = expireDate 15 | } 16 | 17 | public var number: String? 18 | public var name: String? 19 | public var expireDate: DateComponents? 20 | public var year: Int { expireDate?.year ?? 0 } 21 | public var month: Int { expireDate?.month ?? 0 } 22 | public var vendor: CardVendor { CreditCardUtil.getVendor(candidate: self.number) } 23 | public var isNotExpired: Bool? { CreditCardUtil.isValid(candidate: self.expireDate) } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Sources/SweetCardScanner/CreditCardScanner/CreditCardScannerError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CreditCardScannerError.swift 3 | // 4 | // 5 | // Created by josh on 2020/07/26. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct CreditCardScannerError: LocalizedError { 11 | public enum Kind { case cameraSetup, photoProcessing, authorizationDenied, capture } 12 | public var kind: Kind 13 | public var underlyingError: Error? 14 | public var errorDescription: String? { (underlyingError as? LocalizedError)?.errorDescription } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/SweetCardScanner/CreditCardScanner/CreditCardScannerViewController.swift: -------------------------------------------------------------------------------- 1 | // Created by josh on 2020/07/23. 2 | 3 | import AVFoundation 4 | import UIKit 5 | 6 | public protocol CreditCardScannerViewControllerDelegate: AnyObject { 7 | /// Called user taps the cancel button. Comes with a default implementation for UIViewControllers. 8 | /// - Warning: The viewController does not auto-dismiss. You must dismiss the viewController 9 | func creditCardScannerViewControllerDidCancel(_ viewController: CreditCardScannerViewController) 10 | /// Called when an error is encountered 11 | func creditCardScannerViewController(_ viewController: CreditCardScannerViewController, didErrorWith error: CreditCardScannerError) 12 | /// Called when finished successfully 13 | /// - Note: successful finish does not guarentee that all credit card info can be extracted 14 | func creditCardScannerViewController(_ viewController: CreditCardScannerViewController, didFinishWith card: CreditCard) 15 | } 16 | 17 | public extension CreditCardScannerViewControllerDelegate where Self: UIViewController { 18 | func creditCardScannerViewControllerDidCancel(_ viewController: CreditCardScannerViewController) { 19 | viewController.dismiss(animated: true) 20 | } 21 | } 22 | 23 | open class CreditCardScannerViewController: UIViewController { 24 | /// public propaties 25 | 26 | // MARK: - Subviews and layers 27 | 28 | /// View representing live camera 29 | private lazy var cameraView: CameraView = CameraView(delegate: self) 30 | 31 | /// Analyzes text data for credit card info 32 | private lazy var analyzer = ImageAnalyzer(delegate: self, wordsToSkip: self.wordsToSkip, invalidNames: self.invalidNames) 33 | 34 | private weak var delegate: CreditCardScannerViewControllerDelegate? 35 | 36 | /// For Custom Words 37 | private var wordsToSkip: Array? 38 | private var invalidNames: Array? 39 | 40 | // MARK: - Vision-related 41 | 42 | public init(delegate: CreditCardScannerViewControllerDelegate?, wordsToSkip: Array? = nil, invalidNames: Array? = nil) { 43 | self.delegate = delegate 44 | self.wordsToSkip = wordsToSkip 45 | self.invalidNames = invalidNames 46 | super.init(nibName: nil, bundle: nil) 47 | } 48 | 49 | @available(*, unavailable) 50 | public required init?(coder: NSCoder) { 51 | fatalError("Not implemented") 52 | } 53 | 54 | override open func viewWillAppear(_ animated: Bool) { 55 | super.viewWillAppear(animated) 56 | 57 | layoutSubviews() 58 | AVCaptureDevice.authorize { [weak self] authoriazed in 59 | // This is on the main thread. 60 | guard let strongSelf = self else { 61 | return 62 | } 63 | guard authoriazed else { 64 | strongSelf.delegate?.creditCardScannerViewController(strongSelf, didErrorWith: CreditCardScannerError(kind: .authorizationDenied, underlyingError: nil)) 65 | return 66 | } 67 | strongSelf.cameraView.setupCamera() 68 | } 69 | } 70 | 71 | override open func viewDidLayoutSubviews() { 72 | super.viewDidLayoutSubviews() 73 | cameraView.setupRegionOfInterest() 74 | } 75 | } 76 | 77 | private extension CreditCardScannerViewController { 78 | @objc func cancel(_ sender: UIButton) { 79 | delegate?.creditCardScannerViewControllerDidCancel(self) 80 | } 81 | 82 | func layoutSubviews() { 83 | cameraView.translatesAutoresizingMaskIntoConstraints = false 84 | view.addSubview(cameraView) 85 | cameraView.frame = view.frame 86 | } 87 | 88 | } 89 | 90 | extension CreditCardScannerViewController: CameraViewDelegate { 91 | internal func didCapture(image: CGImage) { 92 | analyzer.analyze(image: image) 93 | } 94 | 95 | internal func didError(with error: CreditCardScannerError) { 96 | DispatchQueue.main.async { [weak self] in 97 | guard let strongSelf = self else { return } 98 | strongSelf.delegate?.creditCardScannerViewController(strongSelf, didErrorWith: error) 99 | strongSelf.cameraView.stopSession() 100 | } 101 | } 102 | } 103 | 104 | extension CreditCardScannerViewController: ImageAnalyzerProtocol { 105 | internal func didFinishAnalyzation(with result: Result) { 106 | switch result { 107 | case let .success(creditCard): 108 | DispatchQueue.main.async { [weak self] in 109 | guard let strongSelf = self else { return } 110 | strongSelf.cameraView.stopSession() 111 | strongSelf.delegate?.creditCardScannerViewController(strongSelf, didFinishWith: creditCard) 112 | } 113 | 114 | case let .failure(error): 115 | DispatchQueue.main.async { [weak self] in 116 | guard let strongSelf = self else { return } 117 | strongSelf.cameraView.stopSession() 118 | strongSelf.delegate?.creditCardScannerViewController(strongSelf, didErrorWith: error) 119 | } 120 | } 121 | } 122 | } 123 | 124 | extension AVCaptureDevice { 125 | static func authorize(authorizedHandler: @escaping ((Bool) -> Void)) { 126 | let mainThreadHandler: ((Bool) -> Void) = { isAuthorized in 127 | DispatchQueue.main.async { 128 | authorizedHandler(isAuthorized) 129 | } 130 | } 131 | 132 | switch authorizationStatus(for: .video) { 133 | case .authorized: 134 | mainThreadHandler(true) 135 | case .notDetermined: 136 | requestAccess(for: .video, completionHandler: { granted in 137 | mainThreadHandler(granted) 138 | }) 139 | default: 140 | mainThreadHandler(false) 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /Sources/SweetCardScanner/CreditCardScanner/CreditCardUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CreditCardUtils.swift 3 | // SweetCardScanner 4 | // 5 | // Created by Aaron Lee on 2020-11-30. 6 | // 7 | 8 | import Foundation 9 | 10 | public class CreditCardUtil { 11 | 12 | /// Get Card Vendor 13 | static func getVendor(candidate: String?) -> CardVendor { 14 | guard let candidate = candidate else { return .Unknown } 15 | let onlyNumber = candidate.replacingOccurrences(of: "-", with: "") 16 | 17 | var type: CardVendor = .Unknown 18 | 19 | for card in CardVendor.allCards { 20 | if (matchCardRegex(regex: card.regex, candidate: onlyNumber)) { 21 | type = card 22 | break 23 | } 24 | } 25 | 26 | return type 27 | } 28 | 29 | /// Match Card Vendor 30 | static func matchCardRegex(regex: String, candidate: String)-> Bool { 31 | do { 32 | let regex = try NSRegularExpression(pattern: regex, options: [.caseInsensitive]) 33 | let nsString = candidate as NSString 34 | let match = regex.firstMatch(in: candidate, options: [], range: NSMakeRange(0, nsString.length)) 35 | return (match != nil) 36 | } catch { 37 | return false 38 | } 39 | } 40 | 41 | /// Validate Expiration 42 | static func isValid(candidate: DateComponents?) -> Bool? { 43 | if let candidate = candidate { 44 | let year = candidate.year ?? 0 45 | let month = candidate.month ?? 0 46 | 47 | let now = Date() 48 | let yearFormatter = DateFormatter() 49 | let monthFormatter = DateFormatter() 50 | yearFormatter.dateFormat = "yyyy" 51 | monthFormatter.dateFormat = "MM" 52 | 53 | let currentYear = Int(yearFormatter.string(from: now)) 54 | let currentMonth = Int(monthFormatter.string(from: now)) 55 | 56 | if let currentYear = currentYear, let currentMonth = currentMonth { 57 | if year > currentYear || year == currentYear && month >= currentMonth && month <= 12 && month > 0 { 58 | 59 | return true 60 | } 61 | 62 | return false 63 | } 64 | 65 | return nil 66 | } 67 | 68 | return nil 69 | } 70 | 71 | } 72 | 73 | /// Card Vendors 74 | public enum CardVendor: String { 75 | case Unknown, Amex, Visa, MasterCard, Diners, Discover, JCB, Elo, Hipercard, UnionPay 76 | 77 | static let allCards = [Amex, Visa, MasterCard, Diners, Discover, JCB, Elo, Hipercard, UnionPay] 78 | 79 | var regex: String { 80 | switch self { 81 | case .Amex: 82 | return "^3[47][0-9]{5,}$" 83 | case .Visa: 84 | return "^4[0-9]{6,}([0-9]{3})?$" 85 | case .MasterCard: 86 | return "^(5[1-5][0-9]{4}|677189)[0-9]{5,}$" 87 | case .Diners: 88 | return "^3(?:0[0-5]|[68][0-9])[0-9]{4,}$" 89 | case .Discover: 90 | return "^6(?:011|5[0-9]{2})[0-9]{3,}$" 91 | case .JCB: 92 | return "^(?:2131|1800|35[0-9]{3})[0-9]{3,}$" 93 | case .UnionPay: 94 | return "^(62|88)[0-9]{5,}$" 95 | case .Hipercard: 96 | return "^(606282|3841)[0-9]{5,}$" 97 | case .Elo: 98 | return "^((((636368)|(438935)|(504175)|(451416)|(636297))[0-9]{0,10})|((5067)|(4576)|(4011))[0-9]{0,12})$" 99 | default: 100 | return "" 101 | } 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /Sources/SweetCardScanner/CreditCardScanner/ImageAnalyzer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageAnalyzer.swift 3 | // 4 | // 5 | // Created by miyasaka on 2020/07/30. 6 | // 7 | 8 | import Foundation 9 | import Reg 10 | import Vision 11 | 12 | protocol ImageAnalyzerProtocol: AnyObject { 13 | func didFinishAnalyzation(with result: Result) 14 | } 15 | 16 | final class ImageAnalyzer { 17 | enum Candidate: Hashable { 18 | case number(String), name(String) 19 | case expireDate(DateComponents) 20 | } 21 | 22 | typealias PredictedCount = Int 23 | 24 | private var selectedCard = CreditCard() 25 | private var predictedCardInfo: [Candidate: PredictedCount] = [:] 26 | 27 | // For Custom Words 28 | private var wordsToSkip: Array? 29 | private var invalidNames: Array? 30 | 31 | private weak var delegate: ImageAnalyzerProtocol? 32 | init(delegate: ImageAnalyzerProtocol, wordsToSkip: Array? = nil, invalidNames: Array? = nil) { 33 | self.delegate = delegate 34 | self.wordsToSkip = wordsToSkip 35 | self.invalidNames = invalidNames 36 | } 37 | 38 | // MARK: - Vision-related 39 | 40 | public lazy var request = VNRecognizeTextRequest(completionHandler: requestHandler) 41 | func analyze(image: CGImage) { 42 | let requestHandler = VNImageRequestHandler( 43 | cgImage: image, 44 | orientation: .up, 45 | options: [:] 46 | ) 47 | 48 | do { 49 | try requestHandler.perform([request]) 50 | } catch { 51 | let e = CreditCardScannerError(kind: .photoProcessing, underlyingError: error) 52 | delegate?.didFinishAnalyzation(with: .failure(e)) 53 | delegate = nil 54 | } 55 | } 56 | 57 | lazy var requestHandler: ((VNRequest, Error?) -> Void)? = { [weak self] request, _ in 58 | guard let strongSelf = self else { return } 59 | 60 | let creditCardNumber: Regex = #"(?:\d[ -]*?){13,16}"# 61 | let month: Regex = #"(\d{2})\/\d{2}"# 62 | let year: Regex = #"\d{2}\/(\d{2})"# 63 | var wordsToSkip = ["mastercard", "jcb", "visa", "express", "bank", "card", "platinum", "reward"] 64 | if let safeSkipWords = strongSelf.wordsToSkip { 65 | wordsToSkip += safeSkipWords 66 | } 67 | // These may be contained in the date strings, so ignore them only for names 68 | var invalidNames = ["expiration", "valid", "since", "from", "until", "month", "year"] 69 | if let safeInvalidNames = strongSelf.invalidNames { 70 | invalidNames += safeInvalidNames 71 | } 72 | let name: Regex = #"([A-z]{2,}\h([A-z.]+\h)?[A-z]{2,})"# 73 | 74 | guard let results = request.results as? [VNRecognizedTextObservation] else { return } 75 | 76 | var creditCard = CreditCard(number: nil, name: nil, expireDate: nil) 77 | 78 | let maxCandidates = 1 79 | for result in results { 80 | guard 81 | let candidate = result.topCandidates(maxCandidates).first, 82 | candidate.confidence > 0.1 83 | else { continue } 84 | 85 | let string = candidate.string 86 | let containsWordToSkip = wordsToSkip.contains { string.lowercased().contains($0) } 87 | if containsWordToSkip { continue } 88 | 89 | if let cardNumber = creditCardNumber.firstMatch(in: string)? 90 | .replacingOccurrences(of: " ", with: "") 91 | .replacingOccurrences(of: "-", with: "") { 92 | creditCard.number = cardNumber 93 | 94 | // the first capture is the entire regex match, so using the last 95 | } else if let month = month.captures(in: string).last.flatMap(Int.init), 96 | // Appending 20 to year is necessary to get correct century 97 | let year = year.captures(in: string).last.flatMap({ Int("20" + $0) }) { 98 | creditCard.expireDate = DateComponents(year: year, month: month) 99 | 100 | } else if let name = name.firstMatch(in: string) { 101 | let containsInvalidName = invalidNames.contains { name.lowercased().contains($0) } 102 | if containsInvalidName { continue } 103 | creditCard.name = name 104 | 105 | } else { 106 | continue 107 | } 108 | } 109 | 110 | // Name 111 | if let name = creditCard.name { 112 | let count = strongSelf.predictedCardInfo[.name(name), default: 0] 113 | strongSelf.predictedCardInfo[.name(name)] = count + 1 114 | if count > 2 { 115 | strongSelf.selectedCard.name = name 116 | } 117 | } 118 | // ExpireDate 119 | if let date = creditCard.expireDate { 120 | let count = strongSelf.predictedCardInfo[.expireDate(date), default: 0] 121 | strongSelf.predictedCardInfo[.expireDate(date)] = count + 1 122 | if count > 2 { 123 | strongSelf.selectedCard.expireDate = date 124 | } 125 | } 126 | 127 | // Number 128 | if let number = creditCard.number { 129 | let count = strongSelf.predictedCardInfo[.number(number), default: 0] 130 | strongSelf.predictedCardInfo[.number(number)] = count + 1 131 | if count > 2 { 132 | strongSelf.selectedCard.number = number 133 | } 134 | } 135 | 136 | if strongSelf.selectedCard.number != nil { 137 | strongSelf.delegate?.didFinishAnalyzation(with: .success(strongSelf.selectedCard)) 138 | strongSelf.delegate = nil 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /Sources/SweetCardScanner/CreditCardScanner/ImageRatio.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by miyasaka on 2020/07/31. 6 | // 7 | 8 | import AVFoundation 9 | import Foundation 10 | 11 | enum ImageRatio { 12 | case cif352x288 13 | case vga640x480 14 | case iFrame960x540 15 | case iFrame1280x720 16 | case hd1280x720 17 | case hd1920x1080 18 | case hd4K3840x2160 19 | 20 | var preset: AVCaptureSession.Preset { 21 | switch self { 22 | case .cif352x288: 23 | return .cif352x288 24 | case .vga640x480: 25 | return .vga640x480 26 | case .iFrame960x540: 27 | return .iFrame960x540 28 | case .iFrame1280x720: 29 | return .iFrame1280x720 30 | case .hd1280x720: 31 | return .hd1280x720 32 | case .hd1920x1080: 33 | return .hd1920x1080 34 | case .hd4K3840x2160: 35 | return .hd4K3840x2160 36 | } 37 | } 38 | 39 | var imageHeight: CGFloat { 40 | switch self { 41 | case .cif352x288: 42 | return 352.0 43 | case .vga640x480: 44 | return 640.0 45 | case .iFrame960x540: 46 | return 960.0 47 | case .iFrame1280x720: 48 | return 1280.0 49 | case .hd1280x720: 50 | return 1280.0 51 | case .hd1920x1080: 52 | return 1920.0 53 | case .hd4K3840x2160: 54 | return 3840.0 55 | } 56 | } 57 | 58 | var imageWidth: CGFloat { 59 | switch self { 60 | case .cif352x288: 61 | return 288.0 62 | case .vga640x480: 63 | return 480.0 64 | case .iFrame960x540: 65 | return 540.0 66 | case .hd1280x720: 67 | return 720.0 68 | case .iFrame1280x720: 69 | return 720.0 70 | case .hd1920x1080: 71 | return 1080.0 72 | case .hd4K3840x2160: 73 | return 2160.0 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Sources/SweetCardScanner/CreditCardScanner/String+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+Extensions.swift 3 | // SweetCardScanner 4 | // 5 | // Created by Aaron Lee on 2020-11-30. 6 | // 7 | 8 | import Foundation 9 | 10 | extension String { 11 | 12 | func subString(from: Int, to: Int) -> String { 13 | let startIndex = self.index(self.startIndex, offsetBy: from) 14 | let endIndex = self.index(self.startIndex, offsetBy: to) 15 | return String(self[startIndex...endIndex]) 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Sources/SweetCardScanner/SweetCardScanner.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SweetCardScanner.swift 3 | // SweetCardScanner 4 | // 5 | // Created by Aaron Lee on 2020-11-14. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public struct SweetCardScanner: UIViewControllerRepresentable { 11 | 12 | private var onDismiss: (() -> Void)? 13 | private var onError: ((CreditCardScannerError) -> Void)? 14 | private var onSuccess: ((CreditCard) -> Void)? 15 | 16 | // For Custom Words 17 | private var wordsToSkip: Array? 18 | private var invalidNames: Array? 19 | 20 | public init(wordsToSkip: Array? = nil, invalidNames: Array? = nil) { 21 | self.wordsToSkip = wordsToSkip 22 | self.invalidNames = invalidNames 23 | } 24 | 25 | public func makeUIViewController(context: Context) -> some UIViewController { 26 | let viewController = CreditCardScannerViewController(delegate: context.coordinator, wordsToSkip: wordsToSkip, invalidNames: invalidNames) 27 | return viewController 28 | } 29 | 30 | public func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { } 31 | 32 | public func onDismiss(perform callback: @escaping () -> ()) -> Self { 33 | var copy = self 34 | copy.onDismiss = callback 35 | return copy 36 | } 37 | 38 | public func onError(perform callback: @escaping (CreditCardScannerError) -> ()) -> Self { 39 | var copy = self 40 | copy.onError = callback 41 | return copy 42 | } 43 | 44 | public func onSuccess(perform callback: @escaping (CreditCard) -> ()) -> Self { 45 | var copy = self 46 | copy.onSuccess = callback 47 | return copy 48 | } 49 | 50 | } 51 | 52 | // MARK: - COORDINATOR 53 | 54 | extension SweetCardScanner { 55 | 56 | public func makeCoordinator() -> Coordinator { 57 | return Coordinator(onDismiss: self.onDismiss, onError: self.onError, onSuccess: self.onSuccess) 58 | } 59 | 60 | public class Coordinator: NSObject, CreditCardScannerViewControllerDelegate { 61 | 62 | private var onDismiss: (() -> Void)? 63 | private var onError: ((CreditCardScannerError) -> Void)? 64 | private var onSuccess: ((CreditCard) -> Void)? 65 | 66 | public init(onDismiss: (() -> Void)?, onError: ((CreditCardScannerError) -> Void)?, onSuccess: ((CreditCard) -> Void)?) { 67 | self.onDismiss = onDismiss 68 | self.onError = onError 69 | self.onSuccess = onSuccess 70 | } 71 | 72 | public func creditCardScannerViewControllerDidCancel(_ viewController: CreditCardScannerViewController) { 73 | self.onDismiss?() 74 | } 75 | 76 | public func creditCardScannerViewController(_ viewController: CreditCardScannerViewController, didErrorWith error: CreditCardScannerError) { 77 | self.onError?(error) 78 | } 79 | 80 | public func creditCardScannerViewController(_ viewController: CreditCardScannerViewController, didFinishWith card: CreditCard) { 81 | self.onSuccess?(card) 82 | } 83 | 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /SweetCardScanner.xcodeproj/Reg_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /SweetCardScanner.xcodeproj/SweetCardScannerTests_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | BNDL 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /SweetCardScanner.xcodeproj/SweetCardScanner_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /SweetCardScanner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXAggregateTarget section */ 10 | "SweetCardScanner::SweetCardScannerPackageTests::ProductTarget" /* SweetCardScannerPackageTests */ = { 11 | isa = PBXAggregateTarget; 12 | buildConfigurationList = OBJ_53 /* Build configuration list for PBXAggregateTarget "SweetCardScannerPackageTests" */; 13 | buildPhases = ( 14 | ); 15 | dependencies = ( 16 | OBJ_56 /* PBXTargetDependency */, 17 | ); 18 | name = SweetCardScannerPackageTests; 19 | productName = SweetCardScannerPackageTests; 20 | }; 21 | /* End PBXAggregateTarget section */ 22 | 23 | /* Begin PBXBuildFile section */ 24 | A42A65E0257532C6003FA790 /* CreditCardUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = A42A65DF257532C6003FA790 /* CreditCardUtils.swift */; }; 25 | A42A65E8257532F4003FA790 /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A42A65E7257532F4003FA790 /* String+Extensions.swift */; }; 26 | A4B5C547255FA8C80099F695 /* ImageAnalyzer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4B5C541255FA8C80099F695 /* ImageAnalyzer.swift */; }; 27 | A4B5C548255FA8C80099F695 /* ImageRatio.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4B5C542255FA8C80099F695 /* ImageRatio.swift */; }; 28 | A4B5C549255FA8C80099F695 /* CreditCardScannerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4B5C543255FA8C80099F695 /* CreditCardScannerViewController.swift */; }; 29 | A4B5C54A255FA8C80099F695 /* CameraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4B5C544255FA8C80099F695 /* CameraView.swift */; }; 30 | A4B5C54B255FA8C80099F695 /* CreditCardScannerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4B5C545255FA8C80099F695 /* CreditCardScannerError.swift */; }; 31 | A4B5C54C255FA8C80099F695 /* CreditCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4B5C546255FA8C80099F695 /* CreditCard.swift */; }; 32 | OBJ_29 /* Reg.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_17 /* Reg.swift */; }; 33 | OBJ_36 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_18 /* Package.swift */; }; 34 | OBJ_42 /* SweetCardScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_9 /* SweetCardScanner.swift */; }; 35 | OBJ_44 /* Reg.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "Reg::Reg::Product" /* Reg.framework */; }; 36 | OBJ_51 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; }; 37 | OBJ_62 /* SweetCardScannerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* SweetCardScannerTests.swift */; }; 38 | OBJ_63 /* XCTestManifests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* XCTestManifests.swift */; }; 39 | OBJ_65 /* SweetCardScanner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "SweetCardScanner::SweetCardScanner::Product" /* SweetCardScanner.framework */; }; 40 | OBJ_66 /* Reg.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "Reg::Reg::Product" /* Reg.framework */; }; 41 | /* End PBXBuildFile section */ 42 | 43 | /* Begin PBXContainerItemProxy section */ 44 | A4B5C52E255FA85E0099F695 /* PBXContainerItemProxy */ = { 45 | isa = PBXContainerItemProxy; 46 | containerPortal = OBJ_1 /* Project object */; 47 | proxyType = 1; 48 | remoteGlobalIDString = "Reg::Reg"; 49 | remoteInfo = Reg; 50 | }; 51 | A4B5C52F255FA85E0099F695 /* PBXContainerItemProxy */ = { 52 | isa = PBXContainerItemProxy; 53 | containerPortal = OBJ_1 /* Project object */; 54 | proxyType = 1; 55 | remoteGlobalIDString = "SweetCardScanner::SweetCardScanner"; 56 | remoteInfo = SweetCardScanner; 57 | }; 58 | A4B5C530255FA85E0099F695 /* PBXContainerItemProxy */ = { 59 | isa = PBXContainerItemProxy; 60 | containerPortal = OBJ_1 /* Project object */; 61 | proxyType = 1; 62 | remoteGlobalIDString = "Reg::Reg"; 63 | remoteInfo = Reg; 64 | }; 65 | A4B5C535255FA85F0099F695 /* PBXContainerItemProxy */ = { 66 | isa = PBXContainerItemProxy; 67 | containerPortal = OBJ_1 /* Project object */; 68 | proxyType = 1; 69 | remoteGlobalIDString = "SweetCardScanner::SweetCardScannerTests"; 70 | remoteInfo = SweetCardScannerTests; 71 | }; 72 | /* End PBXContainerItemProxy section */ 73 | 74 | /* Begin PBXFileReference section */ 75 | A42A65DF257532C6003FA790 /* CreditCardUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreditCardUtils.swift; sourceTree = ""; }; 76 | A42A65E7257532F4003FA790 /* String+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = ""; }; 77 | A4B5C538255FA8930099F695 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 78 | A4B5C541255FA8C80099F695 /* ImageAnalyzer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageAnalyzer.swift; sourceTree = ""; }; 79 | A4B5C542255FA8C80099F695 /* ImageRatio.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageRatio.swift; sourceTree = ""; }; 80 | A4B5C543255FA8C80099F695 /* CreditCardScannerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreditCardScannerViewController.swift; sourceTree = ""; }; 81 | A4B5C544255FA8C80099F695 /* CameraView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraView.swift; sourceTree = ""; }; 82 | A4B5C545255FA8C80099F695 /* CreditCardScannerError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreditCardScannerError.swift; sourceTree = ""; }; 83 | A4B5C546255FA8C80099F695 /* CreditCard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreditCard.swift; sourceTree = ""; }; 84 | A4B5C553255FAA210099F695 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; 85 | OBJ_12 /* SweetCardScannerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SweetCardScannerTests.swift; sourceTree = ""; }; 86 | OBJ_13 /* XCTestManifests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTestManifests.swift; sourceTree = ""; }; 87 | OBJ_17 /* Reg.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reg.swift; sourceTree = ""; }; 88 | OBJ_18 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; name = Package.swift; path = /Users/aaronlee/Documents/GitHub/SweetCardScanner/.build/checkouts/Reg/Package.swift; sourceTree = ""; }; 89 | OBJ_23 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 90 | OBJ_6 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; 91 | OBJ_9 /* SweetCardScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SweetCardScanner.swift; sourceTree = ""; }; 92 | "Reg::Reg::Product" /* Reg.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Reg.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 93 | "SweetCardScanner::SweetCardScanner::Product" /* SweetCardScanner.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = SweetCardScanner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 94 | "SweetCardScanner::SweetCardScannerTests::Product" /* SweetCardScannerTests.xctest */ = {isa = PBXFileReference; lastKnownFileType = file; path = SweetCardScannerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 95 | /* End PBXFileReference section */ 96 | 97 | /* Begin PBXFrameworksBuildPhase section */ 98 | OBJ_30 /* Frameworks */ = { 99 | isa = PBXFrameworksBuildPhase; 100 | buildActionMask = 0; 101 | files = ( 102 | ); 103 | runOnlyForDeploymentPostprocessing = 0; 104 | }; 105 | OBJ_43 /* Frameworks */ = { 106 | isa = PBXFrameworksBuildPhase; 107 | buildActionMask = 0; 108 | files = ( 109 | OBJ_44 /* Reg.framework in Frameworks */, 110 | ); 111 | runOnlyForDeploymentPostprocessing = 0; 112 | }; 113 | OBJ_64 /* Frameworks */ = { 114 | isa = PBXFrameworksBuildPhase; 115 | buildActionMask = 0; 116 | files = ( 117 | OBJ_65 /* SweetCardScanner.framework in Frameworks */, 118 | OBJ_66 /* Reg.framework in Frameworks */, 119 | ); 120 | runOnlyForDeploymentPostprocessing = 0; 121 | }; 122 | /* End PBXFrameworksBuildPhase section */ 123 | 124 | /* Begin PBXGroup section */ 125 | A4B5C540255FA8B50099F695 /* CreditCardScanner */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | A4B5C544255FA8C80099F695 /* CameraView.swift */, 129 | A4B5C546255FA8C80099F695 /* CreditCard.swift */, 130 | A4B5C545255FA8C80099F695 /* CreditCardScannerError.swift */, 131 | A4B5C543255FA8C80099F695 /* CreditCardScannerViewController.swift */, 132 | A4B5C541255FA8C80099F695 /* ImageAnalyzer.swift */, 133 | A4B5C542255FA8C80099F695 /* ImageRatio.swift */, 134 | A42A65DF257532C6003FA790 /* CreditCardUtils.swift */, 135 | A42A65E7257532F4003FA790 /* String+Extensions.swift */, 136 | ); 137 | path = CreditCardScanner; 138 | sourceTree = ""; 139 | }; 140 | OBJ_10 /* Tests */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | OBJ_11 /* SweetCardScannerTests */, 144 | ); 145 | name = Tests; 146 | sourceTree = SOURCE_ROOT; 147 | }; 148 | OBJ_11 /* SweetCardScannerTests */ = { 149 | isa = PBXGroup; 150 | children = ( 151 | OBJ_12 /* SweetCardScannerTests.swift */, 152 | OBJ_13 /* XCTestManifests.swift */, 153 | ); 154 | name = SweetCardScannerTests; 155 | path = Tests/SweetCardScannerTests; 156 | sourceTree = SOURCE_ROOT; 157 | }; 158 | OBJ_14 /* Dependencies */ = { 159 | isa = PBXGroup; 160 | children = ( 161 | OBJ_15 /* Reg 0.3.0 */, 162 | ); 163 | name = Dependencies; 164 | sourceTree = ""; 165 | }; 166 | OBJ_15 /* Reg 0.3.0 */ = { 167 | isa = PBXGroup; 168 | children = ( 169 | OBJ_16 /* Reg */, 170 | OBJ_18 /* Package.swift */, 171 | ); 172 | name = "Reg 0.3.0"; 173 | sourceTree = SOURCE_ROOT; 174 | }; 175 | OBJ_16 /* Reg */ = { 176 | isa = PBXGroup; 177 | children = ( 178 | OBJ_17 /* Reg.swift */, 179 | ); 180 | name = Reg; 181 | path = .build/checkouts/Reg/Sources/Reg; 182 | sourceTree = SOURCE_ROOT; 183 | }; 184 | OBJ_19 /* Products */ = { 185 | isa = PBXGroup; 186 | children = ( 187 | "SweetCardScanner::SweetCardScanner::Product" /* SweetCardScanner.framework */, 188 | "Reg::Reg::Product" /* Reg.framework */, 189 | "SweetCardScanner::SweetCardScannerTests::Product" /* SweetCardScannerTests.xctest */, 190 | ); 191 | name = Products; 192 | sourceTree = BUILT_PRODUCTS_DIR; 193 | }; 194 | OBJ_5 = { 195 | isa = PBXGroup; 196 | children = ( 197 | A4B5C553255FAA210099F695 /* LICENSE */, 198 | A4B5C538255FA8930099F695 /* README.md */, 199 | OBJ_6 /* Package.swift */, 200 | OBJ_7 /* Sources */, 201 | OBJ_10 /* Tests */, 202 | OBJ_14 /* Dependencies */, 203 | OBJ_19 /* Products */, 204 | OBJ_23 /* README.md */, 205 | ); 206 | sourceTree = ""; 207 | }; 208 | OBJ_7 /* Sources */ = { 209 | isa = PBXGroup; 210 | children = ( 211 | OBJ_8 /* SweetCardScanner */, 212 | ); 213 | name = Sources; 214 | sourceTree = SOURCE_ROOT; 215 | }; 216 | OBJ_8 /* SweetCardScanner */ = { 217 | isa = PBXGroup; 218 | children = ( 219 | A4B5C540255FA8B50099F695 /* CreditCardScanner */, 220 | OBJ_9 /* SweetCardScanner.swift */, 221 | ); 222 | name = SweetCardScanner; 223 | path = Sources/SweetCardScanner; 224 | sourceTree = SOURCE_ROOT; 225 | }; 226 | /* End PBXGroup section */ 227 | 228 | /* Begin PBXNativeTarget section */ 229 | "Reg::Reg" /* Reg */ = { 230 | isa = PBXNativeTarget; 231 | buildConfigurationList = OBJ_25 /* Build configuration list for PBXNativeTarget "Reg" */; 232 | buildPhases = ( 233 | OBJ_28 /* Sources */, 234 | OBJ_30 /* Frameworks */, 235 | ); 236 | buildRules = ( 237 | ); 238 | dependencies = ( 239 | ); 240 | name = Reg; 241 | productName = Reg; 242 | productReference = "Reg::Reg::Product" /* Reg.framework */; 243 | productType = "com.apple.product-type.framework"; 244 | }; 245 | "Reg::SwiftPMPackageDescription" /* RegPackageDescription */ = { 246 | isa = PBXNativeTarget; 247 | buildConfigurationList = OBJ_32 /* Build configuration list for PBXNativeTarget "RegPackageDescription" */; 248 | buildPhases = ( 249 | OBJ_35 /* Sources */, 250 | ); 251 | buildRules = ( 252 | ); 253 | dependencies = ( 254 | ); 255 | name = RegPackageDescription; 256 | productName = RegPackageDescription; 257 | productType = "com.apple.product-type.framework"; 258 | }; 259 | "SweetCardScanner::SweetCardScanner" /* SweetCardScanner */ = { 260 | isa = PBXNativeTarget; 261 | buildConfigurationList = OBJ_38 /* Build configuration list for PBXNativeTarget "SweetCardScanner" */; 262 | buildPhases = ( 263 | OBJ_41 /* Sources */, 264 | OBJ_43 /* Frameworks */, 265 | ); 266 | buildRules = ( 267 | ); 268 | dependencies = ( 269 | OBJ_45 /* PBXTargetDependency */, 270 | ); 271 | name = SweetCardScanner; 272 | productName = SweetCardScanner; 273 | productReference = "SweetCardScanner::SweetCardScanner::Product" /* SweetCardScanner.framework */; 274 | productType = "com.apple.product-type.framework"; 275 | }; 276 | "SweetCardScanner::SweetCardScannerTests" /* SweetCardScannerTests */ = { 277 | isa = PBXNativeTarget; 278 | buildConfigurationList = OBJ_58 /* Build configuration list for PBXNativeTarget "SweetCardScannerTests" */; 279 | buildPhases = ( 280 | OBJ_61 /* Sources */, 281 | OBJ_64 /* Frameworks */, 282 | ); 283 | buildRules = ( 284 | ); 285 | dependencies = ( 286 | OBJ_67 /* PBXTargetDependency */, 287 | OBJ_68 /* PBXTargetDependency */, 288 | ); 289 | name = SweetCardScannerTests; 290 | productName = SweetCardScannerTests; 291 | productReference = "SweetCardScanner::SweetCardScannerTests::Product" /* SweetCardScannerTests.xctest */; 292 | productType = "com.apple.product-type.bundle.unit-test"; 293 | }; 294 | "SweetCardScanner::SwiftPMPackageDescription" /* SweetCardScannerPackageDescription */ = { 295 | isa = PBXNativeTarget; 296 | buildConfigurationList = OBJ_47 /* Build configuration list for PBXNativeTarget "SweetCardScannerPackageDescription" */; 297 | buildPhases = ( 298 | OBJ_50 /* Sources */, 299 | ); 300 | buildRules = ( 301 | ); 302 | dependencies = ( 303 | ); 304 | name = SweetCardScannerPackageDescription; 305 | productName = SweetCardScannerPackageDescription; 306 | productType = "com.apple.product-type.framework"; 307 | }; 308 | /* End PBXNativeTarget section */ 309 | 310 | /* Begin PBXProject section */ 311 | OBJ_1 /* Project object */ = { 312 | isa = PBXProject; 313 | attributes = { 314 | LastSwiftMigration = 9999; 315 | LastUpgradeCheck = 9999; 316 | }; 317 | buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "SweetCardScanner" */; 318 | compatibilityVersion = "Xcode 3.2"; 319 | developmentRegion = en; 320 | hasScannedForEncodings = 0; 321 | knownRegions = ( 322 | en, 323 | ); 324 | mainGroup = OBJ_5; 325 | productRefGroup = OBJ_19 /* Products */; 326 | projectDirPath = ""; 327 | projectRoot = ""; 328 | targets = ( 329 | "Reg::Reg" /* Reg */, 330 | "Reg::SwiftPMPackageDescription" /* RegPackageDescription */, 331 | "SweetCardScanner::SweetCardScanner" /* SweetCardScanner */, 332 | "SweetCardScanner::SwiftPMPackageDescription" /* SweetCardScannerPackageDescription */, 333 | "SweetCardScanner::SweetCardScannerPackageTests::ProductTarget" /* SweetCardScannerPackageTests */, 334 | "SweetCardScanner::SweetCardScannerTests" /* SweetCardScannerTests */, 335 | ); 336 | }; 337 | /* End PBXProject section */ 338 | 339 | /* Begin PBXSourcesBuildPhase section */ 340 | OBJ_28 /* Sources */ = { 341 | isa = PBXSourcesBuildPhase; 342 | buildActionMask = 0; 343 | files = ( 344 | OBJ_29 /* Reg.swift in Sources */, 345 | ); 346 | runOnlyForDeploymentPostprocessing = 0; 347 | }; 348 | OBJ_35 /* Sources */ = { 349 | isa = PBXSourcesBuildPhase; 350 | buildActionMask = 0; 351 | files = ( 352 | OBJ_36 /* Package.swift in Sources */, 353 | ); 354 | runOnlyForDeploymentPostprocessing = 0; 355 | }; 356 | OBJ_41 /* Sources */ = { 357 | isa = PBXSourcesBuildPhase; 358 | buildActionMask = 0; 359 | files = ( 360 | A4B5C54B255FA8C80099F695 /* CreditCardScannerError.swift in Sources */, 361 | OBJ_42 /* SweetCardScanner.swift in Sources */, 362 | A4B5C54C255FA8C80099F695 /* CreditCard.swift in Sources */, 363 | A4B5C547255FA8C80099F695 /* ImageAnalyzer.swift in Sources */, 364 | A42A65E8257532F4003FA790 /* String+Extensions.swift in Sources */, 365 | A4B5C549255FA8C80099F695 /* CreditCardScannerViewController.swift in Sources */, 366 | A4B5C54A255FA8C80099F695 /* CameraView.swift in Sources */, 367 | A42A65E0257532C6003FA790 /* CreditCardUtils.swift in Sources */, 368 | A4B5C548255FA8C80099F695 /* ImageRatio.swift in Sources */, 369 | ); 370 | runOnlyForDeploymentPostprocessing = 0; 371 | }; 372 | OBJ_50 /* Sources */ = { 373 | isa = PBXSourcesBuildPhase; 374 | buildActionMask = 0; 375 | files = ( 376 | OBJ_51 /* Package.swift in Sources */, 377 | ); 378 | runOnlyForDeploymentPostprocessing = 0; 379 | }; 380 | OBJ_61 /* Sources */ = { 381 | isa = PBXSourcesBuildPhase; 382 | buildActionMask = 0; 383 | files = ( 384 | OBJ_62 /* SweetCardScannerTests.swift in Sources */, 385 | OBJ_63 /* XCTestManifests.swift in Sources */, 386 | ); 387 | runOnlyForDeploymentPostprocessing = 0; 388 | }; 389 | /* End PBXSourcesBuildPhase section */ 390 | 391 | /* Begin PBXTargetDependency section */ 392 | OBJ_45 /* PBXTargetDependency */ = { 393 | isa = PBXTargetDependency; 394 | target = "Reg::Reg" /* Reg */; 395 | targetProxy = A4B5C52E255FA85E0099F695 /* PBXContainerItemProxy */; 396 | }; 397 | OBJ_56 /* PBXTargetDependency */ = { 398 | isa = PBXTargetDependency; 399 | target = "SweetCardScanner::SweetCardScannerTests" /* SweetCardScannerTests */; 400 | targetProxy = A4B5C535255FA85F0099F695 /* PBXContainerItemProxy */; 401 | }; 402 | OBJ_67 /* PBXTargetDependency */ = { 403 | isa = PBXTargetDependency; 404 | target = "SweetCardScanner::SweetCardScanner" /* SweetCardScanner */; 405 | targetProxy = A4B5C52F255FA85E0099F695 /* PBXContainerItemProxy */; 406 | }; 407 | OBJ_68 /* PBXTargetDependency */ = { 408 | isa = PBXTargetDependency; 409 | target = "Reg::Reg" /* Reg */; 410 | targetProxy = A4B5C530255FA85E0099F695 /* PBXContainerItemProxy */; 411 | }; 412 | /* End PBXTargetDependency section */ 413 | 414 | /* Begin XCBuildConfiguration section */ 415 | OBJ_26 /* Debug */ = { 416 | isa = XCBuildConfiguration; 417 | buildSettings = { 418 | ENABLE_TESTABILITY = YES; 419 | FRAMEWORK_SEARCH_PATHS = ( 420 | "$(inherited)", 421 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 422 | ); 423 | HEADER_SEARCH_PATHS = "$(inherited)"; 424 | INFOPLIST_FILE = SweetCardScanner.xcodeproj/Reg_Info.plist; 425 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 426 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx"; 427 | MACOSX_DEPLOYMENT_TARGET = 10.10; 428 | MARKETING_VERSION = 0.1.0; 429 | OTHER_CFLAGS = "$(inherited)"; 430 | OTHER_LDFLAGS = "$(inherited)"; 431 | OTHER_SWIFT_FLAGS = "$(inherited)"; 432 | PRODUCT_BUNDLE_IDENTIFIER = Reg; 433 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 434 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 435 | SKIP_INSTALL = YES; 436 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; 437 | SWIFT_VERSION = 5.0; 438 | TARGET_NAME = Reg; 439 | TVOS_DEPLOYMENT_TARGET = 13.0; 440 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 441 | }; 442 | name = Debug; 443 | }; 444 | OBJ_27 /* Release */ = { 445 | isa = XCBuildConfiguration; 446 | buildSettings = { 447 | ENABLE_TESTABILITY = YES; 448 | FRAMEWORK_SEARCH_PATHS = ( 449 | "$(inherited)", 450 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 451 | ); 452 | HEADER_SEARCH_PATHS = "$(inherited)"; 453 | INFOPLIST_FILE = SweetCardScanner.xcodeproj/Reg_Info.plist; 454 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 455 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx"; 456 | MACOSX_DEPLOYMENT_TARGET = 10.10; 457 | MARKETING_VERSION = 0.1.0; 458 | OTHER_CFLAGS = "$(inherited)"; 459 | OTHER_LDFLAGS = "$(inherited)"; 460 | OTHER_SWIFT_FLAGS = "$(inherited)"; 461 | PRODUCT_BUNDLE_IDENTIFIER = Reg; 462 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 463 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 464 | SKIP_INSTALL = YES; 465 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; 466 | SWIFT_VERSION = 5.0; 467 | TARGET_NAME = Reg; 468 | TVOS_DEPLOYMENT_TARGET = 13.0; 469 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 470 | }; 471 | name = Release; 472 | }; 473 | OBJ_3 /* Debug */ = { 474 | isa = XCBuildConfiguration; 475 | buildSettings = { 476 | CLANG_ENABLE_OBJC_ARC = YES; 477 | COMBINE_HIDPI_IMAGES = YES; 478 | COPY_PHASE_STRIP = NO; 479 | DEBUG_INFORMATION_FORMAT = dwarf; 480 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 481 | ENABLE_NS_ASSERTIONS = YES; 482 | GCC_OPTIMIZATION_LEVEL = 0; 483 | GCC_PREPROCESSOR_DEFINITIONS = ( 484 | "$(inherited)", 485 | "SWIFT_PACKAGE=1", 486 | "DEBUG=1", 487 | ); 488 | MACOSX_DEPLOYMENT_TARGET = 10.10; 489 | ONLY_ACTIVE_ARCH = YES; 490 | OTHER_SWIFT_FLAGS = "$(inherited) -DXcode"; 491 | PRODUCT_NAME = "$(TARGET_NAME)"; 492 | SDKROOT = macosx; 493 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator"; 494 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SWIFT_PACKAGE DEBUG"; 495 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 496 | USE_HEADERMAP = NO; 497 | }; 498 | name = Debug; 499 | }; 500 | OBJ_33 /* Debug */ = { 501 | isa = XCBuildConfiguration; 502 | buildSettings = { 503 | LD = /usr/bin/true; 504 | OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk -package-description-version 5.2.0"; 505 | SWIFT_VERSION = 5.0; 506 | }; 507 | name = Debug; 508 | }; 509 | OBJ_34 /* Release */ = { 510 | isa = XCBuildConfiguration; 511 | buildSettings = { 512 | LD = /usr/bin/true; 513 | OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk -package-description-version 5.2.0"; 514 | SWIFT_VERSION = 5.0; 515 | }; 516 | name = Release; 517 | }; 518 | OBJ_39 /* Debug */ = { 519 | isa = XCBuildConfiguration; 520 | buildSettings = { 521 | ENABLE_TESTABILITY = YES; 522 | FRAMEWORK_SEARCH_PATHS = ( 523 | "$(inherited)", 524 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 525 | ); 526 | HEADER_SEARCH_PATHS = "$(inherited)"; 527 | INFOPLIST_FILE = SweetCardScanner.xcodeproj/SweetCardScanner_Info.plist; 528 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 529 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx"; 530 | MACOSX_DEPLOYMENT_TARGET = 10.10; 531 | OTHER_CFLAGS = "$(inherited)"; 532 | OTHER_LDFLAGS = "$(inherited)"; 533 | OTHER_SWIFT_FLAGS = "$(inherited)"; 534 | PRODUCT_BUNDLE_IDENTIFIER = SweetCardScanner; 535 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 536 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 537 | SKIP_INSTALL = YES; 538 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; 539 | SWIFT_VERSION = 5.0; 540 | TARGET_NAME = SweetCardScanner; 541 | TVOS_DEPLOYMENT_TARGET = 9.0; 542 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 543 | }; 544 | name = Debug; 545 | }; 546 | OBJ_4 /* Release */ = { 547 | isa = XCBuildConfiguration; 548 | buildSettings = { 549 | CLANG_ENABLE_OBJC_ARC = YES; 550 | COMBINE_HIDPI_IMAGES = YES; 551 | COPY_PHASE_STRIP = YES; 552 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 553 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 554 | GCC_OPTIMIZATION_LEVEL = s; 555 | GCC_PREPROCESSOR_DEFINITIONS = ( 556 | "$(inherited)", 557 | "SWIFT_PACKAGE=1", 558 | ); 559 | MACOSX_DEPLOYMENT_TARGET = 10.10; 560 | OTHER_SWIFT_FLAGS = "$(inherited) -DXcode"; 561 | PRODUCT_NAME = "$(TARGET_NAME)"; 562 | SDKROOT = macosx; 563 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator"; 564 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SWIFT_PACKAGE"; 565 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 566 | USE_HEADERMAP = NO; 567 | }; 568 | name = Release; 569 | }; 570 | OBJ_40 /* Release */ = { 571 | isa = XCBuildConfiguration; 572 | buildSettings = { 573 | ENABLE_TESTABILITY = YES; 574 | FRAMEWORK_SEARCH_PATHS = ( 575 | "$(inherited)", 576 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 577 | ); 578 | HEADER_SEARCH_PATHS = "$(inherited)"; 579 | INFOPLIST_FILE = SweetCardScanner.xcodeproj/SweetCardScanner_Info.plist; 580 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 581 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx"; 582 | MACOSX_DEPLOYMENT_TARGET = 10.10; 583 | OTHER_CFLAGS = "$(inherited)"; 584 | OTHER_LDFLAGS = "$(inherited)"; 585 | OTHER_SWIFT_FLAGS = "$(inherited)"; 586 | PRODUCT_BUNDLE_IDENTIFIER = SweetCardScanner; 587 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 588 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 589 | SKIP_INSTALL = YES; 590 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; 591 | SWIFT_VERSION = 5.0; 592 | TARGET_NAME = SweetCardScanner; 593 | TVOS_DEPLOYMENT_TARGET = 9.0; 594 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 595 | }; 596 | name = Release; 597 | }; 598 | OBJ_48 /* Debug */ = { 599 | isa = XCBuildConfiguration; 600 | buildSettings = { 601 | LD = /usr/bin/true; 602 | OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk -package-description-version 5.3.0"; 603 | SWIFT_VERSION = 5.0; 604 | }; 605 | name = Debug; 606 | }; 607 | OBJ_49 /* Release */ = { 608 | isa = XCBuildConfiguration; 609 | buildSettings = { 610 | LD = /usr/bin/true; 611 | OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk -package-description-version 5.3.0"; 612 | SWIFT_VERSION = 5.0; 613 | }; 614 | name = Release; 615 | }; 616 | OBJ_54 /* Debug */ = { 617 | isa = XCBuildConfiguration; 618 | buildSettings = { 619 | }; 620 | name = Debug; 621 | }; 622 | OBJ_55 /* Release */ = { 623 | isa = XCBuildConfiguration; 624 | buildSettings = { 625 | }; 626 | name = Release; 627 | }; 628 | OBJ_59 /* Debug */ = { 629 | isa = XCBuildConfiguration; 630 | buildSettings = { 631 | CLANG_ENABLE_MODULES = YES; 632 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 633 | FRAMEWORK_SEARCH_PATHS = ( 634 | "$(inherited)", 635 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 636 | ); 637 | HEADER_SEARCH_PATHS = "$(inherited)"; 638 | INFOPLIST_FILE = SweetCardScanner.xcodeproj/SweetCardScannerTests_Info.plist; 639 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 640 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @loader_path/../Frameworks @loader_path/Frameworks"; 641 | MACOSX_DEPLOYMENT_TARGET = 10.15; 642 | OTHER_CFLAGS = "$(inherited)"; 643 | OTHER_LDFLAGS = "$(inherited)"; 644 | OTHER_SWIFT_FLAGS = "$(inherited)"; 645 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; 646 | SWIFT_VERSION = 5.0; 647 | TARGET_NAME = SweetCardScannerTests; 648 | TVOS_DEPLOYMENT_TARGET = 9.0; 649 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 650 | }; 651 | name = Debug; 652 | }; 653 | OBJ_60 /* Release */ = { 654 | isa = XCBuildConfiguration; 655 | buildSettings = { 656 | CLANG_ENABLE_MODULES = YES; 657 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 658 | FRAMEWORK_SEARCH_PATHS = ( 659 | "$(inherited)", 660 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 661 | ); 662 | HEADER_SEARCH_PATHS = "$(inherited)"; 663 | INFOPLIST_FILE = SweetCardScanner.xcodeproj/SweetCardScannerTests_Info.plist; 664 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 665 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @loader_path/../Frameworks @loader_path/Frameworks"; 666 | MACOSX_DEPLOYMENT_TARGET = 10.15; 667 | OTHER_CFLAGS = "$(inherited)"; 668 | OTHER_LDFLAGS = "$(inherited)"; 669 | OTHER_SWIFT_FLAGS = "$(inherited)"; 670 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; 671 | SWIFT_VERSION = 5.0; 672 | TARGET_NAME = SweetCardScannerTests; 673 | TVOS_DEPLOYMENT_TARGET = 9.0; 674 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 675 | }; 676 | name = Release; 677 | }; 678 | /* End XCBuildConfiguration section */ 679 | 680 | /* Begin XCConfigurationList section */ 681 | OBJ_2 /* Build configuration list for PBXProject "SweetCardScanner" */ = { 682 | isa = XCConfigurationList; 683 | buildConfigurations = ( 684 | OBJ_3 /* Debug */, 685 | OBJ_4 /* Release */, 686 | ); 687 | defaultConfigurationIsVisible = 0; 688 | defaultConfigurationName = Release; 689 | }; 690 | OBJ_25 /* Build configuration list for PBXNativeTarget "Reg" */ = { 691 | isa = XCConfigurationList; 692 | buildConfigurations = ( 693 | OBJ_26 /* Debug */, 694 | OBJ_27 /* Release */, 695 | ); 696 | defaultConfigurationIsVisible = 0; 697 | defaultConfigurationName = Release; 698 | }; 699 | OBJ_32 /* Build configuration list for PBXNativeTarget "RegPackageDescription" */ = { 700 | isa = XCConfigurationList; 701 | buildConfigurations = ( 702 | OBJ_33 /* Debug */, 703 | OBJ_34 /* Release */, 704 | ); 705 | defaultConfigurationIsVisible = 0; 706 | defaultConfigurationName = Release; 707 | }; 708 | OBJ_38 /* Build configuration list for PBXNativeTarget "SweetCardScanner" */ = { 709 | isa = XCConfigurationList; 710 | buildConfigurations = ( 711 | OBJ_39 /* Debug */, 712 | OBJ_40 /* Release */, 713 | ); 714 | defaultConfigurationIsVisible = 0; 715 | defaultConfigurationName = Release; 716 | }; 717 | OBJ_47 /* Build configuration list for PBXNativeTarget "SweetCardScannerPackageDescription" */ = { 718 | isa = XCConfigurationList; 719 | buildConfigurations = ( 720 | OBJ_48 /* Debug */, 721 | OBJ_49 /* Release */, 722 | ); 723 | defaultConfigurationIsVisible = 0; 724 | defaultConfigurationName = Release; 725 | }; 726 | OBJ_53 /* Build configuration list for PBXAggregateTarget "SweetCardScannerPackageTests" */ = { 727 | isa = XCConfigurationList; 728 | buildConfigurations = ( 729 | OBJ_54 /* Debug */, 730 | OBJ_55 /* Release */, 731 | ); 732 | defaultConfigurationIsVisible = 0; 733 | defaultConfigurationName = Release; 734 | }; 735 | OBJ_58 /* Build configuration list for PBXNativeTarget "SweetCardScannerTests" */ = { 736 | isa = XCConfigurationList; 737 | buildConfigurations = ( 738 | OBJ_59 /* Debug */, 739 | OBJ_60 /* Release */, 740 | ); 741 | defaultConfigurationIsVisible = 0; 742 | defaultConfigurationName = Release; 743 | }; 744 | /* End XCConfigurationList section */ 745 | }; 746 | rootObject = OBJ_1 /* Project object */; 747 | } 748 | -------------------------------------------------------------------------------- /SweetCardScanner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /SweetCardScanner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SweetCardScanner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | -------------------------------------------------------------------------------- /SweetCardScanner.xcodeproj/xcshareddata/xcschemes/SweetCardScanner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /SweetCardScanner.xcodeproj/xcshareddata/xcschemes/SweetCardScannerTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 37 | 38 | 44 | 45 | 47 | 48 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import SweetCardScannerTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += SweetCardScannerTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /Tests/SweetCardScannerTests/SweetCardScannerTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import SweetCardScanner 3 | 4 | final class SweetCardScannerTests: XCTestCase { 5 | func testExample() { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | // XCTAssertEqual(SweetCardScanner().text, "Hello, World!") 10 | } 11 | 12 | static var allTests = [ 13 | ("testExample", testExample), 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Tests/SweetCardScannerTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(SweetCardScannerTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aaronlab/SweetCardScanner/fabdaec839b23856a5f1c843086950211fe25644/preview.gif --------------------------------------------------------------------------------