├── README.md ├── SWQRCode_Swift.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist └── SWQRCode_Swift ├── AppDelegate.swift ├── Assets.xcassets └── AppIcon.appiconset │ └── Contents.json ├── Base.lproj ├── LaunchScreen.storyboard └── Main.storyboard ├── Info.plist ├── SWQRCode ├── Config │ ├── SWQRCodeConfig.swift │ └── SWQRCodeManager.swift ├── SWQRCode.bundle │ ├── Flashlight_Off@2x.png │ ├── Flashlight_Off@3x.png │ ├── Flashlight_On@2x.png │ ├── Flashlight_On@3x.png │ ├── Root.plist │ ├── ScannerLine@2x.png │ ├── ScannerLine@3x.png │ └── en.lproj │ │ └── Root.strings ├── SWQRCodeViewController.swift └── View │ └── SWScannerView.swift └── ViewController.swift /README.md: -------------------------------------------------------------------------------- 1 | # SWQRCode_Swift 2 | 3 | ![image](https://github.com/RockChanel/SWGIF/blob/master/SWQRCode/SWQRCode.gif) 4 | 5 | 简书地址:[iOS 高仿微信扫一扫](https://www.jianshu.com/p/1fc34089adf5) 6 | 7 | Objective - C版本地址:[SWQRCode oc 版本](https://github.com/RockChanel/SWQRCode_Objc) 8 | 9 | `SWQRCode_Swift` 为 `SWQRCode` swift 版本,高仿微信扫一扫功能,包括`二维码/条码识别、相册图片二维码/条码识别、手电筒功能`等,请使用真机进行测试。 10 | 11 | `SWQRCode_Swift` 舍弃了 `Timer` 而采用 `CABasicAnimation` 实现扫一扫动画效果。 12 | 13 | 若需在项目中使用,只需将 `SWQRCode` 文件夹拖入项目中,并在 `info.plist` 中添加 `Privacy - Photo Library Usage Description` 以及 `Privacy - Camera Usage Description` 配置,声明相机相册权限。 14 | 15 | `SWQRCode_Swift` 主要 API : 16 | 17 | // MARK: - 扫一扫Api 18 | extension SWQRCodeViewController { 19 | 20 | /// 处理扫一扫结果 21 | /// 22 | /// - Parameter value: 扫描结果 23 | func sw_handle(value: String) { 24 | print("sw_handle === \(value)") 25 | } 26 | 27 | /// 相册选取图片无法读取数据 28 | func sw_didReadFromAlbumFailed() { 29 | print("sw_didReadFromAlbumFailed") 30 | } 31 | } 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /SWQRCode_Swift.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | ED23D4D2207D9DEC005CC585 /* SWQRCodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED23D4D1207D9DEC005CC585 /* SWQRCodeViewController.swift */; }; 11 | ED23D4D6207D9EC4005CC585 /* SWQRCodeConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED23D4D5207D9EC4005CC585 /* SWQRCodeConfig.swift */; }; 12 | ED23D4D8207DA172005CC585 /* SWQRCodeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED23D4D7207DA172005CC585 /* SWQRCodeManager.swift */; }; 13 | ED23D4DA207DA8B2005CC585 /* SWScannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED23D4D9207DA8B2005CC585 /* SWScannerView.swift */; }; 14 | ED23D4DC207DB777005CC585 /* SWQRCode.bundle in Resources */ = {isa = PBXBuildFile; fileRef = ED23D4DB207DB777005CC585 /* SWQRCode.bundle */; }; 15 | EDA2D4FC207C930A005DF534 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDA2D4FB207C930A005DF534 /* AppDelegate.swift */; }; 16 | EDA2D4FE207C930A005DF534 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDA2D4FD207C930A005DF534 /* ViewController.swift */; }; 17 | EDA2D501207C930A005DF534 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EDA2D4FF207C930A005DF534 /* Main.storyboard */; }; 18 | EDA2D503207C930A005DF534 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EDA2D502207C930A005DF534 /* Assets.xcassets */; }; 19 | EDA2D506207C930A005DF534 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EDA2D504207C930A005DF534 /* LaunchScreen.storyboard */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXFileReference section */ 23 | ED23D4D1207D9DEC005CC585 /* SWQRCodeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SWQRCodeViewController.swift; sourceTree = ""; }; 24 | ED23D4D5207D9EC4005CC585 /* SWQRCodeConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SWQRCodeConfig.swift; sourceTree = ""; }; 25 | ED23D4D7207DA172005CC585 /* SWQRCodeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SWQRCodeManager.swift; sourceTree = ""; }; 26 | ED23D4D9207DA8B2005CC585 /* SWScannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SWScannerView.swift; sourceTree = ""; }; 27 | ED23D4DB207DB777005CC585 /* SWQRCode.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = SWQRCode.bundle; sourceTree = ""; }; 28 | EDA2D4F8207C930A005DF534 /* SWQRCode_Swift.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SWQRCode_Swift.app; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | EDA2D4FB207C930A005DF534 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 30 | EDA2D4FD207C930A005DF534 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 31 | EDA2D500207C930A005DF534 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 32 | EDA2D502207C930A005DF534 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 33 | EDA2D505207C930A005DF534 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 34 | EDA2D507207C930A005DF534 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 35 | /* End PBXFileReference section */ 36 | 37 | /* Begin PBXFrameworksBuildPhase section */ 38 | EDA2D4F5207C930A005DF534 /* Frameworks */ = { 39 | isa = PBXFrameworksBuildPhase; 40 | buildActionMask = 2147483647; 41 | files = ( 42 | ); 43 | runOnlyForDeploymentPostprocessing = 0; 44 | }; 45 | /* End PBXFrameworksBuildPhase section */ 46 | 47 | /* Begin PBXGroup section */ 48 | ED23D4D3207D9E99005CC585 /* Config */ = { 49 | isa = PBXGroup; 50 | children = ( 51 | ED23D4D5207D9EC4005CC585 /* SWQRCodeConfig.swift */, 52 | ED23D4D7207DA172005CC585 /* SWQRCodeManager.swift */, 53 | ); 54 | path = Config; 55 | sourceTree = ""; 56 | }; 57 | ED23D4D4207D9E99005CC585 /* View */ = { 58 | isa = PBXGroup; 59 | children = ( 60 | ED23D4D9207DA8B2005CC585 /* SWScannerView.swift */, 61 | ); 62 | path = View; 63 | sourceTree = ""; 64 | }; 65 | EDA2D4EF207C930A005DF534 = { 66 | isa = PBXGroup; 67 | children = ( 68 | EDA2D4FA207C930A005DF534 /* SWQRCode_Swift */, 69 | EDA2D4F9207C930A005DF534 /* Products */, 70 | ); 71 | sourceTree = ""; 72 | }; 73 | EDA2D4F9207C930A005DF534 /* Products */ = { 74 | isa = PBXGroup; 75 | children = ( 76 | EDA2D4F8207C930A005DF534 /* SWQRCode_Swift.app */, 77 | ); 78 | name = Products; 79 | sourceTree = ""; 80 | }; 81 | EDA2D4FA207C930A005DF534 /* SWQRCode_Swift */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | EDA2D50D207C9349005DF534 /* SWQRCode */, 85 | EDA2D4FB207C930A005DF534 /* AppDelegate.swift */, 86 | EDA2D4FD207C930A005DF534 /* ViewController.swift */, 87 | EDA2D4FF207C930A005DF534 /* Main.storyboard */, 88 | EDA2D502207C930A005DF534 /* Assets.xcassets */, 89 | EDA2D504207C930A005DF534 /* LaunchScreen.storyboard */, 90 | EDA2D507207C930A005DF534 /* Info.plist */, 91 | ); 92 | path = SWQRCode_Swift; 93 | sourceTree = ""; 94 | }; 95 | EDA2D50D207C9349005DF534 /* SWQRCode */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | ED23D4D1207D9DEC005CC585 /* SWQRCodeViewController.swift */, 99 | ED23D4D3207D9E99005CC585 /* Config */, 100 | ED23D4D4207D9E99005CC585 /* View */, 101 | ED23D4DB207DB777005CC585 /* SWQRCode.bundle */, 102 | ); 103 | path = SWQRCode; 104 | sourceTree = ""; 105 | }; 106 | /* End PBXGroup section */ 107 | 108 | /* Begin PBXNativeTarget section */ 109 | EDA2D4F7207C930A005DF534 /* SWQRCode_Swift */ = { 110 | isa = PBXNativeTarget; 111 | buildConfigurationList = EDA2D50A207C930A005DF534 /* Build configuration list for PBXNativeTarget "SWQRCode_Swift" */; 112 | buildPhases = ( 113 | EDA2D4F4207C930A005DF534 /* Sources */, 114 | EDA2D4F5207C930A005DF534 /* Frameworks */, 115 | EDA2D4F6207C930A005DF534 /* Resources */, 116 | ); 117 | buildRules = ( 118 | ); 119 | dependencies = ( 120 | ); 121 | name = SWQRCode_Swift; 122 | productName = SWQRCode_Swift; 123 | productReference = EDA2D4F8207C930A005DF534 /* SWQRCode_Swift.app */; 124 | productType = "com.apple.product-type.application"; 125 | }; 126 | /* End PBXNativeTarget section */ 127 | 128 | /* Begin PBXProject section */ 129 | EDA2D4F0207C930A005DF534 /* Project object */ = { 130 | isa = PBXProject; 131 | attributes = { 132 | LastSwiftUpdateCheck = 0920; 133 | LastUpgradeCheck = 0920; 134 | ORGANIZATIONNAME = selwyn; 135 | TargetAttributes = { 136 | EDA2D4F7207C930A005DF534 = { 137 | CreatedOnToolsVersion = 9.2; 138 | ProvisioningStyle = Automatic; 139 | }; 140 | }; 141 | }; 142 | buildConfigurationList = EDA2D4F3207C930A005DF534 /* Build configuration list for PBXProject "SWQRCode_Swift" */; 143 | compatibilityVersion = "Xcode 8.0"; 144 | developmentRegion = en; 145 | hasScannedForEncodings = 0; 146 | knownRegions = ( 147 | en, 148 | Base, 149 | ); 150 | mainGroup = EDA2D4EF207C930A005DF534; 151 | productRefGroup = EDA2D4F9207C930A005DF534 /* Products */; 152 | projectDirPath = ""; 153 | projectRoot = ""; 154 | targets = ( 155 | EDA2D4F7207C930A005DF534 /* SWQRCode_Swift */, 156 | ); 157 | }; 158 | /* End PBXProject section */ 159 | 160 | /* Begin PBXResourcesBuildPhase section */ 161 | EDA2D4F6207C930A005DF534 /* Resources */ = { 162 | isa = PBXResourcesBuildPhase; 163 | buildActionMask = 2147483647; 164 | files = ( 165 | EDA2D506207C930A005DF534 /* LaunchScreen.storyboard in Resources */, 166 | EDA2D503207C930A005DF534 /* Assets.xcassets in Resources */, 167 | EDA2D501207C930A005DF534 /* Main.storyboard in Resources */, 168 | ED23D4DC207DB777005CC585 /* SWQRCode.bundle in Resources */, 169 | ); 170 | runOnlyForDeploymentPostprocessing = 0; 171 | }; 172 | /* End PBXResourcesBuildPhase section */ 173 | 174 | /* Begin PBXSourcesBuildPhase section */ 175 | EDA2D4F4207C930A005DF534 /* Sources */ = { 176 | isa = PBXSourcesBuildPhase; 177 | buildActionMask = 2147483647; 178 | files = ( 179 | EDA2D4FE207C930A005DF534 /* ViewController.swift in Sources */, 180 | ED23D4D6207D9EC4005CC585 /* SWQRCodeConfig.swift in Sources */, 181 | ED23D4D8207DA172005CC585 /* SWQRCodeManager.swift in Sources */, 182 | ED23D4DA207DA8B2005CC585 /* SWScannerView.swift in Sources */, 183 | EDA2D4FC207C930A005DF534 /* AppDelegate.swift in Sources */, 184 | ED23D4D2207D9DEC005CC585 /* SWQRCodeViewController.swift in Sources */, 185 | ); 186 | runOnlyForDeploymentPostprocessing = 0; 187 | }; 188 | /* End PBXSourcesBuildPhase section */ 189 | 190 | /* Begin PBXVariantGroup section */ 191 | EDA2D4FF207C930A005DF534 /* Main.storyboard */ = { 192 | isa = PBXVariantGroup; 193 | children = ( 194 | EDA2D500207C930A005DF534 /* Base */, 195 | ); 196 | name = Main.storyboard; 197 | sourceTree = ""; 198 | }; 199 | EDA2D504207C930A005DF534 /* LaunchScreen.storyboard */ = { 200 | isa = PBXVariantGroup; 201 | children = ( 202 | EDA2D505207C930A005DF534 /* Base */, 203 | ); 204 | name = LaunchScreen.storyboard; 205 | sourceTree = ""; 206 | }; 207 | /* End PBXVariantGroup section */ 208 | 209 | /* Begin XCBuildConfiguration section */ 210 | EDA2D508207C930A005DF534 /* Debug */ = { 211 | isa = XCBuildConfiguration; 212 | buildSettings = { 213 | ALWAYS_SEARCH_USER_PATHS = NO; 214 | CLANG_ANALYZER_NONNULL = YES; 215 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 216 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 217 | CLANG_CXX_LIBRARY = "libc++"; 218 | CLANG_ENABLE_MODULES = YES; 219 | CLANG_ENABLE_OBJC_ARC = YES; 220 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 221 | CLANG_WARN_BOOL_CONVERSION = YES; 222 | CLANG_WARN_COMMA = YES; 223 | CLANG_WARN_CONSTANT_CONVERSION = YES; 224 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 225 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 226 | CLANG_WARN_EMPTY_BODY = YES; 227 | CLANG_WARN_ENUM_CONVERSION = YES; 228 | CLANG_WARN_INFINITE_RECURSION = YES; 229 | CLANG_WARN_INT_CONVERSION = YES; 230 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 231 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 232 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 233 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 234 | CLANG_WARN_STRICT_PROTOTYPES = YES; 235 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 236 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 237 | CLANG_WARN_UNREACHABLE_CODE = YES; 238 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 239 | CODE_SIGN_IDENTITY = "iPhone Developer"; 240 | COPY_PHASE_STRIP = NO; 241 | DEBUG_INFORMATION_FORMAT = dwarf; 242 | ENABLE_STRICT_OBJC_MSGSEND = YES; 243 | ENABLE_TESTABILITY = YES; 244 | GCC_C_LANGUAGE_STANDARD = gnu11; 245 | GCC_DYNAMIC_NO_PIC = NO; 246 | GCC_NO_COMMON_BLOCKS = YES; 247 | GCC_OPTIMIZATION_LEVEL = 0; 248 | GCC_PREPROCESSOR_DEFINITIONS = ( 249 | "DEBUG=1", 250 | "$(inherited)", 251 | ); 252 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 253 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 254 | GCC_WARN_UNDECLARED_SELECTOR = YES; 255 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 256 | GCC_WARN_UNUSED_FUNCTION = YES; 257 | GCC_WARN_UNUSED_VARIABLE = YES; 258 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 259 | MTL_ENABLE_DEBUG_INFO = YES; 260 | ONLY_ACTIVE_ARCH = YES; 261 | SDKROOT = iphoneos; 262 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 263 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 264 | }; 265 | name = Debug; 266 | }; 267 | EDA2D509207C930A005DF534 /* Release */ = { 268 | isa = XCBuildConfiguration; 269 | buildSettings = { 270 | ALWAYS_SEARCH_USER_PATHS = NO; 271 | CLANG_ANALYZER_NONNULL = YES; 272 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 273 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 274 | CLANG_CXX_LIBRARY = "libc++"; 275 | CLANG_ENABLE_MODULES = YES; 276 | CLANG_ENABLE_OBJC_ARC = YES; 277 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 278 | CLANG_WARN_BOOL_CONVERSION = YES; 279 | CLANG_WARN_COMMA = YES; 280 | CLANG_WARN_CONSTANT_CONVERSION = YES; 281 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 282 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 283 | CLANG_WARN_EMPTY_BODY = YES; 284 | CLANG_WARN_ENUM_CONVERSION = YES; 285 | CLANG_WARN_INFINITE_RECURSION = YES; 286 | CLANG_WARN_INT_CONVERSION = YES; 287 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 288 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 289 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 290 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 291 | CLANG_WARN_STRICT_PROTOTYPES = YES; 292 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 293 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 294 | CLANG_WARN_UNREACHABLE_CODE = YES; 295 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 296 | CODE_SIGN_IDENTITY = "iPhone Developer"; 297 | COPY_PHASE_STRIP = NO; 298 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 299 | ENABLE_NS_ASSERTIONS = NO; 300 | ENABLE_STRICT_OBJC_MSGSEND = YES; 301 | GCC_C_LANGUAGE_STANDARD = gnu11; 302 | GCC_NO_COMMON_BLOCKS = YES; 303 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 304 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 305 | GCC_WARN_UNDECLARED_SELECTOR = YES; 306 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 307 | GCC_WARN_UNUSED_FUNCTION = YES; 308 | GCC_WARN_UNUSED_VARIABLE = YES; 309 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 310 | MTL_ENABLE_DEBUG_INFO = NO; 311 | SDKROOT = iphoneos; 312 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 313 | VALIDATE_PRODUCT = YES; 314 | }; 315 | name = Release; 316 | }; 317 | EDA2D50B207C930A005DF534 /* Debug */ = { 318 | isa = XCBuildConfiguration; 319 | buildSettings = { 320 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 321 | CODE_SIGN_STYLE = Automatic; 322 | DEVELOPMENT_TEAM = 2WJ4BEE5HT; 323 | INFOPLIST_FILE = SWQRCode_Swift/Info.plist; 324 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 325 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 326 | PRODUCT_BUNDLE_IDENTIFIER = "selwynbee.SWQRCode-Swift"; 327 | PRODUCT_NAME = "$(TARGET_NAME)"; 328 | SWIFT_VERSION = 4.0; 329 | TARGETED_DEVICE_FAMILY = "1,2"; 330 | }; 331 | name = Debug; 332 | }; 333 | EDA2D50C207C930A005DF534 /* Release */ = { 334 | isa = XCBuildConfiguration; 335 | buildSettings = { 336 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 337 | CODE_SIGN_STYLE = Automatic; 338 | DEVELOPMENT_TEAM = 2WJ4BEE5HT; 339 | INFOPLIST_FILE = SWQRCode_Swift/Info.plist; 340 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 341 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 342 | PRODUCT_BUNDLE_IDENTIFIER = "selwynbee.SWQRCode-Swift"; 343 | PRODUCT_NAME = "$(TARGET_NAME)"; 344 | SWIFT_VERSION = 4.0; 345 | TARGETED_DEVICE_FAMILY = "1,2"; 346 | }; 347 | name = Release; 348 | }; 349 | /* End XCBuildConfiguration section */ 350 | 351 | /* Begin XCConfigurationList section */ 352 | EDA2D4F3207C930A005DF534 /* Build configuration list for PBXProject "SWQRCode_Swift" */ = { 353 | isa = XCConfigurationList; 354 | buildConfigurations = ( 355 | EDA2D508207C930A005DF534 /* Debug */, 356 | EDA2D509207C930A005DF534 /* Release */, 357 | ); 358 | defaultConfigurationIsVisible = 0; 359 | defaultConfigurationName = Release; 360 | }; 361 | EDA2D50A207C930A005DF534 /* Build configuration list for PBXNativeTarget "SWQRCode_Swift" */ = { 362 | isa = XCConfigurationList; 363 | buildConfigurations = ( 364 | EDA2D50B207C930A005DF534 /* Debug */, 365 | EDA2D50C207C930A005DF534 /* Release */, 366 | ); 367 | defaultConfigurationIsVisible = 0; 368 | defaultConfigurationName = Release; 369 | }; 370 | /* End XCConfigurationList section */ 371 | }; 372 | rootObject = EDA2D4F0207C930A005DF534 /* Project object */; 373 | } 374 | -------------------------------------------------------------------------------- /SWQRCode_Swift.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SWQRCode_Swift.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SWQRCode_Swift/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SWQRCode_Swift 4 | // 5 | // Created by zhuku on 2018/4/10. 6 | // Copyright © 2018年 selwyn. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /SWQRCode_Swift/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /SWQRCode_Swift/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /SWQRCode_Swift/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /SWQRCode_Swift/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPhotoLibraryUsageDescription 6 | 需要使用相册 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 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSCameraUsageDescription 26 | 需要使用相机 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UIRequiresFullScreen 36 | 37 | UISupportedInterfaceOrientations 38 | 39 | UIInterfaceOrientationPortrait 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /SWQRCode_Swift/SWQRCode/Config/SWQRCodeConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SWQRCodeConfig.swift 3 | // SWQRCode_Swift 4 | // 5 | // Created by zhuku on 2018/4/11. 6 | // Copyright © 2018年 selwyn. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// 扫描器类型 12 | /// 13 | /// - qr: 仅支持二维码 14 | /// - bar: 仅支持条码 15 | /// - both: 支持二维码以及条码 16 | enum SWScannerType { 17 | case qr 18 | case bar 19 | case both 20 | } 21 | 22 | /// 扫描区域 23 | /// 24 | /// - def: 扫描框内 25 | /// - fullscreen: 全屏 26 | enum SWScannerArea { 27 | case def 28 | case fullscreen 29 | } 30 | 31 | struct SWQRCodeCompat { 32 | /// 扫描器类型 默认支持二维码以及条码 33 | var scannerType: SWScannerType = .both 34 | /// 扫描区域 35 | var scannerArea: SWScannerArea = .def 36 | 37 | /// 棱角颜色 默认RGB色值 r:63 g:187 b:54 a:1.0 38 | var scannerCornerColor: UIColor = UIColor(red: 63/255.0, green: 187/255.0, blue: 54/255.0, alpha: 1.0) 39 | 40 | /// 边框颜色 默认白色 41 | var scannerBorderColor: UIColor = .white 42 | 43 | /// 指示器风格 44 | var indicatorViewStyle: UIActivityIndicatorViewStyle = .whiteLarge 45 | } 46 | -------------------------------------------------------------------------------- /SWQRCode_Swift/SWQRCode/Config/SWQRCodeManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SWQRCodeManager.swift 3 | // SWQRCode_Swift 4 | // 5 | // Created by zhuku on 2018/4/11. 6 | // Copyright © 2018年 selwyn. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | struct SWQRCodeHelper { 13 | 14 | /** 校验是否有相机权限 */ 15 | static func sw_checkCamera(completion: @escaping (_ granted: Bool) -> Void) { 16 | let videoAuthStatus = AVCaptureDevice.authorizationStatus(for: .video) 17 | 18 | switch videoAuthStatus { 19 | // 已授权 20 | case .authorized: 21 | completion(true) 22 | // 未询问用户是否授权 23 | case .notDetermined: 24 | AVCaptureDevice.requestAccess(for: .video, completionHandler: { (granted) in 25 | completion(granted) 26 | }) 27 | // 用户拒绝授权或权限受限 28 | case .denied, .restricted: 29 | let alter = UIAlertView(title: "请在”设置-隐私-相机”选项中,允许访问你的相机", message: nil, delegate: nil, cancelButtonTitle: "确定") 30 | alter.show() 31 | completion(false) 32 | } 33 | } 34 | 35 | /** 校验是否有相册权限 */ 36 | static func sw_checkAlbum(completion: @escaping (_ granted: Bool) -> Void) { 37 | let photoAuthStatus = PHPhotoLibrary.authorizationStatus() 38 | 39 | switch photoAuthStatus { 40 | // 已授权 41 | case .authorized: 42 | completion(true) 43 | // 未询问用户是否授权 44 | case .notDetermined: 45 | PHPhotoLibrary.requestAuthorization({ (status) in 46 | completion(status == .authorized) 47 | }) 48 | // 用户拒绝授权或权限受限 49 | case .denied, .restricted: 50 | let alter = UIAlertView(title: "请在”设置-隐私-相片”选项中,允许访问你的相册", message: nil, delegate: nil, cancelButtonTitle: "确定") 51 | alter.show() 52 | completion(false) 53 | } 54 | } 55 | 56 | /** 根据扫描器类型配置支持编码格式 */ 57 | static func sw_metadataObjectTypes(type: SWScannerType) -> [AVMetadataObject.ObjectType] { 58 | switch type { 59 | case .qr: 60 | return [.qr] 61 | case .bar: 62 | return [.ean13, .ean8, .upce, .code39, .code39Mod43, .code93, .code128, .pdf417] 63 | case .both: 64 | return [.qr, .ean13, .ean8, .upce, .code39, .code39Mod43, .code93, .code128, .pdf417] 65 | } 66 | } 67 | 68 | /** 根据扫描器类型配置导航栏标题 */ 69 | static func sw_navigationItemTitle(type: SWScannerType) -> String { 70 | switch type { 71 | case .qr: 72 | return "二维码" 73 | case .bar: 74 | return "条码" 75 | case .both: 76 | return "二维码/条码" 77 | } 78 | } 79 | 80 | /** 手电筒开关 */ 81 | static func sw_flashlight(on: Bool) { 82 | guard let device = AVCaptureDevice.default(for: .video) else { 83 | return 84 | } 85 | if device.hasFlash && device.hasTorch { 86 | try? device.lockForConfiguration() 87 | device.torchMode = on ? .on:.off 88 | device.flashMode = on ? .on:.off 89 | device.unlockForConfiguration() 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /SWQRCode_Swift/SWQRCode/SWQRCode.bundle/Flashlight_Off@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RockChanel/SWQRCode_Swift/ebeec5661204f69b106db414a14fb91c92f880bc/SWQRCode_Swift/SWQRCode/SWQRCode.bundle/Flashlight_Off@2x.png -------------------------------------------------------------------------------- /SWQRCode_Swift/SWQRCode/SWQRCode.bundle/Flashlight_Off@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RockChanel/SWQRCode_Swift/ebeec5661204f69b106db414a14fb91c92f880bc/SWQRCode_Swift/SWQRCode/SWQRCode.bundle/Flashlight_Off@3x.png -------------------------------------------------------------------------------- /SWQRCode_Swift/SWQRCode/SWQRCode.bundle/Flashlight_On@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RockChanel/SWQRCode_Swift/ebeec5661204f69b106db414a14fb91c92f880bc/SWQRCode_Swift/SWQRCode/SWQRCode.bundle/Flashlight_On@2x.png -------------------------------------------------------------------------------- /SWQRCode_Swift/SWQRCode/SWQRCode.bundle/Flashlight_On@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RockChanel/SWQRCode_Swift/ebeec5661204f69b106db414a14fb91c92f880bc/SWQRCode_Swift/SWQRCode/SWQRCode.bundle/Flashlight_On@3x.png -------------------------------------------------------------------------------- /SWQRCode_Swift/SWQRCode/SWQRCode.bundle/Root.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | StringsTable 6 | Root 7 | PreferenceSpecifiers 8 | 9 | 10 | Type 11 | PSGroupSpecifier 12 | Title 13 | Group 14 | 15 | 16 | Type 17 | PSTextFieldSpecifier 18 | Title 19 | Name 20 | Key 21 | name_preference 22 | DefaultValue 23 | 24 | IsSecure 25 | 26 | KeyboardType 27 | Alphabet 28 | AutocapitalizationType 29 | None 30 | AutocorrectionType 31 | No 32 | 33 | 34 | Type 35 | PSToggleSwitchSpecifier 36 | Title 37 | Enabled 38 | Key 39 | enabled_preference 40 | DefaultValue 41 | 42 | 43 | 44 | Type 45 | PSSliderSpecifier 46 | Key 47 | slider_preference 48 | DefaultValue 49 | 0.5 50 | MinimumValue 51 | 0 52 | MaximumValue 53 | 1 54 | MinimumValueImage 55 | 56 | MaximumValueImage 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /SWQRCode_Swift/SWQRCode/SWQRCode.bundle/ScannerLine@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RockChanel/SWQRCode_Swift/ebeec5661204f69b106db414a14fb91c92f880bc/SWQRCode_Swift/SWQRCode/SWQRCode.bundle/ScannerLine@2x.png -------------------------------------------------------------------------------- /SWQRCode_Swift/SWQRCode/SWQRCode.bundle/ScannerLine@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RockChanel/SWQRCode_Swift/ebeec5661204f69b106db414a14fb91c92f880bc/SWQRCode_Swift/SWQRCode/SWQRCode.bundle/ScannerLine@3x.png -------------------------------------------------------------------------------- /SWQRCode_Swift/SWQRCode/SWQRCode.bundle/en.lproj/Root.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RockChanel/SWQRCode_Swift/ebeec5661204f69b106db414a14fb91c92f880bc/SWQRCode_Swift/SWQRCode/SWQRCode.bundle/en.lproj/Root.strings -------------------------------------------------------------------------------- /SWQRCode_Swift/SWQRCode/SWQRCodeViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SWQRCodeViewController.swift 3 | // SWQRCode_Swift 4 | // 5 | // Created by zhuku on 2018/4/11. 6 | // Copyright © 2018年 selwyn. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | 12 | class SWQRCodeViewController: UIViewController { 13 | 14 | var config = SWQRCodeCompat() 15 | private let session = AVCaptureSession() 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | navigationItem.title = SWQRCodeHelper.sw_navigationItemTitle(type: self.config.scannerType) 21 | 22 | NotificationCenter.default.addObserver(self, selector: #selector(appDidBecomeActive), name: .UIApplicationDidBecomeActive, object: nil) 23 | 24 | NotificationCenter.default.addObserver(self, selector: #selector(appWillResignActive), name: .UIApplicationWillResignActive, object: nil) 25 | 26 | _setupUI(); 27 | } 28 | 29 | override func viewWillAppear(_ animated: Bool) { 30 | super.viewWillAppear(animated) 31 | _resumeScanning() 32 | } 33 | 34 | override func viewWillDisappear(_ animated: Bool) { 35 | super.viewWillDisappear(animated) 36 | 37 | // 关闭并隐藏手电筒 38 | scannerView.sw_setFlashlight(on: false) 39 | scannerView.sw_hideFlashlight(animated: true) 40 | } 41 | 42 | private func _setupUI() { 43 | view.backgroundColor = .black 44 | 45 | let albumItem = UIBarButtonItem(title: "相册", style: .plain, target: self, action: #selector(showAlbum)) 46 | albumItem.tintColor = .black 47 | navigationItem.rightBarButtonItem = albumItem; 48 | 49 | view.addSubview(scannerView) 50 | 51 | // 校验相机权限 52 | SWQRCodeHelper.sw_checkCamera { (granted) in 53 | if granted { 54 | DispatchQueue.main.async { 55 | self._setupScanner() 56 | } 57 | } 58 | } 59 | } 60 | 61 | /** 创建扫描器 */ 62 | private func _setupScanner() { 63 | 64 | guard let device = AVCaptureDevice.default(for: .video) else { 65 | return 66 | } 67 | if let deviceInput = try? AVCaptureDeviceInput(device: device) { 68 | let metadataOutput = AVCaptureMetadataOutput() 69 | metadataOutput.setMetadataObjectsDelegate(self, queue: .main) 70 | 71 | if config.scannerArea == .def { 72 | metadataOutput.rectOfInterest = CGRect(x: scannerView.scanner_y/view.frame.size.height, y: scannerView.scanner_x/view.frame.size.width, width: scannerView.scanner_width/view.frame.size.height, height: scannerView.scanner_width/view.frame.size.width) 73 | } 74 | 75 | let videoDataOutput = AVCaptureVideoDataOutput() 76 | videoDataOutput.setSampleBufferDelegate(self, queue: .main) 77 | 78 | session.canSetSessionPreset(.high) 79 | if session.canAddInput(deviceInput) { session.addInput(deviceInput) } 80 | if session.canAddOutput(metadataOutput) { session.addOutput(metadataOutput) } 81 | if session.canAddOutput(videoDataOutput) { session.addOutput(videoDataOutput) } 82 | 83 | metadataOutput.metadataObjectTypes = SWQRCodeHelper.sw_metadataObjectTypes(type: config.scannerType) 84 | 85 | let videoPreviewLayer = AVCaptureVideoPreviewLayer(session: session) 86 | videoPreviewLayer.videoGravity = .resizeAspectFill 87 | videoPreviewLayer.frame = view.layer.bounds 88 | view.layer.insertSublayer(videoPreviewLayer, at: 0) 89 | 90 | session.startRunning() 91 | } 92 | } 93 | 94 | @objc func showAlbum() { 95 | SWQRCodeHelper.sw_checkAlbum { (granted) in 96 | if granted { 97 | self.imagePicker() 98 | } 99 | } 100 | } 101 | 102 | // MARK: - 跳转相册 103 | private func imagePicker() { 104 | let imagePicker = UIImagePickerController() 105 | imagePicker.sourceType = .photoLibrary 106 | imagePicker.delegate = self 107 | present(imagePicker, animated: true, completion: nil) 108 | } 109 | 110 | /// 从后台进入前台 111 | @objc func appDidBecomeActive() { 112 | _resumeScanning() 113 | } 114 | 115 | /// 从前台进入后台 116 | @objc func appWillResignActive() { 117 | _pauseScanning() 118 | } 119 | 120 | lazy var scannerView:SWScannerView = { 121 | let tempScannerView = SWScannerView(frame: view.bounds, config: config) 122 | return tempScannerView 123 | }() 124 | 125 | override func didReceiveMemoryWarning() { 126 | super.didReceiveMemoryWarning() 127 | } 128 | } 129 | 130 | // MARK: - 扫一扫Api 131 | extension SWQRCodeViewController { 132 | 133 | /// 处理扫一扫结果 134 | /// 135 | /// - Parameter value: 扫描结果 136 | func sw_handle(value: String) { 137 | print("sw_handle === \(value)") 138 | } 139 | 140 | /// 相册选取图片无法读取数据 141 | func sw_didReadFromAlbumFailed() { 142 | print("sw_didReadFromAlbumFailed") 143 | } 144 | } 145 | 146 | // MARK: - 扫描结果处理 147 | extension SWQRCodeViewController: AVCaptureMetadataOutputObjectsDelegate { 148 | 149 | func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { 150 | 151 | if metadataObjects.count > 0 { 152 | _pauseScanning() 153 | 154 | if let metadataObject = metadataObjects[0] as? AVMetadataMachineReadableCodeObject { 155 | if let stringValue = metadataObject.stringValue { 156 | sw_handle(value: stringValue) 157 | } 158 | } 159 | } 160 | } 161 | } 162 | 163 | // MARK: - 监听光线亮度 164 | extension SWQRCodeViewController: AVCaptureVideoDataOutputSampleBufferDelegate { 165 | 166 | func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { 167 | let metadataDict = CMCopyDictionaryOfAttachments(nil, sampleBuffer, kCMAttachmentMode_ShouldPropagate) 168 | 169 | if let metadata = metadataDict as? [AnyHashable: Any] { 170 | if let exifMetadata = metadata[kCGImagePropertyExifDictionary as String] as? [AnyHashable: Any] { 171 | if let brightness = exifMetadata[kCGImagePropertyExifBrightnessValue as String] as? NSNumber { 172 | // 亮度值 173 | let brightnessValue = brightness.floatValue 174 | if !scannerView.sw_setFlashlightOn() { 175 | if brightnessValue < -4.0 { 176 | scannerView.sw_showFlashlight(animated: true) 177 | } 178 | else { 179 | scannerView.sw_hideFlashlight(animated: true) 180 | } 181 | } 182 | } 183 | } 184 | } 185 | } 186 | } 187 | 188 | // MARK: - 识别选择图片 189 | extension SWQRCodeViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { 190 | 191 | func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) { 192 | picker.dismiss(animated: true) { 193 | if !self.handlePickInfo(info) { 194 | self.sw_didReadFromAlbumFailed() 195 | } 196 | } 197 | } 198 | 199 | /// 识别二维码并返回识别结果 200 | private func handlePickInfo(_ info: [String : Any]) -> Bool { 201 | if let pickImage = info[UIImagePickerControllerOriginalImage] as? UIImage { 202 | let ciImage = CIImage(cgImage: pickImage.cgImage!) 203 | let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: nil, options: [CIDetectorAccuracy:CIDetectorAccuracyHigh]) 204 | 205 | if let features = detector?.features(in: ciImage), 206 | let firstFeature = features.first as? CIQRCodeFeature{ 207 | 208 | if let stringValue = firstFeature.messageString { 209 | sw_handle(value: stringValue) 210 | return true 211 | } 212 | return false 213 | } 214 | } 215 | return false 216 | } 217 | } 218 | 219 | // MARK: - 恢复/暂停扫一扫功能 220 | extension SWQRCodeViewController { 221 | 222 | /// 恢复扫一扫功能 223 | private func _resumeScanning() { 224 | session.startRunning() 225 | scannerView.sw_addScannerLineAnimation() 226 | } 227 | 228 | /// 暂停扫一扫功能 229 | private func _pauseScanning() { 230 | session.stopRunning() 231 | scannerView.sw_pauseScannerLineAnimation() 232 | } 233 | } 234 | 235 | -------------------------------------------------------------------------------- /SWQRCode_Swift/SWQRCode/View/SWScannerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SWScannerView.swift 3 | // SWQRCode_Swift 4 | // 5 | // Created by zhuku on 2018/4/11. 6 | // Copyright © 2018年 selwyn. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | private let scanner_borderWidth: CGFloat = 1.0 /** 扫描器边框宽度 */ 12 | private let scanner_cornerWidth: CGFloat = 3.0 /** 扫描器棱角宽度 */ 13 | private let scanner_cornerLength: CGFloat = 20.0 /** 扫描器棱角长度 */ 14 | private let scanner_lineHeight: CGFloat = 10.0 /** 扫描器线条高度 */ 15 | private let flashlightBtn_width: CGFloat = 20.0 /** 手电筒按钮宽度 */ 16 | private let flashlightLab_height: CGFloat = 15.0 /** 手电筒提示文字高度 */ 17 | private let tipLab_height: CGFloat = 50.0 /** 扫描器下方提示文字高度 */ 18 | 19 | private let scannerLineAnmationKey = "ScannerLineAnmationKey" /** 扫描线条动画Key值 */ 20 | private var flashlightKey: Void? 21 | 22 | class SWScannerView: UIView { 23 | 24 | /** 扫描器宽度 */ 25 | var scanner_width: CGFloat! 26 | /** 扫描器初始x值 */ 27 | var scanner_x: CGFloat! 28 | /** 扫描器初始y值 */ 29 | var scanner_y: CGFloat! 30 | 31 | var config: SWQRCodeCompat! 32 | private var activityIndicator: UIActivityIndicatorView! 33 | 34 | init(frame: CGRect, config: SWQRCodeCompat) { 35 | super.init(frame: frame) 36 | 37 | scanner_width = 0.7*self.frame.size.width 38 | scanner_x = (self.frame.size.width - scanner_width)/2 39 | scanner_y = (self.frame.size.height - scanner_width)/2 - 50 40 | self.config = config 41 | 42 | _setupUI() 43 | } 44 | 45 | private func _setupUI() { 46 | self.backgroundColor = .clear 47 | 48 | self.addSubview(self.scannerLine) 49 | sw_addScannerLineAnimation() 50 | 51 | self.addSubview(self.tipLab) 52 | self.addSubview(self.flashlightBtn) 53 | self.addSubview(self.flashlightLab) 54 | } 55 | 56 | // MARK: - 手电筒点击事件 57 | @objc private func flashlightClicked(button: UIButton) { 58 | button.isSelected = !button.isSelected 59 | sw_setFlashlight(on: button.isSelected) 60 | } 61 | 62 | override func draw(_ rect: CGRect) { 63 | super.draw(rect) 64 | 65 | // 半透明区域 66 | UIColor(white: 0, alpha: 0.7).setFill() 67 | UIRectFill(rect) 68 | 69 | // 透明区域 70 | let scanner_rect = CGRect(x: scanner_x, y: scanner_y, width: scanner_width, height: scanner_width) 71 | UIColor.clear.setFill() 72 | UIRectFill(scanner_rect) 73 | 74 | // 边框 75 | let borderPath = UIBezierPath(rect: CGRect(x: scanner_x, y: scanner_y, width: scanner_width, height: scanner_width)) 76 | borderPath.lineCapStyle = .round 77 | borderPath.lineWidth = scanner_borderWidth 78 | self.config.scannerBorderColor.set() 79 | borderPath.stroke() 80 | 81 | for index in 0...3 { 82 | let tempPath = UIBezierPath() 83 | tempPath.lineWidth = scanner_cornerWidth 84 | self.config.scannerCornerColor.set() 85 | 86 | switch index { 87 | // 左上角棱角 88 | case 0: 89 | tempPath.move(to: CGPoint(x: scanner_x + scanner_cornerLength, y: scanner_y)) 90 | tempPath.addLine(to: CGPoint(x: scanner_x, y: scanner_y)) 91 | tempPath.addLine(to: CGPoint(x: scanner_x, y: scanner_y + scanner_cornerLength)) 92 | // 右上角 93 | case 1: 94 | tempPath.move(to: CGPoint(x: scanner_x + scanner_width - scanner_cornerLength, y: self.scanner_y)) 95 | tempPath.addLine(to: CGPoint(x: scanner_x + scanner_width, y: scanner_y)) 96 | tempPath.addLine(to: CGPoint(x: scanner_x + scanner_width, y: scanner_y + scanner_cornerLength)) 97 | // 左下角 98 | case 2: 99 | tempPath.move(to: CGPoint(x: scanner_x, y: scanner_y + scanner_width - scanner_cornerLength)) 100 | tempPath.addLine(to: CGPoint(x: scanner_x, y: scanner_y + scanner_width)) 101 | tempPath.addLine(to: CGPoint(x: scanner_x + scanner_cornerLength, y: scanner_y + scanner_width)) 102 | // 右下角 103 | case 3: 104 | tempPath.move(to: CGPoint(x: scanner_x + scanner_width - scanner_cornerLength, y: scanner_y + scanner_width)) 105 | tempPath.addLine(to: CGPoint(x: scanner_x + scanner_width, y: scanner_y + scanner_width)) 106 | tempPath.addLine(to: CGPoint(x: scanner_x + scanner_width, y: scanner_y + scanner_width - scanner_cornerLength)) 107 | default: 108 | break 109 | } 110 | tempPath.stroke() 111 | } 112 | } 113 | 114 | /** 扫描线条 */ 115 | private lazy var scannerLine: UIImageView = { 116 | let tempScannerLine = UIImageView(frame: CGRect(x: scanner_x, y: scanner_y, width: scanner_width, height: scanner_lineHeight)) 117 | tempScannerLine.image = UIImage(named: "SWQRCode.bundle/ScannerLine") 118 | return tempScannerLine 119 | }() 120 | 121 | /** 扫描器下方提示文字 */ 122 | private lazy var tipLab: UILabel = { 123 | let tempTipLab = UILabel(frame: CGRect(x: 0, y: scanner_y + scanner_width, width: self.frame.size.width, height: 50)) 124 | tempTipLab.textAlignment = .center 125 | tempTipLab.textColor = .lightGray 126 | tempTipLab.font = UIFont.systemFont(ofSize: 12) 127 | tempTipLab.text = "将二维码/条码放入框内,即可自动扫描" 128 | return tempTipLab 129 | }() 130 | 131 | /** 手电筒开关 */ 132 | private lazy var flashlightBtn: UIButton = { 133 | let tempFlashlightBtn = UIButton(type: .custom) 134 | tempFlashlightBtn.frame = CGRect(x: (self.frame.size.width - flashlightBtn_width)/2, y: scanner_y + scanner_width - 15 - flashlightLab_height - flashlightBtn_width, width: flashlightBtn_width, height: flashlightBtn_width) 135 | tempFlashlightBtn.isEnabled = false 136 | tempFlashlightBtn.alpha = 0 137 | tempFlashlightBtn.addTarget(self, action: #selector(flashlightClicked), for: .touchUpInside) 138 | tempFlashlightBtn.setBackgroundImage(UIImage(named: "SWQRCode.bundle/Flashlight_Off"), for: .normal) 139 | tempFlashlightBtn.setBackgroundImage(UIImage(named: "SWQRCode.bundle/Flashlight_On"), for: .selected) 140 | return tempFlashlightBtn 141 | }() 142 | 143 | /** 手电筒提示文字 */ 144 | private lazy var flashlightLab: UILabel = { 145 | let tempFlashlightLab = UILabel(frame: CGRect(x: scanner_x, y: scanner_y + scanner_width - 10 - flashlightLab_height, width: scanner_width, height: flashlightLab_height)) 146 | tempFlashlightLab.font = UIFont.systemFont(ofSize: 12) 147 | tempFlashlightLab.textColor = .white 148 | tempFlashlightLab.text = "轻触照亮" 149 | tempFlashlightLab.alpha = 0; 150 | tempFlashlightLab.textAlignment = .center 151 | return tempFlashlightLab 152 | }() 153 | 154 | required init?(coder aDecoder: NSCoder) { 155 | super.init(coder: aDecoder) 156 | } 157 | } 158 | 159 | // MARK: - 扫描线条动画 160 | extension SWScannerView { 161 | 162 | /** 添加扫描线条动画 */ 163 | func sw_addScannerLineAnimation() { 164 | // 若已添加动画,则先移除动画再添加 165 | self.scannerLine.layer.removeAllAnimations() 166 | 167 | let lineAnimation = CABasicAnimation(keyPath: "transform") 168 | lineAnimation.toValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0, scanner_width - scanner_lineHeight, 1)) 169 | lineAnimation.duration = 4 170 | lineAnimation.repeatCount = MAXFLOAT 171 | self.scannerLine.layer.add(lineAnimation, forKey: scannerLineAnmationKey) 172 | // 重置动画运行速度为1.0 173 | self.scannerLine.layer.speed = 1.0 174 | } 175 | 176 | /** 暂停扫描器动画 */ 177 | func sw_pauseScannerLineAnimation() { 178 | // 取出当前时间,转成动画暂停的时间 179 | let pauseTime = self.scannerLine.layer.convertTime(CACurrentMediaTime(), from: nil) 180 | // 设置动画的时间偏移量,指定时间偏移量的目的是让动画定格在该时间点的位置 181 | self.scannerLine.layer.timeOffset = pauseTime 182 | // 将动画的运行速度设置为0, 默认的运行速度是1.0 183 | self.scannerLine.layer.speed = 0 184 | } 185 | } 186 | 187 | // MARK: - 显示/隐藏手电筒 188 | extension SWScannerView { 189 | 190 | /** 显示手电筒 */ 191 | func sw_showFlashlight(animated: Bool) { 192 | if animated { 193 | UIView.animate(withDuration: 0.6, animations: { 194 | self.flashlightLab.alpha = 1.0 195 | self.flashlightBtn.alpha = 1.0 196 | self.tipLab.alpha = 0 197 | }, completion: { (finished) in 198 | self.flashlightBtn.isEnabled = true 199 | }) 200 | } 201 | else { 202 | self.flashlightLab.alpha = 1.0 203 | self.flashlightBtn.alpha = 1.0 204 | self.tipLab.alpha = 0 205 | self.flashlightBtn.isEnabled = true 206 | } 207 | } 208 | 209 | /** 隐藏手电筒 */ 210 | func sw_hideFlashlight(animated: Bool) { 211 | self.flashlightBtn.isEnabled = false 212 | if animated { 213 | UIView.animate(withDuration: 0.6, animations: { 214 | self.flashlightLab.alpha = 0 215 | self.flashlightBtn.alpha = 0 216 | self.tipLab.alpha = 1.0 217 | }) 218 | } 219 | else { 220 | self.flashlightLab.alpha = 0 221 | self.flashlightBtn.alpha = 0 222 | self.tipLab.alpha = 1.0 223 | } 224 | } 225 | } 226 | 227 | // MARK: - 添加/移除指示器 228 | extension SWScannerView { 229 | 230 | /** 添加指示器 */ 231 | func sw_addActivityIndicator() { 232 | if self.activityIndicator == nil { 233 | self.activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: self.config.indicatorViewStyle) 234 | self.activityIndicator.center = self.center 235 | self.addSubview(self.activityIndicator) 236 | } 237 | self.activityIndicator.startAnimating() 238 | } 239 | 240 | /** 移除指示器 */ 241 | func sw_removeActivityIndicator() { 242 | if self.activityIndicator != nil { 243 | self.activityIndicator.removeFromSuperview() 244 | self.activityIndicator = nil 245 | } 246 | } 247 | } 248 | 249 | // MARK: - 设置/获取手电筒开关状态 250 | extension SWScannerView { 251 | 252 | /** 设置手电筒开关 */ 253 | func sw_setFlashlight(on: Bool) { 254 | SWQRCodeHelper.sw_flashlight(on: on) 255 | self.flashlightLab.text = on ? "轻触关闭":"轻触照亮" 256 | self.flashlightBtn.isSelected = on; 257 | objc_setAssociatedObject(self, &flashlightKey, on, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 258 | } 259 | 260 | /** 获取手电筒当前开关状态 */ 261 | func sw_setFlashlightOn() -> Bool { 262 | return objc_getAssociatedObject(self, &flashlightKey) as? Bool ?? false 263 | } 264 | } 265 | 266 | 267 | 268 | 269 | 270 | -------------------------------------------------------------------------------- /SWQRCode_Swift/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SWQRCode_Swift 4 | // 5 | // Created by zhuku on 2018/4/10. 6 | // Copyright © 2018年 selwyn. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | let titles = ["扫一扫"]; 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | // Do any additional setup after loading the view, typically from a nib. 18 | 19 | let tbv = UITableView(frame: self.view.bounds, style: .plain) 20 | tbv.delegate = self 21 | tbv.dataSource = self 22 | self.view.addSubview(tbv) 23 | 24 | tbv.register(UITableViewCell.self, forCellReuseIdentifier: "cell_id") 25 | } 26 | 27 | override func didReceiveMemoryWarning() { 28 | super.didReceiveMemoryWarning() 29 | // Dispose of any resources that can be recreated. 30 | } 31 | 32 | } 33 | 34 | extension ViewController: UITableViewDelegate, UITableViewDataSource { 35 | 36 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 37 | return self.titles.count 38 | } 39 | 40 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 41 | let cell = tableView.dequeueReusableCell(withIdentifier: "cell_id", for: indexPath) 42 | cell.textLabel?.text = self.titles[indexPath.row] 43 | cell.selectionStyle = .none 44 | return cell 45 | } 46 | 47 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 48 | switch indexPath.row { 49 | case 0: 50 | let qrcodeVC = SWQRCodeViewController() 51 | self.navigationController?.pushViewController(qrcodeVC, animated: true) 52 | default: 53 | break 54 | } 55 | } 56 | } 57 | 58 | 59 | 60 | 61 | 62 | --------------------------------------------------------------------------------