├── .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 | [](https://developer.apple.com/swift)
2 | 
3 | 
4 | [](https://github.com/aaronLab/SweetCardScanner/blob/main/LICENSE)
5 | [](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
--------------------------------------------------------------------------------