├── .gitignore ├── LICENSE ├── Podfile ├── Podfile.lock ├── README.md ├── SwiftTemplate.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── jetlee.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── SwiftTemplate.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── SwiftTemplate ├── Controllers │ ├── LaunchViewController.swift │ ├── NewsViewController.swift │ └── ScanViewController.swift ├── Extensions │ ├── Foundation.swift │ └── UIKit.swift ├── Models │ ├── AuthenticationModel.swift │ ├── ErrorModel.swift │ ├── NewsModel.swift │ └── UserInfoModel.swift ├── Services │ ├── APIService.swift │ ├── ApplePayService.swift │ └── PublicService.swift ├── Supporting Files │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── headiconiPadApp_76pt.png │ │ │ ├── headiconiPadApp_76pt@2x.png │ │ │ ├── headiconiPadNotifications_20pt.png │ │ │ ├── headiconiPadNotifications_20pt@2x.png │ │ │ ├── headiconiPadProApp_83.5pt@2x.png │ │ │ ├── headiconiPadSpootlight5_29pt.png │ │ │ ├── headiconiPadSpootlight5_29pt@2x.png │ │ │ ├── headiconiPadSpootlight7_40pt.png │ │ │ ├── headiconiPadSpootlight7_40pt@2x.png │ │ │ ├── headiconiPhoneApp_60pt@2x.png │ │ │ ├── headiconiPhoneApp_60pt@3x.png │ │ │ ├── headiconiPhoneNotification_20pt@2x.png │ │ │ ├── headiconiPhoneNotification_20pt@3x.png │ │ │ ├── headiconiPhoneSpootlight5_29pt@2x.png │ │ │ ├── headiconiPhoneSpootlight5_29pt@3x.png │ │ │ ├── headiconiPhoneSpootlight7_40pt@2x.png │ │ │ ├── headiconiPhoneSpootlight7_40pt@3x.png │ │ │ └── headiconstore_1024pt.png │ │ ├── Contents.json │ │ ├── icon_nav_back.imageset │ │ │ ├── Contents.json │ │ │ └── icon_nav_back@2x.png │ │ └── img_qrcode_border.imageset │ │ │ ├── Contents.json │ │ │ └── img_qrcode_border@2x.png │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ └── Info.plist ├── Utils │ └── Constant.swift ├── Venders │ ├── Toast.swift │ └── ZZYQRCodeSwift │ │ ├── AVCaptureSessionManager.swift │ │ ├── UIImage+ZZYQRCodeImage.swift │ │ └── ZZYQRCode.bundle │ │ ├── Info.plist │ │ └── sound.caf └── Views │ ├── BadgeButton.swift │ ├── NewsTableViewCell.swift │ └── SendCodeButton.swift ├── SwiftTemplateTests ├── Info.plist └── SwiftTemplateTests.swift ├── SwiftTemplateUITests ├── Info.plist └── SwiftTemplateUITests.swift └── fastlane ├── Appfile ├── Deliverfile ├── Fastfile └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | compile_commands.json 5 | xcodebuild.log 6 | 7 | .idea/ 8 | Preview.html 9 | 10 | ## Build generated 11 | build/ 12 | DerivedData 13 | 14 | ## Various settings 15 | *.pbxuser 16 | !default.pbxuser 17 | *.mode1v3 18 | !default.mode1v3 19 | *.mode2v3 20 | !default.mode2v3 21 | *.perspectivev3 22 | !default.perspectivev3 23 | xcuserdata 24 | 25 | ## Other 26 | *.xccheckout 27 | *.moved-aside 28 | *.xcuserstate 29 | *.xcscmblueprint 30 | 31 | ## Obj-C/Swift specific 32 | *.hmap 33 | *.ipa 34 | 35 | # CocoaPods 36 | # 37 | # We recommend against adding the Pods directory to your .gitignore. However 38 | # you should judge for yourself, the pros and cons are mentioned at: 39 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 40 | # 41 | Pods/ 42 | 43 | # Carthage 44 | # 45 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 46 | # Carthage/Checkouts 47 | 48 | Carthage/Build 49 | 50 | .DS_Store 51 | *.mobileprovision 52 | *.zip 53 | *.cer 54 | 55 | 56 | # fastlane specific 57 | fastlane/report.xml 58 | 59 | # deliver temporary files 60 | fastlane/Preview.html 61 | 62 | # snapshot generated screenshots 63 | 64 | # scan temporary files 65 | fastlane/test_output 66 | 67 | *.p12 68 | *.pem 69 | *.pkey 70 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2018 PUPBOSS (https://www.pupboss.com) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '12.0' 2 | use_frameworks! 3 | inhibit_all_warnings! 4 | 5 | target 'SwiftTemplate' do 6 | pod 'SnapKit', '~> 5.0.1' 7 | pod 'Alamofire', '~> 5.2.1' 8 | pod 'MJRefresh', '~> 3.4' 9 | pod 'Kingfisher', '~> 5.14' 10 | end 11 | 12 | post_install do |installer| 13 | installer.pods_project.targets.each do |target| 14 | target.build_configurations.each do |config| 15 | config.build_settings.delete 'IPHONEOS_DEPLOYMENT_TARGET' 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (5.2.2) 3 | - Kingfisher (5.15.8): 4 | - Kingfisher/Core (= 5.15.8) 5 | - Kingfisher/Core (5.15.8) 6 | - MJRefresh (3.5.0) 7 | - SnapKit (5.0.1) 8 | 9 | DEPENDENCIES: 10 | - Alamofire (~> 5.2.1) 11 | - Kingfisher (~> 5.14) 12 | - MJRefresh (~> 3.4) 13 | - SnapKit (~> 5.0.1) 14 | 15 | SPEC REPOS: 16 | trunk: 17 | - Alamofire 18 | - Kingfisher 19 | - MJRefresh 20 | - SnapKit 21 | 22 | SPEC CHECKSUMS: 23 | Alamofire: 814429acc853c6c54ff123fc3d2ef66803823ce0 24 | Kingfisher: a3c03d702433fa6cfedabb2bddbe076fb8f2e902 25 | MJRefresh: 6afc955813966afb08305477dd7a0d9ad5e79a16 26 | SnapKit: 97b92857e3df3a0c71833cce143274bf6ef8e5eb 27 | 28 | PODFILE CHECKSUM: 47126ae66baeed2c351007297372c0ebe5c0e9ba 29 | 30 | COCOAPODS: 1.11.2 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftTemplate 2 | 3 | [![Platform](https://img.shields.io/badge/platform-iOS-red.svg)](https://developer.apple.com/iphone/index.action) 4 | [![Language](https://img.shields.io/badge/language-swift5-yellow.svg?style=flat 5 | )](https://en.wikipedia.org/wiki/swift) 6 | [![License](https://img.shields.io/badge/license-MIT-blue.svg)](http://mit-license.org) 7 | 8 | Demo Swift project with clear and concise structure. 9 | 10 | 适合小型 app 的 Swift 项目架构。代码结构简练,没有多余的废话。此项目包括了网络模块,布局,主题色管理,tableview 标准实现,还有一些零碎东西。网络层改了好几版花了不少心思,用起来还是很爽的比如 JSON 转 model 还有错误统一处理,有一些可复用的 extension 请参考 readme 文档。 11 | 12 | ## Models 13 | 14 | Please refer to [Ultimate Guide to JSON Parsing with Swift 4](https://benscheirman.com/2017/06/swift-json/) 15 | 16 | ``` 17 | func requestDecodable(path: String, decodableType: T.Type, completionHandler: @escaping (Result) -> Void) { 18 | requestDecodable(method: .get, path: path, params: nil, paramsType: .form, decodableType: decodableType, completionHandler: completionHandler) 19 | } 20 | ``` 21 | 22 | ## Services 23 | 24 | ### APIService 25 | 26 | A simple get & non-params request: 27 | 28 | ``` 29 | // Returns a normal object, use UserInfoModel.self 30 | APIService.shared.requestDecodable(path: "/your/profile", decodableType: UserInfoModel.self) { (result) in 31 | switch result { 32 | case .success(let user): 33 | user = user 34 | case .failure(let error): 35 | view.makeToast(error.message) 36 | } 37 | } 38 | 39 | // Returns an array, use [OrderModel].self 40 | APIService.shared.requestDecodable(path: "/your/orders", decodableType: [OrderModel].self) { (result) in 41 | switch result { 42 | case .success(let orders): 43 | orders = orders 44 | tableView.reloadData() 45 | case .failure(let error): 46 | view.makeToast(error.message) 47 | } 48 | } 49 | ``` 50 | 51 | Other requests: 52 | 53 | ``` 54 | func requestDecodable(method: HTTPMethod, path: String, params: [String: Any]?, paramsType: ParamsType, decodableType: T.Type, completionHandler: @escaping (Result) -> Void) 55 | ``` 56 | 57 | ## Extensions 58 | 59 | ### Date 60 | 61 | ``` 62 | let createTime: Date 63 | 64 | createTime.formattedString(withDateFormat: "yyyy-MM-dd") 65 | ``` 66 | 67 | ### Bool 68 | 69 | ``` 70 | true.intValue 71 | ``` 72 | 73 | ### String 74 | 75 | ``` 76 | let n = "Name".notificationName 77 | 78 | let phone = "00321456".trimLeft(withTargetString: "0") 79 | 80 | let base64 = "string".toBase64() 81 | 82 | let c = "0123".isValidCreditCard 83 | 84 | let e = "a@b.com".isValidEmail 85 | ``` 86 | 87 | ### UILabel 88 | 89 | ``` 90 | numberLabel.text = "**** **** **** ****" 91 | numberLabel.changeCharacterSpace(space: 6) 92 | ``` 93 | 94 | ### UIImage 95 | 96 | ``` 97 | let c = UIImage.fromColor(color: UIColor.clear) 98 | 99 | button.setBackgroundImage(c, for: .normal) 100 | ``` 101 | 102 | ## Views 103 | 104 | ### SendCodeButton 105 | 106 | ``` 107 | let sendCodeButton = SendCodeButton() 108 | sendCodeButton.addTarget(self, action: #selector(sendCodeButtonTapped), for: .touchUpInside) 109 | sendCodeButton.titleLabel?.font = UIFont.systemFont(ofSize: 13) 110 | sendCodeButton.layer.cornerRadius = 20 111 | sendCodeButton.layer.masksToBounds = true 112 | 113 | @objc func sendCodeButtonTapped(btn: UIButton) { 114 | sendVerifyCode() 115 | btn.enterDisableMode() 116 | } 117 | ``` 118 | 119 | ### BadgeButton 120 | 121 | ``` 122 | let button = BadgeButton() 123 | 124 | button.badgeTextColor = UIColor.themeColor() 125 | button.badgeBackgroundColor = UIColor.white 126 | button.badgeFont = UIFont.boldSystemFont(ofSize: 14) 127 | button.badgeEdgeInsets = UIEdgeInsets(top: 30, left: 0, bottom: 0, right: view.frame.size.width / 2 - 50) 128 | ``` 129 | 130 | -------------------------------------------------------------------------------- /SwiftTemplate.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 51; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 083D98E520C66CCE00873C41 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083D98E420C66CCE00873C41 /* AppDelegate.swift */; }; 11 | 083D98EA20C66CCE00873C41 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 083D98E820C66CCE00873C41 /* Main.storyboard */; }; 12 | 083D98EC20C66CCF00873C41 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 083D98EB20C66CCF00873C41 /* Assets.xcassets */; }; 13 | 083D98EF20C66CCF00873C41 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 083D98ED20C66CCF00873C41 /* LaunchScreen.storyboard */; }; 14 | 083D98FA20C66CCF00873C41 /* SwiftTemplateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083D98F920C66CCF00873C41 /* SwiftTemplateTests.swift */; }; 15 | 083D990520C66CCF00873C41 /* SwiftTemplateUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083D990420C66CCF00873C41 /* SwiftTemplateUITests.swift */; }; 16 | 083D991820C66DFA00873C41 /* ErrorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083D991720C66DF900873C41 /* ErrorModel.swift */; }; 17 | 083D991E20C66EC500873C41 /* BadgeButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083D991B20C66EC500873C41 /* BadgeButton.swift */; }; 18 | 083D991F20C66EC500873C41 /* SendCodeButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083D991C20C66EC500873C41 /* SendCodeButton.swift */; }; 19 | 083D992C20C66EFE00873C41 /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083D992520C66EFE00873C41 /* Foundation.swift */; }; 20 | 083D993020C66EFE00873C41 /* UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083D992920C66EFE00873C41 /* UIKit.swift */; }; 21 | 083D993820C66F1800873C41 /* ApplePayService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083D993520C66F1800873C41 /* ApplePayService.swift */; }; 22 | 083D993E20C66F1D00873C41 /* Constant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083D993B20C66F1D00873C41 /* Constant.swift */; }; 23 | 083D995C20C66F2300873C41 /* AVCaptureSessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083D994F20C66F2300873C41 /* AVCaptureSessionManager.swift */; }; 24 | 083D995D20C66F2300873C41 /* UIImage+ZZYQRCodeImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083D995020C66F2300873C41 /* UIImage+ZZYQRCodeImage.swift */; }; 25 | 083D995E20C66F2300873C41 /* ZZYQRCode.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 083D995120C66F2300873C41 /* ZZYQRCode.bundle */; }; 26 | 083D999A20C676FF00873C41 /* LaunchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083D999920C676FF00873C41 /* LaunchViewController.swift */; }; 27 | 083D999E20C6776F00873C41 /* ScanViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083D999D20C6776F00873C41 /* ScanViewController.swift */; }; 28 | 083D99A020C677BA00873C41 /* UserInfoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083D999F20C677BA00873C41 /* UserInfoModel.swift */; }; 29 | 1133DFFC6AC537E82B2E5CCC /* Pods_SwiftTemplate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B7639F6FB3C35F6B3F3F205E /* Pods_SwiftTemplate.framework */; }; 30 | 400C778424A997CD004B2E03 /* Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 400C778324A997CD004B2E03 /* Toast.swift */; }; 31 | 406BCD0724A79635007EEE48 /* AuthenticationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406BCD0624A79635007EEE48 /* AuthenticationModel.swift */; }; 32 | 40BB57132409379A00236BFE /* APIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40BB57122409379A00236BFE /* APIService.swift */; }; 33 | 40BB8C0F24AE00C000313055 /* PublicService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40BB8C0E24AE00C000313055 /* PublicService.swift */; }; 34 | 40DF677624C9A72A00542634 /* NewsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40DF677524C9A72A00542634 /* NewsViewController.swift */; }; 35 | 40DF677824C9A7E900542634 /* NewsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40DF677724C9A7E900542634 /* NewsModel.swift */; }; 36 | 40DF677A24C9A7FB00542634 /* NewsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40DF677924C9A7FB00542634 /* NewsTableViewCell.swift */; }; 37 | /* End PBXBuildFile section */ 38 | 39 | /* Begin PBXContainerItemProxy section */ 40 | 083D98F620C66CCF00873C41 /* PBXContainerItemProxy */ = { 41 | isa = PBXContainerItemProxy; 42 | containerPortal = 083D98D920C66CCE00873C41 /* Project object */; 43 | proxyType = 1; 44 | remoteGlobalIDString = 083D98E020C66CCE00873C41; 45 | remoteInfo = SwiftTemplate; 46 | }; 47 | 083D990120C66CCF00873C41 /* PBXContainerItemProxy */ = { 48 | isa = PBXContainerItemProxy; 49 | containerPortal = 083D98D920C66CCE00873C41 /* Project object */; 50 | proxyType = 1; 51 | remoteGlobalIDString = 083D98E020C66CCE00873C41; 52 | remoteInfo = SwiftTemplate; 53 | }; 54 | /* End PBXContainerItemProxy section */ 55 | 56 | /* Begin PBXFileReference section */ 57 | 083D98E120C66CCE00873C41 /* SwiftTemplate.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftTemplate.app; sourceTree = BUILT_PRODUCTS_DIR; }; 58 | 083D98E420C66CCE00873C41 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 59 | 083D98E920C66CCE00873C41 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 60 | 083D98EB20C66CCF00873C41 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 61 | 083D98EE20C66CCF00873C41 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 62 | 083D98F020C66CCF00873C41 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 63 | 083D98F520C66CCF00873C41 /* SwiftTemplateTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftTemplateTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 64 | 083D98F920C66CCF00873C41 /* SwiftTemplateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftTemplateTests.swift; sourceTree = ""; }; 65 | 083D98FB20C66CCF00873C41 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 66 | 083D990020C66CCF00873C41 /* SwiftTemplateUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftTemplateUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 67 | 083D990420C66CCF00873C41 /* SwiftTemplateUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftTemplateUITests.swift; sourceTree = ""; }; 68 | 083D990620C66CCF00873C41 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 69 | 083D991720C66DF900873C41 /* ErrorModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErrorModel.swift; sourceTree = ""; }; 70 | 083D991B20C66EC500873C41 /* BadgeButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BadgeButton.swift; sourceTree = ""; }; 71 | 083D991C20C66EC500873C41 /* SendCodeButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendCodeButton.swift; sourceTree = ""; }; 72 | 083D992520C66EFE00873C41 /* Foundation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Foundation.swift; sourceTree = ""; }; 73 | 083D992920C66EFE00873C41 /* UIKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIKit.swift; sourceTree = ""; }; 74 | 083D993520C66F1800873C41 /* ApplePayService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplePayService.swift; sourceTree = ""; }; 75 | 083D993B20C66F1D00873C41 /* Constant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constant.swift; sourceTree = ""; }; 76 | 083D994F20C66F2300873C41 /* AVCaptureSessionManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AVCaptureSessionManager.swift; sourceTree = ""; }; 77 | 083D995020C66F2300873C41 /* UIImage+ZZYQRCodeImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+ZZYQRCodeImage.swift"; sourceTree = ""; }; 78 | 083D995120C66F2300873C41 /* ZZYQRCode.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = ZZYQRCode.bundle; sourceTree = ""; }; 79 | 083D999920C676FF00873C41 /* LaunchViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LaunchViewController.swift; sourceTree = ""; }; 80 | 083D999D20C6776F00873C41 /* ScanViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanViewController.swift; sourceTree = ""; }; 81 | 083D999F20C677BA00873C41 /* UserInfoModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInfoModel.swift; sourceTree = ""; }; 82 | 12B85DB6F8015DED62A5F6CF /* Pods-SwiftTemplate.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftTemplate.release.xcconfig"; path = "Pods/Target Support Files/Pods-SwiftTemplate/Pods-SwiftTemplate.release.xcconfig"; sourceTree = ""; }; 83 | 400C778324A997CD004B2E03 /* Toast.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Toast.swift; sourceTree = ""; }; 84 | 406BCD0624A79635007EEE48 /* AuthenticationModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationModel.swift; sourceTree = ""; }; 85 | 40BB57122409379A00236BFE /* APIService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIService.swift; sourceTree = ""; }; 86 | 40BB8C0E24AE00C000313055 /* PublicService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PublicService.swift; sourceTree = ""; }; 87 | 40DF677524C9A72A00542634 /* NewsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsViewController.swift; sourceTree = ""; }; 88 | 40DF677724C9A7E900542634 /* NewsModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewsModel.swift; sourceTree = ""; }; 89 | 40DF677924C9A7FB00542634 /* NewsTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewsTableViewCell.swift; sourceTree = ""; }; 90 | B7639F6FB3C35F6B3F3F205E /* Pods_SwiftTemplate.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftTemplate.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 91 | C28A5DC2A6DA4B3A8E756602 /* Pods-SwiftTemplate.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftTemplate.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SwiftTemplate/Pods-SwiftTemplate.debug.xcconfig"; sourceTree = ""; }; 92 | /* End PBXFileReference section */ 93 | 94 | /* Begin PBXFrameworksBuildPhase section */ 95 | 083D98DE20C66CCE00873C41 /* Frameworks */ = { 96 | isa = PBXFrameworksBuildPhase; 97 | buildActionMask = 2147483647; 98 | files = ( 99 | 1133DFFC6AC537E82B2E5CCC /* Pods_SwiftTemplate.framework in Frameworks */, 100 | ); 101 | runOnlyForDeploymentPostprocessing = 0; 102 | }; 103 | 083D98F220C66CCF00873C41 /* Frameworks */ = { 104 | isa = PBXFrameworksBuildPhase; 105 | buildActionMask = 2147483647; 106 | files = ( 107 | ); 108 | runOnlyForDeploymentPostprocessing = 0; 109 | }; 110 | 083D98FD20C66CCF00873C41 /* Frameworks */ = { 111 | isa = PBXFrameworksBuildPhase; 112 | buildActionMask = 2147483647; 113 | files = ( 114 | ); 115 | runOnlyForDeploymentPostprocessing = 0; 116 | }; 117 | /* End PBXFrameworksBuildPhase section */ 118 | 119 | /* Begin PBXGroup section */ 120 | 083D98D820C66CCE00873C41 = { 121 | isa = PBXGroup; 122 | children = ( 123 | 083D98E320C66CCE00873C41 /* SwiftTemplate */, 124 | 083D98F820C66CCF00873C41 /* SwiftTemplateTests */, 125 | 083D990320C66CCF00873C41 /* SwiftTemplateUITests */, 126 | 083D98E220C66CCE00873C41 /* Products */, 127 | BFA5F32810645C56FD9FCDA8 /* Pods */, 128 | 6EECA2AEADAA0ABF846F1B9E /* Frameworks */, 129 | ); 130 | sourceTree = ""; 131 | }; 132 | 083D98E220C66CCE00873C41 /* Products */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | 083D98E120C66CCE00873C41 /* SwiftTemplate.app */, 136 | 083D98F520C66CCF00873C41 /* SwiftTemplateTests.xctest */, 137 | 083D990020C66CCF00873C41 /* SwiftTemplateUITests.xctest */, 138 | ); 139 | name = Products; 140 | sourceTree = ""; 141 | }; 142 | 083D98E320C66CCE00873C41 /* SwiftTemplate */ = { 143 | isa = PBXGroup; 144 | children = ( 145 | 083D991620C66DF900873C41 /* Models */, 146 | 083D991920C66EC500873C41 /* Views */, 147 | 083D991420C66D9800873C41 /* Controllers */, 148 | 083D992420C66EFE00873C41 /* Extensions */, 149 | 083D993320C66F1800873C41 /* Services */, 150 | 083D993A20C66F1D00873C41 /* Utils */, 151 | 083D994120C66F2300873C41 /* Venders */, 152 | 083D991520C66DAA00873C41 /* Supporting Files */, 153 | ); 154 | path = SwiftTemplate; 155 | sourceTree = ""; 156 | }; 157 | 083D98F820C66CCF00873C41 /* SwiftTemplateTests */ = { 158 | isa = PBXGroup; 159 | children = ( 160 | 083D98F920C66CCF00873C41 /* SwiftTemplateTests.swift */, 161 | 083D98FB20C66CCF00873C41 /* Info.plist */, 162 | ); 163 | path = SwiftTemplateTests; 164 | sourceTree = ""; 165 | }; 166 | 083D990320C66CCF00873C41 /* SwiftTemplateUITests */ = { 167 | isa = PBXGroup; 168 | children = ( 169 | 083D990420C66CCF00873C41 /* SwiftTemplateUITests.swift */, 170 | 083D990620C66CCF00873C41 /* Info.plist */, 171 | ); 172 | path = SwiftTemplateUITests; 173 | sourceTree = ""; 174 | }; 175 | 083D991420C66D9800873C41 /* Controllers */ = { 176 | isa = PBXGroup; 177 | children = ( 178 | 083D999920C676FF00873C41 /* LaunchViewController.swift */, 179 | 40DF677524C9A72A00542634 /* NewsViewController.swift */, 180 | 083D999D20C6776F00873C41 /* ScanViewController.swift */, 181 | ); 182 | path = Controllers; 183 | sourceTree = ""; 184 | }; 185 | 083D991520C66DAA00873C41 /* Supporting Files */ = { 186 | isa = PBXGroup; 187 | children = ( 188 | 083D98E420C66CCE00873C41 /* AppDelegate.swift */, 189 | 083D98ED20C66CCF00873C41 /* LaunchScreen.storyboard */, 190 | 083D98E820C66CCE00873C41 /* Main.storyboard */, 191 | 083D98EB20C66CCF00873C41 /* Assets.xcassets */, 192 | 083D98F020C66CCF00873C41 /* Info.plist */, 193 | ); 194 | path = "Supporting Files"; 195 | sourceTree = ""; 196 | }; 197 | 083D991620C66DF900873C41 /* Models */ = { 198 | isa = PBXGroup; 199 | children = ( 200 | 406BCD0624A79635007EEE48 /* AuthenticationModel.swift */, 201 | 083D999F20C677BA00873C41 /* UserInfoModel.swift */, 202 | 40DF677724C9A7E900542634 /* NewsModel.swift */, 203 | 083D991720C66DF900873C41 /* ErrorModel.swift */, 204 | ); 205 | path = Models; 206 | sourceTree = ""; 207 | }; 208 | 083D991920C66EC500873C41 /* Views */ = { 209 | isa = PBXGroup; 210 | children = ( 211 | 083D991B20C66EC500873C41 /* BadgeButton.swift */, 212 | 083D991C20C66EC500873C41 /* SendCodeButton.swift */, 213 | 40DF677924C9A7FB00542634 /* NewsTableViewCell.swift */, 214 | ); 215 | path = Views; 216 | sourceTree = ""; 217 | }; 218 | 083D992420C66EFE00873C41 /* Extensions */ = { 219 | isa = PBXGroup; 220 | children = ( 221 | 083D992520C66EFE00873C41 /* Foundation.swift */, 222 | 083D992920C66EFE00873C41 /* UIKit.swift */, 223 | ); 224 | name = Extensions; 225 | path = SwiftTemplate/Extensions; 226 | sourceTree = SOURCE_ROOT; 227 | }; 228 | 083D993320C66F1800873C41 /* Services */ = { 229 | isa = PBXGroup; 230 | children = ( 231 | 40BB57122409379A00236BFE /* APIService.swift */, 232 | 40BB8C0E24AE00C000313055 /* PublicService.swift */, 233 | 083D993520C66F1800873C41 /* ApplePayService.swift */, 234 | ); 235 | path = Services; 236 | sourceTree = ""; 237 | }; 238 | 083D993A20C66F1D00873C41 /* Utils */ = { 239 | isa = PBXGroup; 240 | children = ( 241 | 083D993B20C66F1D00873C41 /* Constant.swift */, 242 | ); 243 | path = Utils; 244 | sourceTree = ""; 245 | }; 246 | 083D994120C66F2300873C41 /* Venders */ = { 247 | isa = PBXGroup; 248 | children = ( 249 | 400C778324A997CD004B2E03 /* Toast.swift */, 250 | 083D994E20C66F2300873C41 /* ZZYQRCodeSwift */, 251 | ); 252 | path = Venders; 253 | sourceTree = ""; 254 | }; 255 | 083D994E20C66F2300873C41 /* ZZYQRCodeSwift */ = { 256 | isa = PBXGroup; 257 | children = ( 258 | 083D994F20C66F2300873C41 /* AVCaptureSessionManager.swift */, 259 | 083D995020C66F2300873C41 /* UIImage+ZZYQRCodeImage.swift */, 260 | 083D995120C66F2300873C41 /* ZZYQRCode.bundle */, 261 | ); 262 | path = ZZYQRCodeSwift; 263 | sourceTree = ""; 264 | }; 265 | 6EECA2AEADAA0ABF846F1B9E /* Frameworks */ = { 266 | isa = PBXGroup; 267 | children = ( 268 | B7639F6FB3C35F6B3F3F205E /* Pods_SwiftTemplate.framework */, 269 | ); 270 | name = Frameworks; 271 | sourceTree = ""; 272 | }; 273 | BFA5F32810645C56FD9FCDA8 /* Pods */ = { 274 | isa = PBXGroup; 275 | children = ( 276 | C28A5DC2A6DA4B3A8E756602 /* Pods-SwiftTemplate.debug.xcconfig */, 277 | 12B85DB6F8015DED62A5F6CF /* Pods-SwiftTemplate.release.xcconfig */, 278 | ); 279 | name = Pods; 280 | sourceTree = ""; 281 | }; 282 | /* End PBXGroup section */ 283 | 284 | /* Begin PBXNativeTarget section */ 285 | 083D98E020C66CCE00873C41 /* SwiftTemplate */ = { 286 | isa = PBXNativeTarget; 287 | buildConfigurationList = 083D990920C66CCF00873C41 /* Build configuration list for PBXNativeTarget "SwiftTemplate" */; 288 | buildPhases = ( 289 | BB8657F86FB02AB0674716E1 /* [CP] Check Pods Manifest.lock */, 290 | 083D98DD20C66CCE00873C41 /* Sources */, 291 | 083D98DE20C66CCE00873C41 /* Frameworks */, 292 | 083D98DF20C66CCE00873C41 /* Resources */, 293 | 586C7A9D06C638BB8ACDBCEE /* [CP] Embed Pods Frameworks */, 294 | 083D991220C66D4100873C41 /* Run Script (Increase build number) */, 295 | ); 296 | buildRules = ( 297 | ); 298 | dependencies = ( 299 | ); 300 | name = SwiftTemplate; 301 | productName = SwiftTemplate; 302 | productReference = 083D98E120C66CCE00873C41 /* SwiftTemplate.app */; 303 | productType = "com.apple.product-type.application"; 304 | }; 305 | 083D98F420C66CCF00873C41 /* SwiftTemplateTests */ = { 306 | isa = PBXNativeTarget; 307 | buildConfigurationList = 083D990C20C66CCF00873C41 /* Build configuration list for PBXNativeTarget "SwiftTemplateTests" */; 308 | buildPhases = ( 309 | 083D98F120C66CCF00873C41 /* Sources */, 310 | 083D98F220C66CCF00873C41 /* Frameworks */, 311 | 083D98F320C66CCF00873C41 /* Resources */, 312 | ); 313 | buildRules = ( 314 | ); 315 | dependencies = ( 316 | 083D98F720C66CCF00873C41 /* PBXTargetDependency */, 317 | ); 318 | name = SwiftTemplateTests; 319 | productName = SwiftTemplateTests; 320 | productReference = 083D98F520C66CCF00873C41 /* SwiftTemplateTests.xctest */; 321 | productType = "com.apple.product-type.bundle.unit-test"; 322 | }; 323 | 083D98FF20C66CCF00873C41 /* SwiftTemplateUITests */ = { 324 | isa = PBXNativeTarget; 325 | buildConfigurationList = 083D990F20C66CCF00873C41 /* Build configuration list for PBXNativeTarget "SwiftTemplateUITests" */; 326 | buildPhases = ( 327 | 083D98FC20C66CCF00873C41 /* Sources */, 328 | 083D98FD20C66CCF00873C41 /* Frameworks */, 329 | 083D98FE20C66CCF00873C41 /* Resources */, 330 | ); 331 | buildRules = ( 332 | ); 333 | dependencies = ( 334 | 083D990220C66CCF00873C41 /* PBXTargetDependency */, 335 | ); 336 | name = SwiftTemplateUITests; 337 | productName = SwiftTemplateUITests; 338 | productReference = 083D990020C66CCF00873C41 /* SwiftTemplateUITests.xctest */; 339 | productType = "com.apple.product-type.bundle.ui-testing"; 340 | }; 341 | /* End PBXNativeTarget section */ 342 | 343 | /* Begin PBXProject section */ 344 | 083D98D920C66CCE00873C41 /* Project object */ = { 345 | isa = PBXProject; 346 | attributes = { 347 | LastSwiftUpdateCheck = 0940; 348 | LastUpgradeCheck = 1250; 349 | ORGANIZATIONNAME = "Meltdown Research"; 350 | TargetAttributes = { 351 | 083D98E020C66CCE00873C41 = { 352 | CreatedOnToolsVersion = 9.4; 353 | LastSwiftMigration = 1130; 354 | }; 355 | 083D98F420C66CCF00873C41 = { 356 | CreatedOnToolsVersion = 9.4; 357 | LastSwiftMigration = 1130; 358 | TestTargetID = 083D98E020C66CCE00873C41; 359 | }; 360 | 083D98FF20C66CCF00873C41 = { 361 | CreatedOnToolsVersion = 9.4; 362 | LastSwiftMigration = 1130; 363 | TestTargetID = 083D98E020C66CCE00873C41; 364 | }; 365 | }; 366 | }; 367 | buildConfigurationList = 083D98DC20C66CCE00873C41 /* Build configuration list for PBXProject "SwiftTemplate" */; 368 | compatibilityVersion = "Xcode 9.3"; 369 | developmentRegion = en; 370 | hasScannedForEncodings = 0; 371 | knownRegions = ( 372 | en, 373 | Base, 374 | ); 375 | mainGroup = 083D98D820C66CCE00873C41; 376 | productRefGroup = 083D98E220C66CCE00873C41 /* Products */; 377 | projectDirPath = ""; 378 | projectRoot = ""; 379 | targets = ( 380 | 083D98E020C66CCE00873C41 /* SwiftTemplate */, 381 | 083D98F420C66CCF00873C41 /* SwiftTemplateTests */, 382 | 083D98FF20C66CCF00873C41 /* SwiftTemplateUITests */, 383 | ); 384 | }; 385 | /* End PBXProject section */ 386 | 387 | /* Begin PBXResourcesBuildPhase section */ 388 | 083D98DF20C66CCE00873C41 /* Resources */ = { 389 | isa = PBXResourcesBuildPhase; 390 | buildActionMask = 2147483647; 391 | files = ( 392 | 083D98EF20C66CCF00873C41 /* LaunchScreen.storyboard in Resources */, 393 | 083D98EC20C66CCF00873C41 /* Assets.xcassets in Resources */, 394 | 083D98EA20C66CCE00873C41 /* Main.storyboard in Resources */, 395 | 083D995E20C66F2300873C41 /* ZZYQRCode.bundle in Resources */, 396 | ); 397 | runOnlyForDeploymentPostprocessing = 0; 398 | }; 399 | 083D98F320C66CCF00873C41 /* Resources */ = { 400 | isa = PBXResourcesBuildPhase; 401 | buildActionMask = 2147483647; 402 | files = ( 403 | ); 404 | runOnlyForDeploymentPostprocessing = 0; 405 | }; 406 | 083D98FE20C66CCF00873C41 /* Resources */ = { 407 | isa = PBXResourcesBuildPhase; 408 | buildActionMask = 2147483647; 409 | files = ( 410 | ); 411 | runOnlyForDeploymentPostprocessing = 0; 412 | }; 413 | /* End PBXResourcesBuildPhase section */ 414 | 415 | /* Begin PBXShellScriptBuildPhase section */ 416 | 083D991220C66D4100873C41 /* Run Script (Increase build number) */ = { 417 | isa = PBXShellScriptBuildPhase; 418 | buildActionMask = 2147483647; 419 | files = ( 420 | ); 421 | inputPaths = ( 422 | ); 423 | name = "Run Script (Increase build number)"; 424 | outputPaths = ( 425 | ); 426 | runOnlyForDeploymentPostprocessing = 0; 427 | shellPath = /bin/sh; 428 | shellScript = "buildNumber=$(/usr/libexec/PlistBuddy -c \"Print CFBundleVersion\" \"${INFOPLIST_FILE}\")\nbuildNumber=$(($buildNumber + 1))\n/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $buildNumber\" \"${INFOPLIST_FILE}\"\n"; 429 | }; 430 | 586C7A9D06C638BB8ACDBCEE /* [CP] Embed Pods Frameworks */ = { 431 | isa = PBXShellScriptBuildPhase; 432 | buildActionMask = 2147483647; 433 | files = ( 434 | ); 435 | inputFileListPaths = ( 436 | "${PODS_ROOT}/Target Support Files/Pods-SwiftTemplate/Pods-SwiftTemplate-frameworks-${CONFIGURATION}-input-files.xcfilelist", 437 | ); 438 | name = "[CP] Embed Pods Frameworks"; 439 | outputFileListPaths = ( 440 | "${PODS_ROOT}/Target Support Files/Pods-SwiftTemplate/Pods-SwiftTemplate-frameworks-${CONFIGURATION}-output-files.xcfilelist", 441 | ); 442 | runOnlyForDeploymentPostprocessing = 0; 443 | shellPath = /bin/sh; 444 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SwiftTemplate/Pods-SwiftTemplate-frameworks.sh\"\n"; 445 | showEnvVarsInLog = 0; 446 | }; 447 | BB8657F86FB02AB0674716E1 /* [CP] Check Pods Manifest.lock */ = { 448 | isa = PBXShellScriptBuildPhase; 449 | buildActionMask = 2147483647; 450 | files = ( 451 | ); 452 | inputPaths = ( 453 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 454 | "${PODS_ROOT}/Manifest.lock", 455 | ); 456 | name = "[CP] Check Pods Manifest.lock"; 457 | outputPaths = ( 458 | "$(DERIVED_FILE_DIR)/Pods-SwiftTemplate-checkManifestLockResult.txt", 459 | ); 460 | runOnlyForDeploymentPostprocessing = 0; 461 | shellPath = /bin/sh; 462 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 463 | showEnvVarsInLog = 0; 464 | }; 465 | /* End PBXShellScriptBuildPhase section */ 466 | 467 | /* Begin PBXSourcesBuildPhase section */ 468 | 083D98DD20C66CCE00873C41 /* Sources */ = { 469 | isa = PBXSourcesBuildPhase; 470 | buildActionMask = 2147483647; 471 | files = ( 472 | 083D991820C66DFA00873C41 /* ErrorModel.swift in Sources */, 473 | 406BCD0724A79635007EEE48 /* AuthenticationModel.swift in Sources */, 474 | 40DF677A24C9A7FB00542634 /* NewsTableViewCell.swift in Sources */, 475 | 400C778424A997CD004B2E03 /* Toast.swift in Sources */, 476 | 40BB57132409379A00236BFE /* APIService.swift in Sources */, 477 | 083D992C20C66EFE00873C41 /* Foundation.swift in Sources */, 478 | 083D993820C66F1800873C41 /* ApplePayService.swift in Sources */, 479 | 083D991F20C66EC500873C41 /* SendCodeButton.swift in Sources */, 480 | 083D993E20C66F1D00873C41 /* Constant.swift in Sources */, 481 | 40DF677824C9A7E900542634 /* NewsModel.swift in Sources */, 482 | 083D98E520C66CCE00873C41 /* AppDelegate.swift in Sources */, 483 | 083D995C20C66F2300873C41 /* AVCaptureSessionManager.swift in Sources */, 484 | 083D993020C66EFE00873C41 /* UIKit.swift in Sources */, 485 | 083D999A20C676FF00873C41 /* LaunchViewController.swift in Sources */, 486 | 40BB8C0F24AE00C000313055 /* PublicService.swift in Sources */, 487 | 083D995D20C66F2300873C41 /* UIImage+ZZYQRCodeImage.swift in Sources */, 488 | 083D99A020C677BA00873C41 /* UserInfoModel.swift in Sources */, 489 | 40DF677624C9A72A00542634 /* NewsViewController.swift in Sources */, 490 | 083D991E20C66EC500873C41 /* BadgeButton.swift in Sources */, 491 | 083D999E20C6776F00873C41 /* ScanViewController.swift in Sources */, 492 | ); 493 | runOnlyForDeploymentPostprocessing = 0; 494 | }; 495 | 083D98F120C66CCF00873C41 /* Sources */ = { 496 | isa = PBXSourcesBuildPhase; 497 | buildActionMask = 2147483647; 498 | files = ( 499 | 083D98FA20C66CCF00873C41 /* SwiftTemplateTests.swift in Sources */, 500 | ); 501 | runOnlyForDeploymentPostprocessing = 0; 502 | }; 503 | 083D98FC20C66CCF00873C41 /* Sources */ = { 504 | isa = PBXSourcesBuildPhase; 505 | buildActionMask = 2147483647; 506 | files = ( 507 | 083D990520C66CCF00873C41 /* SwiftTemplateUITests.swift in Sources */, 508 | ); 509 | runOnlyForDeploymentPostprocessing = 0; 510 | }; 511 | /* End PBXSourcesBuildPhase section */ 512 | 513 | /* Begin PBXTargetDependency section */ 514 | 083D98F720C66CCF00873C41 /* PBXTargetDependency */ = { 515 | isa = PBXTargetDependency; 516 | target = 083D98E020C66CCE00873C41 /* SwiftTemplate */; 517 | targetProxy = 083D98F620C66CCF00873C41 /* PBXContainerItemProxy */; 518 | }; 519 | 083D990220C66CCF00873C41 /* PBXTargetDependency */ = { 520 | isa = PBXTargetDependency; 521 | target = 083D98E020C66CCE00873C41 /* SwiftTemplate */; 522 | targetProxy = 083D990120C66CCF00873C41 /* PBXContainerItemProxy */; 523 | }; 524 | /* End PBXTargetDependency section */ 525 | 526 | /* Begin PBXVariantGroup section */ 527 | 083D98E820C66CCE00873C41 /* Main.storyboard */ = { 528 | isa = PBXVariantGroup; 529 | children = ( 530 | 083D98E920C66CCE00873C41 /* Base */, 531 | ); 532 | name = Main.storyboard; 533 | sourceTree = ""; 534 | }; 535 | 083D98ED20C66CCF00873C41 /* LaunchScreen.storyboard */ = { 536 | isa = PBXVariantGroup; 537 | children = ( 538 | 083D98EE20C66CCF00873C41 /* Base */, 539 | ); 540 | name = LaunchScreen.storyboard; 541 | sourceTree = ""; 542 | }; 543 | /* End PBXVariantGroup section */ 544 | 545 | /* Begin XCBuildConfiguration section */ 546 | 083D990720C66CCF00873C41 /* Debug */ = { 547 | isa = XCBuildConfiguration; 548 | buildSettings = { 549 | ALWAYS_SEARCH_USER_PATHS = NO; 550 | CLANG_ANALYZER_NONNULL = YES; 551 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 552 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 553 | CLANG_CXX_LIBRARY = "libc++"; 554 | CLANG_ENABLE_MODULES = YES; 555 | CLANG_ENABLE_OBJC_ARC = YES; 556 | CLANG_ENABLE_OBJC_WEAK = YES; 557 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 558 | CLANG_WARN_BOOL_CONVERSION = YES; 559 | CLANG_WARN_COMMA = YES; 560 | CLANG_WARN_CONSTANT_CONVERSION = YES; 561 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 562 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 563 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 564 | CLANG_WARN_EMPTY_BODY = YES; 565 | CLANG_WARN_ENUM_CONVERSION = YES; 566 | CLANG_WARN_INFINITE_RECURSION = YES; 567 | CLANG_WARN_INT_CONVERSION = YES; 568 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 569 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 570 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 571 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 572 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 573 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 574 | CLANG_WARN_STRICT_PROTOTYPES = YES; 575 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 576 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 577 | CLANG_WARN_UNREACHABLE_CODE = YES; 578 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 579 | CODE_SIGN_IDENTITY = "iPhone Developer"; 580 | COPY_PHASE_STRIP = NO; 581 | DEBUG_INFORMATION_FORMAT = dwarf; 582 | ENABLE_STRICT_OBJC_MSGSEND = YES; 583 | ENABLE_TESTABILITY = YES; 584 | GCC_C_LANGUAGE_STANDARD = gnu11; 585 | GCC_DYNAMIC_NO_PIC = NO; 586 | GCC_NO_COMMON_BLOCKS = YES; 587 | GCC_OPTIMIZATION_LEVEL = 0; 588 | GCC_PREPROCESSOR_DEFINITIONS = ( 589 | "DEBUG=1", 590 | "$(inherited)", 591 | ); 592 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 593 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 594 | GCC_WARN_UNDECLARED_SELECTOR = YES; 595 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 596 | GCC_WARN_UNUSED_FUNCTION = YES; 597 | GCC_WARN_UNUSED_VARIABLE = YES; 598 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 599 | MTL_ENABLE_DEBUG_INFO = YES; 600 | ONLY_ACTIVE_ARCH = YES; 601 | SDKROOT = iphoneos; 602 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 603 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 604 | }; 605 | name = Debug; 606 | }; 607 | 083D990820C66CCF00873C41 /* Release */ = { 608 | isa = XCBuildConfiguration; 609 | buildSettings = { 610 | ALWAYS_SEARCH_USER_PATHS = NO; 611 | CLANG_ANALYZER_NONNULL = YES; 612 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 613 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 614 | CLANG_CXX_LIBRARY = "libc++"; 615 | CLANG_ENABLE_MODULES = YES; 616 | CLANG_ENABLE_OBJC_ARC = YES; 617 | CLANG_ENABLE_OBJC_WEAK = YES; 618 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 619 | CLANG_WARN_BOOL_CONVERSION = YES; 620 | CLANG_WARN_COMMA = YES; 621 | CLANG_WARN_CONSTANT_CONVERSION = YES; 622 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 623 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 624 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 625 | CLANG_WARN_EMPTY_BODY = YES; 626 | CLANG_WARN_ENUM_CONVERSION = YES; 627 | CLANG_WARN_INFINITE_RECURSION = YES; 628 | CLANG_WARN_INT_CONVERSION = YES; 629 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 630 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 631 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 632 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 633 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 634 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 635 | CLANG_WARN_STRICT_PROTOTYPES = YES; 636 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 637 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 638 | CLANG_WARN_UNREACHABLE_CODE = YES; 639 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 640 | CODE_SIGN_IDENTITY = "iPhone Developer"; 641 | COPY_PHASE_STRIP = NO; 642 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 643 | ENABLE_NS_ASSERTIONS = NO; 644 | ENABLE_STRICT_OBJC_MSGSEND = YES; 645 | GCC_C_LANGUAGE_STANDARD = gnu11; 646 | GCC_NO_COMMON_BLOCKS = YES; 647 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 648 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 649 | GCC_WARN_UNDECLARED_SELECTOR = YES; 650 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 651 | GCC_WARN_UNUSED_FUNCTION = YES; 652 | GCC_WARN_UNUSED_VARIABLE = YES; 653 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 654 | MTL_ENABLE_DEBUG_INFO = NO; 655 | SDKROOT = iphoneos; 656 | SWIFT_COMPILATION_MODE = wholemodule; 657 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 658 | VALIDATE_PRODUCT = YES; 659 | }; 660 | name = Release; 661 | }; 662 | 083D990A20C66CCF00873C41 /* Debug */ = { 663 | isa = XCBuildConfiguration; 664 | baseConfigurationReference = C28A5DC2A6DA4B3A8E756602 /* Pods-SwiftTemplate.debug.xcconfig */; 665 | buildSettings = { 666 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 667 | CODE_SIGN_STYLE = Automatic; 668 | DEVELOPMENT_TEAM = 8964PV2P6Z; 669 | INFOPLIST_FILE = "SwiftTemplate/Supporting Files/Info.plist"; 670 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 671 | LD_RUNPATH_SEARCH_PATHS = ( 672 | "$(inherited)", 673 | "@executable_path/Frameworks", 674 | ); 675 | PRODUCT_BUNDLE_IDENTIFIER = com.pupboss.SwiftTemplate; 676 | PRODUCT_NAME = "$(TARGET_NAME)"; 677 | SWIFT_VERSION = 5.0; 678 | TARGETED_DEVICE_FAMILY = "1,2"; 679 | }; 680 | name = Debug; 681 | }; 682 | 083D990B20C66CCF00873C41 /* Release */ = { 683 | isa = XCBuildConfiguration; 684 | baseConfigurationReference = 12B85DB6F8015DED62A5F6CF /* Pods-SwiftTemplate.release.xcconfig */; 685 | buildSettings = { 686 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 687 | CODE_SIGN_STYLE = Automatic; 688 | DEVELOPMENT_TEAM = 8964PV2P6Z; 689 | INFOPLIST_FILE = "SwiftTemplate/Supporting Files/Info.plist"; 690 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 691 | LD_RUNPATH_SEARCH_PATHS = ( 692 | "$(inherited)", 693 | "@executable_path/Frameworks", 694 | ); 695 | PRODUCT_BUNDLE_IDENTIFIER = com.pupboss.SwiftTemplate; 696 | PRODUCT_NAME = "$(TARGET_NAME)"; 697 | SWIFT_VERSION = 5.0; 698 | TARGETED_DEVICE_FAMILY = "1,2"; 699 | }; 700 | name = Release; 701 | }; 702 | 083D990D20C66CCF00873C41 /* Debug */ = { 703 | isa = XCBuildConfiguration; 704 | buildSettings = { 705 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 706 | BUNDLE_LOADER = "$(TEST_HOST)"; 707 | CODE_SIGN_STYLE = Automatic; 708 | DEVELOPMENT_TEAM = BH46MT8H22; 709 | INFOPLIST_FILE = SwiftTemplateTests/Info.plist; 710 | LD_RUNPATH_SEARCH_PATHS = ( 711 | "$(inherited)", 712 | "@executable_path/Frameworks", 713 | "@loader_path/Frameworks", 714 | ); 715 | PRODUCT_BUNDLE_IDENTIFIER = com.pupboss.SwiftTemplateTests; 716 | PRODUCT_NAME = "$(TARGET_NAME)"; 717 | SWIFT_VERSION = 5.0; 718 | TARGETED_DEVICE_FAMILY = "1,2"; 719 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SwiftTemplate.app/SwiftTemplate"; 720 | }; 721 | name = Debug; 722 | }; 723 | 083D990E20C66CCF00873C41 /* Release */ = { 724 | isa = XCBuildConfiguration; 725 | buildSettings = { 726 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 727 | BUNDLE_LOADER = "$(TEST_HOST)"; 728 | CODE_SIGN_STYLE = Automatic; 729 | DEVELOPMENT_TEAM = BH46MT8H22; 730 | INFOPLIST_FILE = SwiftTemplateTests/Info.plist; 731 | LD_RUNPATH_SEARCH_PATHS = ( 732 | "$(inherited)", 733 | "@executable_path/Frameworks", 734 | "@loader_path/Frameworks", 735 | ); 736 | PRODUCT_BUNDLE_IDENTIFIER = com.pupboss.SwiftTemplateTests; 737 | PRODUCT_NAME = "$(TARGET_NAME)"; 738 | SWIFT_VERSION = 5.0; 739 | TARGETED_DEVICE_FAMILY = "1,2"; 740 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SwiftTemplate.app/SwiftTemplate"; 741 | }; 742 | name = Release; 743 | }; 744 | 083D991020C66CCF00873C41 /* Debug */ = { 745 | isa = XCBuildConfiguration; 746 | buildSettings = { 747 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 748 | CODE_SIGN_STYLE = Automatic; 749 | DEVELOPMENT_TEAM = BH46MT8H22; 750 | INFOPLIST_FILE = SwiftTemplateUITests/Info.plist; 751 | LD_RUNPATH_SEARCH_PATHS = ( 752 | "$(inherited)", 753 | "@executable_path/Frameworks", 754 | "@loader_path/Frameworks", 755 | ); 756 | PRODUCT_BUNDLE_IDENTIFIER = com.pupboss.SwiftTemplateUITests; 757 | PRODUCT_NAME = "$(TARGET_NAME)"; 758 | SWIFT_VERSION = 5.0; 759 | TARGETED_DEVICE_FAMILY = "1,2"; 760 | TEST_TARGET_NAME = SwiftTemplate; 761 | }; 762 | name = Debug; 763 | }; 764 | 083D991120C66CCF00873C41 /* Release */ = { 765 | isa = XCBuildConfiguration; 766 | buildSettings = { 767 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 768 | CODE_SIGN_STYLE = Automatic; 769 | DEVELOPMENT_TEAM = BH46MT8H22; 770 | INFOPLIST_FILE = SwiftTemplateUITests/Info.plist; 771 | LD_RUNPATH_SEARCH_PATHS = ( 772 | "$(inherited)", 773 | "@executable_path/Frameworks", 774 | "@loader_path/Frameworks", 775 | ); 776 | PRODUCT_BUNDLE_IDENTIFIER = com.pupboss.SwiftTemplateUITests; 777 | PRODUCT_NAME = "$(TARGET_NAME)"; 778 | SWIFT_VERSION = 5.0; 779 | TARGETED_DEVICE_FAMILY = "1,2"; 780 | TEST_TARGET_NAME = SwiftTemplate; 781 | }; 782 | name = Release; 783 | }; 784 | /* End XCBuildConfiguration section */ 785 | 786 | /* Begin XCConfigurationList section */ 787 | 083D98DC20C66CCE00873C41 /* Build configuration list for PBXProject "SwiftTemplate" */ = { 788 | isa = XCConfigurationList; 789 | buildConfigurations = ( 790 | 083D990720C66CCF00873C41 /* Debug */, 791 | 083D990820C66CCF00873C41 /* Release */, 792 | ); 793 | defaultConfigurationIsVisible = 0; 794 | defaultConfigurationName = Release; 795 | }; 796 | 083D990920C66CCF00873C41 /* Build configuration list for PBXNativeTarget "SwiftTemplate" */ = { 797 | isa = XCConfigurationList; 798 | buildConfigurations = ( 799 | 083D990A20C66CCF00873C41 /* Debug */, 800 | 083D990B20C66CCF00873C41 /* Release */, 801 | ); 802 | defaultConfigurationIsVisible = 0; 803 | defaultConfigurationName = Release; 804 | }; 805 | 083D990C20C66CCF00873C41 /* Build configuration list for PBXNativeTarget "SwiftTemplateTests" */ = { 806 | isa = XCConfigurationList; 807 | buildConfigurations = ( 808 | 083D990D20C66CCF00873C41 /* Debug */, 809 | 083D990E20C66CCF00873C41 /* Release */, 810 | ); 811 | defaultConfigurationIsVisible = 0; 812 | defaultConfigurationName = Release; 813 | }; 814 | 083D990F20C66CCF00873C41 /* Build configuration list for PBXNativeTarget "SwiftTemplateUITests" */ = { 815 | isa = XCConfigurationList; 816 | buildConfigurations = ( 817 | 083D991020C66CCF00873C41 /* Debug */, 818 | 083D991120C66CCF00873C41 /* Release */, 819 | ); 820 | defaultConfigurationIsVisible = 0; 821 | defaultConfigurationName = Release; 822 | }; 823 | /* End XCConfigurationList section */ 824 | }; 825 | rootObject = 083D98D920C66CCE00873C41 /* Project object */; 826 | } 827 | -------------------------------------------------------------------------------- /SwiftTemplate.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftTemplate.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftTemplate.xcodeproj/xcuserdata/jetlee.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SwiftTemplate.xcscheme 8 | 9 | orderHint 10 | 3 11 | 12 | SwiftTemplate.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 3 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /SwiftTemplate.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /SwiftTemplate.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftTemplate/Controllers/LaunchViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LaunchViewController.swift 3 | // SwiftTemplate 4 | // 5 | // Created by Jie Li on 30/5/18. 6 | // Copyright © 2018 Meltdown Research. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class LaunchViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | 16 | setupAppearance() 17 | setupToast() 18 | } 19 | 20 | override func viewDidAppear(_ animated: Bool) { 21 | super.viewDidAppear(animated) 22 | 23 | APIService.shared.apiAuthToken = "To simulate exsiting users" // To simulate exsiting users 24 | 25 | if APIService.shared.apiAuthToken != nil { 26 | view.makeToastActivity(.center) 27 | 28 | APIService.shared.fetchUserInfo { (result) in 29 | self.view.hideToastActivity() 30 | switch result { 31 | case .success(let userInfo): 32 | // Do something 33 | PublicService.shared.userInfo = userInfo 34 | UIApplication.shared.windows.first!.rootViewController = UINavigationController(rootViewController: NewsViewController()) 35 | break 36 | case .failure(_): 37 | APIService.shared.clearAuthAndReLogin() 38 | } 39 | } 40 | } else { 41 | // APIService.shared.clearAuthAndReLogin() 42 | // UIApplication.shared.windows.first!.rootViewController = 43 | } 44 | } 45 | 46 | func setupAppearance() { 47 | UINavigationBar.appearance().tintColor = .gray 48 | UINavigationBar.appearance().backIndicatorImage = UIImage(named: "icon_nav_back") 49 | UINavigationBar.appearance().backIndicatorTransitionMaskImage = UIImage(named: "icon_nav_back") 50 | 51 | UINavigationBar.appearance().titleTextAttributes = [.foregroundColor: UIColor.tc.shadeTheme] 52 | UINavigationBar.appearance().shadowImage = UIImage() 53 | UINavigationBar.appearance().setBackgroundImage(UIImage(), for: .default) 54 | 55 | UINavigationBar.appearance().isTranslucent = true 56 | 57 | UIBarButtonItem.appearance().setTitleTextAttributes([.foregroundColor: UIColor.clear], for: .normal) 58 | UIBarButtonItem.appearance().setTitleTextAttributes([.foregroundColor: UIColor.clear], for: .highlighted) 59 | 60 | UITabBar.appearance().backgroundColor = UIColor.tc.subBackground 61 | UITabBar.appearance().tintColor = UIColor.tc.tintTheme 62 | } 63 | 64 | func setupToast() { 65 | ToastManager.shared.isTapToDismissEnabled = true 66 | ToastManager.shared.isQueueEnabled = false 67 | let style = ToastStyle() 68 | ToastManager.shared.style = style 69 | ToastManager.shared.duration = 2.0 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /SwiftTemplate/Controllers/NewsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NewsViewController.swift 3 | // SwiftTemplate 4 | // 5 | // Created by Jie Li on 23/7/20. 6 | // Copyright © 2020 Meltdown Research. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import MJRefresh 11 | import SafariServices 12 | 13 | class NewsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIViewControllerPreviewingDelegate { 14 | 15 | let cellReuseIdentifier = "NewsTableViewCellIdentifier" 16 | 17 | var newsListPage = 1 18 | let newsListPageSize = 15 19 | 20 | var news: [NewsModel] = [] 21 | 22 | lazy var tableView: UITableView = { 23 | let tableView = UITableView() 24 | tableView.delegate = self 25 | tableView.dataSource = self 26 | tableView.backgroundColor = UIColor.tc.subBackground 27 | tableView.register(NewsTableViewCell.self, forCellReuseIdentifier: cellReuseIdentifier) 28 | tableView.tableFooterView = UIView() 29 | tableView.rowHeight = UITableView.automaticDimension 30 | tableView.estimatedRowHeight = 200 31 | tableView.separatorStyle = .none 32 | return tableView 33 | }() 34 | 35 | override func viewDidLoad() { 36 | super.viewDidLoad() 37 | view.backgroundColor = UIColor.tc.background 38 | navigationItem.title = "News" 39 | 40 | view.addSubview(tableView) 41 | tableView.snp.makeConstraints { (make) in 42 | make.edges.equalTo(view) 43 | } 44 | 45 | tableView.mj_header = MJRefreshNormalHeader.init(refreshingBlock: { 46 | self.fetchDataFromServer() 47 | }) 48 | tableView.mj_header?.isAutomaticallyChangeAlpha = true 49 | 50 | tableView.mj_footer = MJRefreshAutoNormalFooter.init(refreshingBlock: { 51 | self.appendDataFromServer() 52 | }) 53 | 54 | tableView.mj_header?.beginRefreshing() 55 | 56 | registerForPreviewing(with: self, sourceView: tableView) 57 | } 58 | 59 | func fetchDataFromServer() { 60 | 61 | newsListPage = 1 62 | let path = "/api/v1/news.json" 63 | 64 | APIService.shared.requestDecodable(method: .get, path: path, params: ["pageSize": newsListPageSize, "page": newsListPage], paramsType: .form, decodableType: [NewsModel].self) { (result) in 65 | switch result { 66 | case .success(let value): 67 | self.news.removeAll() 68 | self.news.append(contentsOf: value) 69 | 70 | if self.news.count > 0 { 71 | self.tableView.mj_footer?.resetNoMoreData() 72 | } else { 73 | self.tableView.mj_footer?.endRefreshingWithNoMoreData() 74 | } 75 | 76 | self.tableView.reloadData() 77 | self.tableView.mj_header?.endRefreshing() 78 | self.newsListPage = 2 79 | case .failure(let error): 80 | self.view.makeToast(error.message) 81 | } 82 | } 83 | } 84 | 85 | func appendDataFromServer() { 86 | 87 | APIService.shared.requestDecodable(method: .get, path: "/api/v1/news.json", params: ["pageSize": newsListPageSize, "page": newsListPage], paramsType: .form, decodableType: [NewsModel].self) { (result) in 88 | switch result { 89 | case .success(let value): 90 | 91 | if value.count > 0 { 92 | self.news.append(contentsOf: value) 93 | self.tableView.reloadData() 94 | self.newsListPage += 1 95 | 96 | self.tableView.mj_footer?.endRefreshing() 97 | } else { 98 | self.tableView.mj_footer?.endRefreshingWithNoMoreData() 99 | } 100 | case .failure(let error): 101 | self.view.makeToast(error.message) 102 | } 103 | } 104 | } 105 | 106 | func numberOfSections(in tableView: UITableView) -> Int { 107 | return 1 108 | } 109 | 110 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 111 | return news.count 112 | } 113 | 114 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 115 | let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier, for: indexPath) as! NewsTableViewCell 116 | cell.new = news[indexPath.row] 117 | return cell 118 | } 119 | 120 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 121 | tableView.deselectRow(at: indexPath, animated: true) 122 | 123 | if let url = URL(string: news[indexPath.row].url) { 124 | let vc = SFSafariViewController(url: url) 125 | present(vc, animated: true) 126 | } 127 | } 128 | 129 | func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? { 130 | 131 | let indexPath = tableView.indexPathForRow(at: location)! 132 | let cell = tableView.cellForRow(at: indexPath) as! NewsTableViewCell 133 | previewingContext.sourceRect = cell.frame 134 | 135 | if let url = URL(string: news[indexPath.row].url) { 136 | let vc = SFSafariViewController(url: url) 137 | vc.preferredContentSize = .zero 138 | return vc 139 | } 140 | return nil 141 | } 142 | 143 | func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) { 144 | present(viewControllerToCommit, animated: true) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /SwiftTemplate/Controllers/ScanViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScanViewController.swift 3 | // SwiftTemplate 4 | // 5 | // Created by Jie Li on 5/6/18. 6 | // Copyright © 2018 Meltdown Research. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SnapKit 11 | 12 | class ScanViewController: UIViewController { 13 | 14 | lazy var sessionManager: AVCaptureSessionManager? = { 15 | var sManager: AVCaptureSessionManager? = nil 16 | AVCaptureSessionManager.checkAuthorizationStatusForCamera(grant: { 17 | sManager = AVCaptureSessionManager(captureType: .both, scanRect: .null, success: { (result) in 18 | if let r = result { 19 | self.scanFinishedWithResult(result: r) 20 | } 21 | }) 22 | sManager!.showPreViewLayerIn(view: self.view) 23 | sManager!.isPlaySound = true 24 | }, denied: { 25 | let action = UIAlertAction(title: "OK", style: .default, handler: { (action) in 26 | let url = URL(string: UIApplication.openSettingsURLString) 27 | UIApplication.shared.open(url!, options: [:], completionHandler: nil) 28 | }) 29 | let alert = UIAlertController(title: "Permission denied", message: "Please allow camera permission to make barcode detection", preferredStyle: .alert) 30 | alert.addAction(action) 31 | self.present(alert, animated: true, completion: nil) 32 | }) 33 | return sManager 34 | }() 35 | 36 | lazy var borderImageView: UIImageView = { 37 | let borderImageView = UIImageView(image: UIImage(named: "img_qrcode_border")) 38 | borderImageView.contentMode = .scaleToFill 39 | return borderImageView 40 | }() 41 | 42 | var onScreen = false 43 | 44 | override func viewDidLoad() { 45 | super.viewDidLoad() 46 | view.backgroundColor = UIColor.tc.background 47 | 48 | view.addSubview(borderImageView) 49 | setupConstraints() 50 | } 51 | 52 | override func viewDidAppear(_ animated: Bool) { 53 | super.viewDidAppear(animated) 54 | 55 | if let sessionManager = sessionManager { 56 | sessionManager.start() 57 | } 58 | UIApplication.shared.isIdleTimerDisabled = true 59 | navigationController?.isNavigationBarHidden = true 60 | onScreen = true 61 | } 62 | 63 | override func viewWillDisappear(_ animated: Bool) { 64 | super.viewWillDisappear(animated) 65 | 66 | UIApplication.shared.isIdleTimerDisabled = false 67 | navigationController?.isNavigationBarHidden = false 68 | onScreen = false 69 | } 70 | 71 | override func viewDidDisappear(_ animated: Bool) { 72 | super.viewDidDisappear(animated) 73 | 74 | if let sessionManager = sessionManager { 75 | sessionManager.stop() 76 | } 77 | } 78 | 79 | func scanFinishedWithResult(result: String) { 80 | if !onScreen { 81 | return 82 | } 83 | 84 | let action = UIAlertAction(title: "OK", style: .default) { (_) in 85 | 86 | if let sessionManager = self.sessionManager { 87 | sessionManager.start() 88 | } 89 | } 90 | let alert = UIAlertController(title: "Content", message: result, preferredStyle: .alert) 91 | alert.addAction(action) 92 | self.present(alert, animated: true, completion: nil) 93 | } 94 | 95 | func setupConstraints() { 96 | borderImageView.snp.makeConstraints { (make) in 97 | make.center.equalTo(view) 98 | make.width.equalTo(250) 99 | make.height.equalTo(155) 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /SwiftTemplate/Extensions/Foundation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Foundation.swift 3 | // SwiftTemplate 4 | // 5 | // Created by Jie Li on 29/5/18. 6 | // Copyright © 2018 Meltdown Research. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Date { 12 | 13 | func formattedString(withDateFormat dateFormat: String) -> String { 14 | 15 | let f = DateFormatter() 16 | f.dateFormat = dateFormat 17 | return f.string(from: self) 18 | } 19 | 20 | var timeAgoString: String { 21 | let calendar = Calendar.current 22 | let now = Date() 23 | let unitFlags: NSCalendar.Unit = [.second, .minute, .hour, .day, .weekOfYear, .month, .year] 24 | let components = (calendar as NSCalendar).components(unitFlags, from: self, to: now, options: []) 25 | 26 | if let year = components.year, year >= 2 { 27 | return "\(year) years ago" 28 | } 29 | 30 | if let year = components.year, year >= 1 { 31 | return "Last year" 32 | } 33 | 34 | if let month = components.month, month >= 2 { 35 | return "\(month) months ago" 36 | } 37 | 38 | if let month = components.month, month >= 1 { 39 | return "Last month" 40 | } 41 | 42 | if let week = components.weekOfYear, week >= 2 { 43 | return "\(week) weeks ago" 44 | } 45 | 46 | if let week = components.weekOfYear, week >= 1 { 47 | return "Last week" 48 | } 49 | 50 | if let day = components.day, day >= 2 { 51 | return "\(day) days ago" 52 | } 53 | 54 | if let day = components.day, day >= 1 { 55 | return "Yesterday" 56 | } 57 | 58 | if let hour = components.hour, hour >= 2 { 59 | return "\(hour) hours ago" 60 | } 61 | 62 | if let hour = components.hour, hour >= 1 { 63 | return "An hour ago" 64 | } 65 | 66 | if let minute = components.minute, minute >= 2 { 67 | return "\(minute) minutes ago" 68 | } 69 | 70 | if let minute = components.minute, minute >= 1 { 71 | return "A minute ago" 72 | } 73 | 74 | if let second = components.second, second >= 3 { 75 | return "\(second) seconds ago" 76 | } 77 | 78 | return "Just now" 79 | } 80 | } 81 | 82 | extension Bool { 83 | var intValue: Int { 84 | return self ? 1 : 0 85 | } 86 | } 87 | 88 | extension String { 89 | 90 | func trimLeft(withTargetString target: String) -> String { 91 | 92 | if hasPrefix(target) { 93 | let aString = String(suffix(from: target.endIndex)) 94 | return aString.trimLeft(withTargetString:target) 95 | } else { 96 | return self 97 | } 98 | } 99 | 100 | var notificationName: NSNotification.Name { 101 | return NSNotification.Name(rawValue: self) 102 | } 103 | 104 | var fromBase64: String? { 105 | guard let data = Data(base64Encoded: self, options: Data.Base64DecodingOptions(rawValue: 0)) else { 106 | return nil 107 | } 108 | 109 | return String(data: data as Data, encoding: String.Encoding.utf8) 110 | } 111 | 112 | var toBase64: String? { 113 | guard let data = self.data(using: String.Encoding.utf8) else { 114 | return nil 115 | } 116 | 117 | return data.base64EncodedString(options: Data.Base64EncodingOptions(rawValue: 0)) 118 | } 119 | 120 | var isValidCreditCard: Bool { 121 | let regex = "((?:4\\d{3})|(?:5[1-5]\\d{2})|(?:6011)|(?:3[68]\\d{2})|(?:30[012345]\\d))[ -]?(\\d{4})[ -]?(\\d{4})[ -]?(\\d{4}|3[4,7]\\d{13})$" 122 | let predicate = NSPredicate(format: "SELF MATCHES %@", regex) 123 | return predicate.evaluate(with: self) 124 | } 125 | 126 | var isValidEmail: Bool { 127 | let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" 128 | 129 | let emailPred = NSPredicate(format:"SELF MATCHES %@", emailRegEx) 130 | return emailPred.evaluate(with: self) 131 | } 132 | } 133 | 134 | extension Double { 135 | 136 | func priceString() -> String { 137 | 138 | return String(format: "%0.2f", self) 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /SwiftTemplate/Extensions/UIKit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIKit.swift 3 | // SwiftTemplate 4 | // 5 | // Created by Jie Li on 31/5/18. 6 | // Copyright © 2018 Meltdown Research. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIColor { 12 | 13 | convenience init(r: CGFloat, g: CGFloat, b: CGFloat) { 14 | self.init(red: r/255, green: g/255, blue: b/255, alpha: 1) 15 | } 16 | 17 | /// tc means theme color 18 | struct tc { 19 | static var background: UIColor { return .white } 20 | static var subBackground: UIColor { return UIColor(r: 250, g: 250, b: 250) } 21 | static var theme: UIColor { return UIColor(r: 60, g: 50, b: 171) } 22 | static var shadeTheme: UIColor { return UIColor(r: 60, g: 50, b: 171) } 23 | static var tintTheme: UIColor { return UIColor(r: 183, g: 162, b: 222) } 24 | static var line: UIColor { return UIColor(r: 217, g: 217, b: 217) } 25 | static var bodyText: UIColor { return UIColor(r: 23, g: 22, b: 22) } 26 | static var descText: UIColor { return .lightGray } 27 | static var disableText: UIColor { return .lightGray } 28 | static var red: UIColor { return UIColor(r: 241, g: 62, b: 58) } 29 | static var green: UIColor { return UIColor(r: 0, g: 170, b: 59) } 30 | } 31 | } 32 | 33 | extension UILabel { 34 | func changeCharacterSpace(_ space: CGFloat) { 35 | text = text ?? "" 36 | let attributedString = NSMutableAttributedString(string: text!, attributes: [.kern: space]) 37 | let paragraphStyle = NSMutableParagraphStyle() 38 | attributedString.addAttributes([.paragraphStyle: paragraphStyle], range: NSMakeRange(0, text!.count)) 39 | 40 | attributedText = attributedString 41 | } 42 | } 43 | 44 | extension UITextField { 45 | func changeCharacterSpace(_ space: CGFloat) { 46 | text = text ?? "" 47 | let attributedString = NSMutableAttributedString(string: text!, attributes: [.kern: space]) 48 | let paragraphStyle = NSMutableParagraphStyle() 49 | attributedString.addAttributes([.paragraphStyle: paragraphStyle], range: NSMakeRange(0, text!.count)) 50 | 51 | attributedText = attributedString 52 | } 53 | } 54 | 55 | extension UIImage { 56 | 57 | class func fromColor(_ color: UIColor) -> UIImage { 58 | 59 | let rect = CGRect(origin: .zero, size: CGSize(width: 1, height: 1)) 60 | UIGraphicsBeginImageContext(rect.size) 61 | let context = UIGraphicsGetCurrentContext()! 62 | context.setFillColor(color.cgColor) 63 | context.fill(rect) 64 | let newImage = UIGraphicsGetImageFromCurrentImageContext() 65 | UIGraphicsEndImageContext() 66 | return newImage! 67 | } 68 | 69 | class func fromView(_ view: UIView) -> UIImage { 70 | 71 | UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0.0) 72 | view.layer.render(in: UIGraphicsGetCurrentContext()!) 73 | let image = UIGraphicsGetImageFromCurrentImageContext() 74 | UIGraphicsEndImageContext() 75 | return image ?? UIImage() 76 | } 77 | } 78 | 79 | extension UIScreen { 80 | 81 | private static let step: CGFloat = 0.1 82 | 83 | static func animateBrightness(to value: CGFloat) { 84 | guard abs(UIScreen.main.brightness - value) > step else { return } 85 | 86 | let delta = UIScreen.main.brightness > value ? -step : step 87 | 88 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) { 89 | UIScreen.main.brightness += delta 90 | animateBrightness(to: value) 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /SwiftTemplate/Models/AuthenticationModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuthenticationModel.swift 3 | // MeltdownPlatform 4 | // 5 | // Created by Jie Li on 27/6/20. 6 | // Copyright © 2020 Jie Li. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum UserStatus : Int, Codable { 12 | 13 | case normal = 0 14 | case suspended 15 | } 16 | 17 | struct AuthenticationModel : Codable { 18 | 19 | let loginToken: String 20 | let role: String 21 | let userStatus: UserStatus 22 | var userStatusString: String { 23 | switch userStatus { 24 | case .normal: 25 | return "Normal" 26 | case .suspended: 27 | return "Suspended" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /SwiftTemplate/Models/ErrorModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ErrorModel.swift 3 | // SwiftTemplate 4 | // 5 | // Created by Jie Li on 1/6/18. 6 | // Copyright © 2018 Meltdown Research. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct ErrorModel: Error, Codable { 12 | 13 | let code: Int 14 | let message: String 15 | } 16 | -------------------------------------------------------------------------------- /SwiftTemplate/Models/NewsModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NewsModel.swift 3 | // MeltdownPlatform 4 | // 5 | // Created by Jie Li on 29/6/20. 6 | // Copyright © 2020 Meltdown Research. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct NewsModel: Codable { 12 | let sourceName: String 13 | let author: String? 14 | let title: String 15 | let description: String? 16 | let url: String 17 | let imageUrl: String? 18 | let country: String 19 | let category: String 20 | let publishedAt: Date 21 | } 22 | -------------------------------------------------------------------------------- /SwiftTemplate/Models/UserInfoModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserInfoModel.swift 3 | // SwiftTemplate 4 | // 5 | // Created by Jie Li on 5/6/18. 6 | // Copyright © 2018 Meltdown Research. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct UserInfoModel : Codable { 12 | let userId: String 13 | let email: String 14 | let name: String 15 | let publicKey: String 16 | let role: String 17 | let userStatus: UserStatus 18 | } 19 | -------------------------------------------------------------------------------- /SwiftTemplate/Services/APIService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APIService.swift 3 | // SwiftTemplate 4 | // 5 | // Created by Jie Li on 28/2/20. 6 | // Copyright © 2020 Meltdown Research. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Alamofire 11 | 12 | public enum ParamsType : Int { 13 | 14 | case json 15 | 16 | case form 17 | } 18 | 19 | final class AccessTokenInterceptor: RequestInterceptor { 20 | 21 | func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result) -> Void) { 22 | guard urlRequest.url?.absoluteString.hasPrefix(Constants.apiHost + "/v1/auth/") == false else { 23 | return completion(.success(urlRequest)) 24 | } 25 | 26 | var urlRequest = urlRequest 27 | urlRequest.headers.add(HTTPHeader(name: "Authorizationxxx", value: APIService.shared.apiAuthToken ?? "")) 28 | completion(.success(urlRequest)) 29 | } 30 | } 31 | 32 | class APIService { 33 | static let shared = APIService() 34 | let manager = NetworkReachabilityManager(host: Constants.apiHost) 35 | 36 | // Properties 37 | var apiAuthToken: String? 38 | var networkReachable = true 39 | 40 | private lazy var session: Session = { 41 | let configuration = URLSessionConfiguration.af.default 42 | let uaHeader = HTTPHeader.userAgent("\(PublicService.shared.bundleName)/\(PublicService.shared.versionString)" + " (\(PublicService.shared.deviceModel); iOS\(PublicService.shared.systemVersion))") 43 | configuration.headers.add(uaHeader) 44 | let session = Session(configuration: configuration, interceptor:AccessTokenInterceptor()) 45 | 46 | return session 47 | }() 48 | 49 | private init() { 50 | 51 | apiAuthToken = UserDefaults.standard.object(forKey: Constants.authTokenDefaultsKey) as? String 52 | manager?.startListening { status in 53 | print("Network Status Changed: \(status)") 54 | } 55 | } 56 | 57 | func clearAuthAndReLogin() { 58 | UserDefaults.standard.set(nil, forKey: Constants.authTokenDefaultsKey) 59 | apiAuthToken = nil 60 | 61 | // UIApplication.shared.windows.first!.rootViewController = 62 | } 63 | 64 | func fetchUserInfo(completionHandler: @escaping (Result) -> Void) { 65 | 66 | requestDecodable(path: "/api/v1/user.json", decodableType: UserInfoModel.self, completionHandler: completionHandler) 67 | } 68 | 69 | func requestDecodable(path: String, decodableType: T.Type, completionHandler: @escaping (Result) -> Void) { 70 | requestDecodable(method: .get, path: path, params: nil, paramsType: .form, decodableType: decodableType, completionHandler: completionHandler) 71 | } 72 | 73 | func requestDecodable(method: HTTPMethod, path: String, params: [String: Any]?, paramsType: ParamsType, decodableType: T.Type, completionHandler: @escaping (Result) -> Void) { 74 | 75 | let requestURL = Constants.apiHost + path 76 | 77 | let paramsEncoding: ParameterEncoding = paramsType == .form ? URLEncoding.default : JSONEncoding.default 78 | 79 | let decoder = JSONDecoder() 80 | let formatter = DateFormatter() 81 | formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" 82 | decoder.dateDecodingStrategy = .formatted(formatter) 83 | session.request(requestURL, method: method, parameters: params, encoding: paramsEncoding).validate(statusCode: 200..<300).responseDecodable(of: decodableType, decoder: decoder) { response in 84 | print( 85 | """ 86 | [Request] 87 | \(method.rawValue) \(String(describing: params)) 88 | \(requestURL) 89 | [Response] 90 | \(response.response?.statusCode ?? 999) \(String(decoding: response.data ?? Data(), as: UTF8.self)) 91 | """ 92 | ) 93 | switch response.result { 94 | case .success(let value): 95 | completionHandler(.success(value)) 96 | case .failure(let anError): 97 | let statusCode = response.response?.statusCode ?? 999 98 | 99 | if statusCode == 401 { 100 | self.clearAuthAndReLogin() 101 | } 102 | 103 | guard let error = try? JSONDecoder().decode(ErrorModel.self, from: response.data ?? Data()) else { 104 | completionHandler(.failure(ErrorModel(code: statusCode, message: anError.localizedDescription))) 105 | return 106 | } 107 | completionHandler(.failure(error)) 108 | } 109 | } 110 | } 111 | 112 | func requestJSON(method: HTTPMethod, path: String, params: [String: Any]?, paramsType: ParamsType, completionHandler: @escaping (Result) -> Void) { 113 | 114 | let requestURL = Constants.apiHost + path 115 | 116 | let paramsEncoding: ParameterEncoding = paramsType == .form ? URLEncoding.default : JSONEncoding.default 117 | 118 | session.request(requestURL, method: method, parameters: params, encoding: paramsEncoding).validate(statusCode: 200..<300).responseJSON { response in 119 | print( 120 | """ 121 | [Request] 122 | \(method.rawValue) \(String(describing: params)) 123 | \(requestURL) 124 | [Response] 125 | \(response.response?.statusCode ?? 999) \(String(decoding: response.data ?? Data(), as: UTF8.self)) 126 | """ 127 | ) 128 | switch response.result { 129 | case .success(let value): 130 | completionHandler(.success(value)) 131 | case .failure(let anError): 132 | let statusCode = response.response?.statusCode ?? 999 133 | 134 | if statusCode == 401 { 135 | self.clearAuthAndReLogin() 136 | } 137 | 138 | guard let error = try? JSONDecoder().decode(ErrorModel.self, from: response.data ?? Data()) else { 139 | completionHandler(.failure(ErrorModel(code: statusCode, message: anError.localizedDescription))) 140 | return 141 | } 142 | completionHandler(.failure(error)) 143 | } 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /SwiftTemplate/Services/ApplePayService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ApplePayService.swift 3 | // SwiftTemplate 4 | // 5 | // Created by Jie Li on 31/5/18. 6 | // Copyright © 2018 Meltdown Research. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import PassKit 11 | 12 | class ApplePayService { 13 | 14 | class func deviceSupportsApplePay() -> Bool { 15 | return PKPaymentAuthorizationViewController.canMakePayments(usingNetworks: supportedPKPaymentNetworks()) 16 | } 17 | class func supportedPKPaymentNetworks() -> [PKPaymentNetwork] { 18 | return [.visa, .masterCard] 19 | } 20 | class func parameters(forPayment payment: PKPayment) -> [String: Any] { 21 | let paymentString = String.init(data: payment.token.paymentData, encoding: .utf8) 22 | var payload: [String: Any] = Dictionary() 23 | payload["pk_token"] = paymentString 24 | 25 | if let contact = payment.billingContact { 26 | payload["card"] = addressParamsFromPKContact(billingContact: contact) 27 | } 28 | 29 | guard !(paymentString?.count == 0 && Constants.stripeAppKey.hasPrefix("pk_live")) else { 30 | return payload 31 | } 32 | 33 | if let paymentInstrumentName = payment.token.paymentMethod.displayName { 34 | payload["pk_token_instrument_name"] = paymentInstrumentName 35 | } 36 | 37 | if let paymentNetwork = payment.token.paymentMethod.network { 38 | payload["pk_token_payment_network"] = paymentNetwork 39 | } 40 | 41 | var transactionIdentifier = payment.token.transactionIdentifier 42 | 43 | if transactionIdentifier == "Simulated Identifier" { 44 | transactionIdentifier = testTransactionIdentifier() 45 | } 46 | 47 | payload["pk_token_transaction_id"] = transactionIdentifier 48 | 49 | return payload 50 | } 51 | 52 | class func paymentRequest(withMerchantIdentifier merchantIdentifier: String, country: String, currency: String) -> PKPaymentRequest { 53 | let paymentRequest = PKPaymentRequest() 54 | paymentRequest.merchantIdentifier = merchantIdentifier 55 | paymentRequest.supportedNetworks = supportedPKPaymentNetworks() 56 | paymentRequest.merchantCapabilities = .capability3DS 57 | paymentRequest.countryCode = country.uppercased() 58 | paymentRequest.currencyCode = currency.uppercased() 59 | return paymentRequest 60 | } 61 | 62 | private class func addressParamsFromPKContact(billingContact: PKContact) -> [String: String]? { 63 | var params: [String: String] = [:] 64 | if let nameComponents = billingContact.name { 65 | params["name"] = PersonNameComponentsFormatter.localizedString(from: nameComponents, style: .default, options: PersonNameComponentsFormatter.Options.init(rawValue: 0)) 66 | } 67 | if let address = billingContact.postalAddress { 68 | params["address_line1"] = address.street 69 | params["address_city"] = address.city 70 | params["address_state"] = address.state 71 | params["address_zip"] = address.postalCode 72 | params["address_country"] = address.isoCountryCode 73 | } 74 | 75 | return params 76 | } 77 | 78 | private class func testTransactionIdentifier() -> String { 79 | var uuid = UUID().uuidString 80 | uuid = uuid.replacingOccurrences(of: "~", with: "") 81 | 82 | let number = "4242424242424242" 83 | let cents = "100" 84 | let identifier = ["ApplePayStubs", number, cents, "SGD", uuid].joined(separator: "~") 85 | return identifier 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /SwiftTemplate/Services/PublicService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PublicService.swift 3 | // MeltdownPlatform 4 | // 5 | // Created by Jie Li on 2/7/20. 6 | // Copyright © 2020 Meltdown Research. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class PublicService { 12 | static let shared = PublicService() 13 | 14 | var displayName: String 15 | var bundleName: String 16 | var versionString: String 17 | var buildString: String 18 | var deviceModel: String 19 | var systemVersion: String 20 | 21 | var userInfo: UserInfoModel 22 | 23 | init() { 24 | userInfo = UserInfoModel(userId: "", email: "", name: "", publicKey: "", role: "", userStatus: .normal) 25 | displayName = Bundle.main.infoDictionary?["CFBundleDisplayName"] as? String ?? "" 26 | bundleName = Bundle.main.infoDictionary?["CFBundleName"] as? String ?? "" 27 | versionString = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "" 28 | buildString = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "" 29 | systemVersion = UIDevice.current.systemVersion 30 | deviceModel = PublicService.getDeviceModel() 31 | } 32 | 33 | 34 | 35 | private class func getDeviceModel() -> String { 36 | var systemInfo = utsname() 37 | uname(&systemInfo) 38 | let machineMirror = Mirror(reflecting: systemInfo.machine) 39 | let identifier = machineMirror.children.reduce("") { identifier, element in 40 | guard let value = element.value as? Int8, value != 0 else { return identifier } 41 | return identifier + String(UnicodeScalar(UInt8(value))) 42 | } 43 | 44 | let modelDict = [ 45 | "AppleTV1,1": "Apple TV 1", 46 | "AppleTV2,1": "Apple TV 2", 47 | "AppleTV3,1": "Apple TV 3", 48 | "AppleTV3,2": "Apple TV 3", 49 | "AppleTV5,3": "Apple TV 4", 50 | "AppleTV6,2": "Apple TV 4K", 51 | 52 | "Watch1,1": "Apple Watch 1", 53 | "Watch1,2": "Apple Watch 1", 54 | "Watch2,6": "Apple Watch Series 1", 55 | "Watch2,7": "Apple Watch Series 1", 56 | "Watch2,3": "Apple Watch Series 2", 57 | "Watch2,4": "Apple Watch Series 2", 58 | "Watch3,1": "Apple Watch Series 3", 59 | "Watch3,2": "Apple Watch Series 3", 60 | "Watch3,3": "Apple Watch Series 3", 61 | "Watch3,4": "Apple Watch Series 3", 62 | "Watch4,1": "Apple Watch Series 4", 63 | "Watch4,2": "Apple Watch Series 4", 64 | "Watch4,3": "Apple Watch Series 4", 65 | "Watch4,4": "Apple Watch Series 4", 66 | 67 | "AudioAccessory1,1": "HomePod", 68 | "AudioAccessory1,2": "HomePod", 69 | 70 | "iPhone1,1": "iPhone 2G", 71 | "iPhone1,2": "iPhone 3G", 72 | "iPhone2,1": "iPhone 3GS", 73 | "iPhone3,1": "iPhone 4", 74 | "iPhone3,2": "iPhone 4", 75 | "iPhone3,3": "iPhone 4", 76 | "iPhone4,1": "iPhone 4s", 77 | "iPhone5,1": "iPhone 5", 78 | "iPhone5,2": "iPhone 5", 79 | "iPhone5,3": "iPhone 5c", 80 | "iPhone5,4": "iPhone 5c", 81 | "iPhone6,1": "iPhone 5s", 82 | "iPhone6,2": "iPhone 5s", 83 | "iPhone7,1": "iPhone 6 Plus", 84 | "iPhone7,2": "iPhone 6", 85 | "iPhone8,1": "iPhone 6s", 86 | "iPhone8,2": "iPhone 6s Plus", 87 | "iPhone8,4": "iPhone SE", 88 | "iPhone9,1": "iPhone 7", 89 | "iPhone9,3": "iPhone 7", 90 | "iPhone9,2": "iPhone 7 Plus", 91 | "iPhone9,4": "iPhone 7 Plus", 92 | "iPhone10,1": "iPhone 8", 93 | "iPhone10,4": "iPhone 8", 94 | "iPhone10,2": "iPhone 8 Plus", 95 | "iPhone10,5": "iPhone 8 Plus", 96 | "iPhone10,3": "iPhone X", 97 | "iPhone10,6": "iPhone X", 98 | "iPhone11,8": "iPhone XR", 99 | "iPhone11,2": "iPhone XS", 100 | "iPhone11,6": "iPhone XS Max", 101 | "iPhone12,1": "iPhone 11", 102 | "iPhone12,3": "iPhone 11 Pro", 103 | "iPhone12,5": "iPhone 11 Pro Max", 104 | "iPhone12,8": "iPhone SE 2", 105 | 106 | "iPod1,1": "iPod Touch 1", 107 | "iPod2,1": "iPod Touch 2", 108 | "iPod3,1": "iPod Touch 3", 109 | "iPod4,1": "iPod Touch 4", 110 | "iPod5,1": "iPod Touch 5", 111 | "iPod7,1": "iPod Touch 6", 112 | "iPod9,1": "iPod Touch 7", 113 | 114 | "iPad1,1": "iPad 1", 115 | "iPad2,1": "iPad 2", 116 | "iPad2,2": "iPad 2", 117 | "iPad2,3": "iPad 2", 118 | "iPad2,4": "iPad 2", 119 | "iPad3,1": "iPad 3", 120 | "iPad3,2": "iPad 3", 121 | "iPad3,3": "iPad 3", 122 | "iPad3,4": "iPad 4", 123 | "iPad3,5": "iPad 4", 124 | "iPad3,6": "iPad 4", 125 | "iPad6,11": "iPad 5", 126 | "iPad6,12": "iPad 5", 127 | "iPad7,5": "iPad 6", 128 | "iPad7,6": "iPad 6", 129 | "iPad7,11": "iPad 7", 130 | "iPad7,12": "iPad 7", 131 | "iPad4,1": "iPad Air", 132 | "iPad4,2": "iPad Air", 133 | "iPad4,3": "iPad Air", 134 | "iPad5,3": "iPad Air 2", 135 | "iPad5,4": "iPad Air 2", 136 | "iPad11,3": "iPad Air 3", 137 | "iPad11,4": "iPad Air 3", 138 | "iPad6,7": "iPad Pro (12.9 inch)", 139 | "iPad6,8": "iPad Pro (12.9 inch)", 140 | "iPad6,3": "iPad Pro (9.7 inch)", 141 | "iPad6,4": "iPad Pro (9.7 inch)", 142 | "iPad7,1": "iPad Pro (12.9 inch) 2", 143 | "iPad7,2": "iPad Pro (12.9 inch) 2", 144 | "iPad7,3": "iPad Pro (10.5 inch)", 145 | "iPad7,4": "iPad Pro (10.5 inch)", 146 | "iPad8,1": "iPad Pro (11 inch)", 147 | "iPad8,2": "iPad Pro (11 inch)", 148 | "iPad8,3": "iPad Pro (11 inch)", 149 | "iPad8,4": "iPad Pro (11 inch)", 150 | "iPad8,5": "iPad Pro (12.9 inch) 3", 151 | "iPad8,6": "iPad Pro (12.9 inch) 3", 152 | "iPad8,7": "iPad Pro (12.9 inch) 3", 153 | "iPad8,8": "iPad Pro (12.9 inch) 3", 154 | "iPad8,9": "iPad Pro (11 inch) 2", 155 | "iPad8,10": "iPad Pro (11 inch) 2", 156 | "iPad8,11": "iPad Pro (12.9 inch) 4", 157 | "iPad8,12": "iPad Pro (12.9 inch) 4", 158 | "iPad2,5": "iPad mini 1", 159 | "iPad2,6": "iPad mini 1", 160 | "iPad2,7": "iPad mini 1", 161 | "iPad4,4": "iPad mini 2", 162 | "iPad4,5": "iPad mini 2", 163 | "iPad4,6": "iPad mini 2", 164 | "iPad4,7": "iPad mini 3", 165 | "iPad4,8": "iPad mini 3", 166 | "iPad4,9": "iPad mini 3", 167 | "iPad5,1": "iPad mini 4", 168 | "iPad5,2": "iPad mini 4", 169 | "iPad11,1": "iPad mini 5", 170 | "iPad11,2": "iPad mini 5", 171 | "i386": "iPhone Simulator", 172 | "x86_64": "iPhone Simulator", 173 | ] 174 | return modelDict[identifier] ?? identifier 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /SwiftTemplate/Supporting Files/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SwiftTemplate 4 | // 5 | // Created by Jie Li on 5/6/18. 6 | // Copyright © 2018 Meltdown Research. 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: [UIApplication.LaunchOptionsKey: 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 | -------------------------------------------------------------------------------- /SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "headiconiPhoneNotification_20pt@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "headiconiPhoneNotification_20pt@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "headiconiPhoneSpootlight5_29pt@2x.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "headiconiPhoneSpootlight5_29pt@3x.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "40x40", 29 | "idiom" : "iphone", 30 | "filename" : "headiconiPhoneSpootlight7_40pt@2x.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "headiconiPhoneSpootlight7_40pt@3x.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "60x60", 41 | "idiom" : "iphone", 42 | "filename" : "headiconiPhoneApp_60pt@2x.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "headiconiPhoneApp_60pt@3x.png", 49 | "scale" : "3x" 50 | }, 51 | { 52 | "size" : "20x20", 53 | "idiom" : "ipad", 54 | "filename" : "headiconiPadNotifications_20pt.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "headiconiPadNotifications_20pt@2x.png", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "size" : "29x29", 65 | "idiom" : "ipad", 66 | "filename" : "headiconiPadSpootlight5_29pt.png", 67 | "scale" : "1x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "headiconiPadSpootlight5_29pt@2x.png", 73 | "scale" : "2x" 74 | }, 75 | { 76 | "size" : "40x40", 77 | "idiom" : "ipad", 78 | "filename" : "headiconiPadSpootlight7_40pt.png", 79 | "scale" : "1x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "headiconiPadSpootlight7_40pt@2x.png", 85 | "scale" : "2x" 86 | }, 87 | { 88 | "size" : "76x76", 89 | "idiom" : "ipad", 90 | "filename" : "headiconiPadApp_76pt.png", 91 | "scale" : "1x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "headiconiPadApp_76pt@2x.png", 97 | "scale" : "2x" 98 | }, 99 | { 100 | "size" : "83.5x83.5", 101 | "idiom" : "ipad", 102 | "filename" : "headiconiPadProApp_83.5pt@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "1024x1024", 107 | "idiom" : "ios-marketing", 108 | "filename" : "headiconstore_1024pt.png", 109 | "scale" : "1x" 110 | } 111 | ], 112 | "info" : { 113 | "version" : 1, 114 | "author" : "xcode" 115 | } 116 | } -------------------------------------------------------------------------------- /SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPadApp_76pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pupboss/SwiftTemplate/cb6e2641d60c143d3b5e01519fce6cd815c36ed2/SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPadApp_76pt.png -------------------------------------------------------------------------------- /SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPadApp_76pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pupboss/SwiftTemplate/cb6e2641d60c143d3b5e01519fce6cd815c36ed2/SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPadApp_76pt@2x.png -------------------------------------------------------------------------------- /SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPadNotifications_20pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pupboss/SwiftTemplate/cb6e2641d60c143d3b5e01519fce6cd815c36ed2/SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPadNotifications_20pt.png -------------------------------------------------------------------------------- /SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPadNotifications_20pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pupboss/SwiftTemplate/cb6e2641d60c143d3b5e01519fce6cd815c36ed2/SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPadNotifications_20pt@2x.png -------------------------------------------------------------------------------- /SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPadProApp_83.5pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pupboss/SwiftTemplate/cb6e2641d60c143d3b5e01519fce6cd815c36ed2/SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPadProApp_83.5pt@2x.png -------------------------------------------------------------------------------- /SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPadSpootlight5_29pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pupboss/SwiftTemplate/cb6e2641d60c143d3b5e01519fce6cd815c36ed2/SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPadSpootlight5_29pt.png -------------------------------------------------------------------------------- /SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPadSpootlight5_29pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pupboss/SwiftTemplate/cb6e2641d60c143d3b5e01519fce6cd815c36ed2/SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPadSpootlight5_29pt@2x.png -------------------------------------------------------------------------------- /SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPadSpootlight7_40pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pupboss/SwiftTemplate/cb6e2641d60c143d3b5e01519fce6cd815c36ed2/SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPadSpootlight7_40pt.png -------------------------------------------------------------------------------- /SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPadSpootlight7_40pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pupboss/SwiftTemplate/cb6e2641d60c143d3b5e01519fce6cd815c36ed2/SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPadSpootlight7_40pt@2x.png -------------------------------------------------------------------------------- /SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPhoneApp_60pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pupboss/SwiftTemplate/cb6e2641d60c143d3b5e01519fce6cd815c36ed2/SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPhoneApp_60pt@2x.png -------------------------------------------------------------------------------- /SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPhoneApp_60pt@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pupboss/SwiftTemplate/cb6e2641d60c143d3b5e01519fce6cd815c36ed2/SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPhoneApp_60pt@3x.png -------------------------------------------------------------------------------- /SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPhoneNotification_20pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pupboss/SwiftTemplate/cb6e2641d60c143d3b5e01519fce6cd815c36ed2/SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPhoneNotification_20pt@2x.png -------------------------------------------------------------------------------- /SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPhoneNotification_20pt@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pupboss/SwiftTemplate/cb6e2641d60c143d3b5e01519fce6cd815c36ed2/SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPhoneNotification_20pt@3x.png -------------------------------------------------------------------------------- /SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPhoneSpootlight5_29pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pupboss/SwiftTemplate/cb6e2641d60c143d3b5e01519fce6cd815c36ed2/SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPhoneSpootlight5_29pt@2x.png -------------------------------------------------------------------------------- /SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPhoneSpootlight5_29pt@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pupboss/SwiftTemplate/cb6e2641d60c143d3b5e01519fce6cd815c36ed2/SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPhoneSpootlight5_29pt@3x.png -------------------------------------------------------------------------------- /SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPhoneSpootlight7_40pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pupboss/SwiftTemplate/cb6e2641d60c143d3b5e01519fce6cd815c36ed2/SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPhoneSpootlight7_40pt@2x.png -------------------------------------------------------------------------------- /SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPhoneSpootlight7_40pt@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pupboss/SwiftTemplate/cb6e2641d60c143d3b5e01519fce6cd815c36ed2/SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconiPhoneSpootlight7_40pt@3x.png -------------------------------------------------------------------------------- /SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconstore_1024pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pupboss/SwiftTemplate/cb6e2641d60c143d3b5e01519fce6cd815c36ed2/SwiftTemplate/Supporting Files/Assets.xcassets/AppIcon.appiconset/headiconstore_1024pt.png -------------------------------------------------------------------------------- /SwiftTemplate/Supporting Files/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /SwiftTemplate/Supporting Files/Assets.xcassets/icon_nav_back.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "icon_nav_back@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SwiftTemplate/Supporting Files/Assets.xcassets/icon_nav_back.imageset/icon_nav_back@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pupboss/SwiftTemplate/cb6e2641d60c143d3b5e01519fce6cd815c36ed2/SwiftTemplate/Supporting Files/Assets.xcassets/icon_nav_back.imageset/icon_nav_back@2x.png -------------------------------------------------------------------------------- /SwiftTemplate/Supporting Files/Assets.xcassets/img_qrcode_border.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "resizing" : { 9 | "mode" : "9-part", 10 | "center" : { 11 | "mode" : "tile", 12 | "width" : 2, 13 | "height" : 1 14 | }, 15 | "cap-insets" : { 16 | "bottom" : 50, 17 | "top" : 50, 18 | "right" : 50, 19 | "left" : 50 20 | } 21 | }, 22 | "idiom" : "universal", 23 | "filename" : "img_qrcode_border@2x.png", 24 | "scale" : "2x" 25 | }, 26 | { 27 | "idiom" : "universal", 28 | "scale" : "3x" 29 | } 30 | ], 31 | "info" : { 32 | "version" : 1, 33 | "author" : "xcode" 34 | } 35 | } -------------------------------------------------------------------------------- /SwiftTemplate/Supporting Files/Assets.xcassets/img_qrcode_border.imageset/img_qrcode_border@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pupboss/SwiftTemplate/cb6e2641d60c143d3b5e01519fce6cd815c36ed2/SwiftTemplate/Supporting Files/Assets.xcassets/img_qrcode_border.imageset/img_qrcode_border@2x.png -------------------------------------------------------------------------------- /SwiftTemplate/Supporting Files/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 | -------------------------------------------------------------------------------- /SwiftTemplate/Supporting Files/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 | -------------------------------------------------------------------------------- /SwiftTemplate/Supporting Files/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 | APPL 17 | CFBundleShortVersionString 18 | 0.9.0 19 | CFBundleVersion 20 | 9056 21 | LSRequiresIPhoneOS 22 | 23 | NSCameraUsageDescription 24 | Allow camera access to scan the barcode 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /SwiftTemplate/Utils/Constant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constant.swift 3 | // SwiftTemplate 4 | // 5 | // Created by Jie Li on 29/5/18. 6 | // Copyright © 2018 Meltdown Research. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Constants { 12 | 13 | static let apiHost = "https://static.pupboss.com" 14 | 15 | static let authTokenDefaultsKey = "authTokenDefaultsKey" 16 | 17 | static let stripeAppKey = "pk_test_xxxxx" 18 | } 19 | -------------------------------------------------------------------------------- /SwiftTemplate/Venders/Toast.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Toast.swift 3 | // Toast-Swift 4 | // 5 | // Copyright (c) 2015-2019 Charles Scalesse. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a 8 | // copy of this software and associated documentation files (the 9 | // "Software"), to deal in the Software without restriction, including 10 | // without limitation the rights to use, copy, modify, merge, publish, 11 | // distribute, sublicense, and/or sell copies of the Software, and to 12 | // permit persons to whom the Software is furnished to do so, subject to 13 | // the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included 16 | // in all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | 26 | import UIKit 27 | import ObjectiveC 28 | 29 | /** 30 | Toast is a Swift extension that adds toast notifications to the `UIView` object class. 31 | It is intended to be simple, lightweight, and easy to use. Most toast notifications 32 | can be triggered with a single line of code. 33 | 34 | The `makeToast` methods create a new view and then display it as toast. 35 | 36 | The `showToast` methods display any view as toast. 37 | 38 | */ 39 | public extension UIView { 40 | 41 | /** 42 | Keys used for associated objects. 43 | */ 44 | private struct ToastKeys { 45 | static var timer = "com.toast-swift.timer" 46 | static var duration = "com.toast-swift.duration" 47 | static var point = "com.toast-swift.point" 48 | static var completion = "com.toast-swift.completion" 49 | static var activeToasts = "com.toast-swift.activeToasts" 50 | static var activityView = "com.toast-swift.activityView" 51 | static var queue = "com.toast-swift.queue" 52 | } 53 | 54 | /** 55 | Swift closures can't be directly associated with objects via the 56 | Objective-C runtime, so the (ugly) solution is to wrap them in a 57 | class that can be used with associated objects. 58 | */ 59 | private class ToastCompletionWrapper { 60 | let completion: ((Bool) -> Void)? 61 | 62 | init(_ completion: ((Bool) -> Void)?) { 63 | self.completion = completion 64 | } 65 | } 66 | 67 | private enum ToastError: Error { 68 | case missingParameters 69 | } 70 | 71 | private var activeToasts: NSMutableArray { 72 | get { 73 | if let activeToasts = objc_getAssociatedObject(self, &ToastKeys.activeToasts) as? NSMutableArray { 74 | return activeToasts 75 | } else { 76 | let activeToasts = NSMutableArray() 77 | objc_setAssociatedObject(self, &ToastKeys.activeToasts, activeToasts, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 78 | return activeToasts 79 | } 80 | } 81 | } 82 | 83 | private var queue: NSMutableArray { 84 | get { 85 | if let queue = objc_getAssociatedObject(self, &ToastKeys.queue) as? NSMutableArray { 86 | return queue 87 | } else { 88 | let queue = NSMutableArray() 89 | objc_setAssociatedObject(self, &ToastKeys.queue, queue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 90 | return queue 91 | } 92 | } 93 | } 94 | 95 | // MARK: - Make Toast Methods 96 | 97 | /** 98 | Creates and presents a new toast view. 99 | 100 | @param message The message to be displayed 101 | @param duration The toast duration 102 | @param position The toast's position 103 | @param title The title 104 | @param image The image 105 | @param style The style. The shared style will be used when nil 106 | @param completion The completion closure, executed after the toast view disappears. 107 | didTap will be `true` if the toast view was dismissed from a tap. 108 | */ 109 | func makeToast(_ message: String?, duration: TimeInterval = ToastManager.shared.duration, position: ToastPosition = ToastManager.shared.position, title: String? = nil, image: UIImage? = nil, style: ToastStyle = ToastManager.shared.style, completion: ((_ didTap: Bool) -> Void)? = nil) { 110 | do { 111 | let toast = try toastViewForMessage(message, title: title, image: image, style: style) 112 | showToast(toast, duration: duration, position: position, completion: completion) 113 | } catch ToastError.missingParameters { 114 | print("Error: message, title, and image are all nil") 115 | } catch {} 116 | } 117 | 118 | /** 119 | Creates a new toast view and presents it at a given center point. 120 | 121 | @param message The message to be displayed 122 | @param duration The toast duration 123 | @param point The toast's center point 124 | @param title The title 125 | @param image The image 126 | @param style The style. The shared style will be used when nil 127 | @param completion The completion closure, executed after the toast view disappears. 128 | didTap will be `true` if the toast view was dismissed from a tap. 129 | */ 130 | func makeToast(_ message: String?, duration: TimeInterval = ToastManager.shared.duration, point: CGPoint, title: String?, image: UIImage?, style: ToastStyle = ToastManager.shared.style, completion: ((_ didTap: Bool) -> Void)?) { 131 | do { 132 | let toast = try toastViewForMessage(message, title: title, image: image, style: style) 133 | showToast(toast, duration: duration, point: point, completion: completion) 134 | } catch ToastError.missingParameters { 135 | print("Error: message, title, and image cannot all be nil") 136 | } catch {} 137 | } 138 | 139 | // MARK: - Show Toast Methods 140 | 141 | /** 142 | Displays any view as toast at a provided position and duration. The completion closure 143 | executes when the toast view completes. `didTap` will be `true` if the toast view was 144 | dismissed from a tap. 145 | 146 | @param toast The view to be displayed as toast 147 | @param duration The notification duration 148 | @param position The toast's position 149 | @param completion The completion block, executed after the toast view disappears. 150 | didTap will be `true` if the toast view was dismissed from a tap. 151 | */ 152 | func showToast(_ toast: UIView, duration: TimeInterval = ToastManager.shared.duration, position: ToastPosition = ToastManager.shared.position, completion: ((_ didTap: Bool) -> Void)? = nil) { 153 | let point = position.centerPoint(forToast: toast, inSuperview: self) 154 | showToast(toast, duration: duration, point: point, completion: completion) 155 | } 156 | 157 | /** 158 | Displays any view as toast at a provided center point and duration. The completion closure 159 | executes when the toast view completes. `didTap` will be `true` if the toast view was 160 | dismissed from a tap. 161 | 162 | @param toast The view to be displayed as toast 163 | @param duration The notification duration 164 | @param point The toast's center point 165 | @param completion The completion block, executed after the toast view disappears. 166 | didTap will be `true` if the toast view was dismissed from a tap. 167 | */ 168 | func showToast(_ toast: UIView, duration: TimeInterval = ToastManager.shared.duration, point: CGPoint, completion: ((_ didTap: Bool) -> Void)? = nil) { 169 | objc_setAssociatedObject(toast, &ToastKeys.completion, ToastCompletionWrapper(completion), .OBJC_ASSOCIATION_RETAIN_NONATOMIC); 170 | 171 | if ToastManager.shared.isQueueEnabled, activeToasts.count > 0 { 172 | objc_setAssociatedObject(toast, &ToastKeys.duration, NSNumber(value: duration), .OBJC_ASSOCIATION_RETAIN_NONATOMIC); 173 | objc_setAssociatedObject(toast, &ToastKeys.point, NSValue(cgPoint: point), .OBJC_ASSOCIATION_RETAIN_NONATOMIC); 174 | 175 | queue.add(toast) 176 | } else { 177 | showToast(toast, duration: duration, point: point) 178 | } 179 | } 180 | 181 | // MARK: - Hide Toast Methods 182 | 183 | /** 184 | Hides the active toast. If there are multiple toasts active in a view, this method 185 | hides the oldest toast (the first of the toasts to have been presented). 186 | 187 | @see `hideAllToasts()` to remove all active toasts from a view. 188 | 189 | @warning This method has no effect on activity toasts. Use `hideToastActivity` to 190 | hide activity toasts. 191 | 192 | */ 193 | func hideToast() { 194 | guard let activeToast = activeToasts.firstObject as? UIView else { return } 195 | hideToast(activeToast) 196 | } 197 | 198 | /** 199 | Hides an active toast. 200 | 201 | @param toast The active toast view to dismiss. Any toast that is currently being displayed 202 | on the screen is considered active. 203 | 204 | @warning this does not clear a toast view that is currently waiting in the queue. 205 | */ 206 | func hideToast(_ toast: UIView) { 207 | guard activeToasts.contains(toast) else { return } 208 | hideToast(toast, fromTap: false) 209 | } 210 | 211 | /** 212 | Hides all toast views. 213 | 214 | @param includeActivity If `true`, toast activity will also be hidden. Default is `false`. 215 | @param clearQueue If `true`, removes all toast views from the queue. Default is `true`. 216 | */ 217 | func hideAllToasts(includeActivity: Bool = false, clearQueue: Bool = true) { 218 | if clearQueue { 219 | clearToastQueue() 220 | } 221 | 222 | activeToasts.compactMap { $0 as? UIView } 223 | .forEach { hideToast($0) } 224 | 225 | if includeActivity { 226 | hideToastActivity() 227 | } 228 | } 229 | 230 | /** 231 | Removes all toast views from the queue. This has no effect on toast views that are 232 | active. Use `hideAllToasts(clearQueue:)` to hide the active toasts views and clear 233 | the queue. 234 | */ 235 | func clearToastQueue() { 236 | queue.removeAllObjects() 237 | } 238 | 239 | // MARK: - Activity Methods 240 | 241 | /** 242 | Creates and displays a new toast activity indicator view at a specified position. 243 | 244 | @warning Only one toast activity indicator view can be presented per superview. Subsequent 245 | calls to `makeToastActivity(position:)` will be ignored until `hideToastActivity()` is called. 246 | 247 | @warning `makeToastActivity(position:)` works independently of the `showToast` methods. Toast 248 | activity views can be presented and dismissed while toast views are being displayed. 249 | `makeToastActivity(position:)` has no effect on the queueing behavior of the `showToast` methods. 250 | 251 | @param position The toast's position 252 | */ 253 | func makeToastActivity(_ position: ToastPosition) { 254 | // sanity 255 | guard objc_getAssociatedObject(self, &ToastKeys.activityView) as? UIView == nil else { return } 256 | 257 | let toast = createToastActivityView() 258 | let point = position.centerPoint(forToast: toast, inSuperview: self) 259 | makeToastActivity(toast, point: point) 260 | } 261 | 262 | /** 263 | Creates and displays a new toast activity indicator view at a specified position. 264 | 265 | @warning Only one toast activity indicator view can be presented per superview. Subsequent 266 | calls to `makeToastActivity(position:)` will be ignored until `hideToastActivity()` is called. 267 | 268 | @warning `makeToastActivity(position:)` works independently of the `showToast` methods. Toast 269 | activity views can be presented and dismissed while toast views are being displayed. 270 | `makeToastActivity(position:)` has no effect on the queueing behavior of the `showToast` methods. 271 | 272 | @param point The toast's center point 273 | */ 274 | func makeToastActivity(_ point: CGPoint) { 275 | // sanity 276 | guard objc_getAssociatedObject(self, &ToastKeys.activityView) as? UIView == nil else { return } 277 | 278 | let toast = createToastActivityView() 279 | makeToastActivity(toast, point: point) 280 | } 281 | 282 | /** 283 | Dismisses the active toast activity indicator view. 284 | */ 285 | func hideToastActivity() { 286 | if let toast = objc_getAssociatedObject(self, &ToastKeys.activityView) as? UIView { 287 | UIView.animate(withDuration: ToastManager.shared.style.fadeDuration, delay: 0.0, options: [.curveEaseIn, .beginFromCurrentState], animations: { 288 | toast.alpha = 0.0 289 | }) { _ in 290 | toast.removeFromSuperview() 291 | objc_setAssociatedObject(self, &ToastKeys.activityView, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 292 | } 293 | } 294 | } 295 | 296 | // MARK: - Private Activity Methods 297 | 298 | private func makeToastActivity(_ toast: UIView, point: CGPoint) { 299 | toast.alpha = 0.0 300 | toast.center = point 301 | 302 | objc_setAssociatedObject(self, &ToastKeys.activityView, toast, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 303 | 304 | self.addSubview(toast) 305 | 306 | UIView.animate(withDuration: ToastManager.shared.style.fadeDuration, delay: 0.0, options: .curveEaseOut, animations: { 307 | toast.alpha = 1.0 308 | }) 309 | } 310 | 311 | private func createToastActivityView() -> UIView { 312 | let style = ToastManager.shared.style 313 | 314 | let activityView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: style.activitySize.width, height: style.activitySize.height)) 315 | activityView.backgroundColor = style.activityBackgroundColor 316 | activityView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleTopMargin, .flexibleBottomMargin] 317 | activityView.layer.cornerRadius = style.cornerRadius 318 | 319 | if style.displayShadow { 320 | activityView.layer.shadowColor = style.shadowColor.cgColor 321 | activityView.layer.shadowOpacity = style.shadowOpacity 322 | activityView.layer.shadowRadius = style.shadowRadius 323 | activityView.layer.shadowOffset = style.shadowOffset 324 | } 325 | 326 | let activityIndicatorView = UIActivityIndicatorView(style: .whiteLarge) 327 | activityIndicatorView.center = CGPoint(x: activityView.bounds.size.width / 2.0, y: activityView.bounds.size.height / 2.0) 328 | activityView.addSubview(activityIndicatorView) 329 | activityIndicatorView.color = style.activityIndicatorColor 330 | activityIndicatorView.startAnimating() 331 | 332 | return activityView 333 | } 334 | 335 | // MARK: - Private Show/Hide Methods 336 | 337 | private func showToast(_ toast: UIView, duration: TimeInterval, point: CGPoint) { 338 | toast.center = point 339 | toast.alpha = 0.0 340 | 341 | if ToastManager.shared.isTapToDismissEnabled { 342 | let recognizer = UITapGestureRecognizer(target: self, action: #selector(UIView.handleToastTapped(_:))) 343 | toast.addGestureRecognizer(recognizer) 344 | toast.isUserInteractionEnabled = true 345 | toast.isExclusiveTouch = true 346 | } 347 | 348 | activeToasts.add(toast) 349 | self.addSubview(toast) 350 | 351 | UIView.animate(withDuration: ToastManager.shared.style.fadeDuration, delay: 0.0, options: [.curveEaseOut, .allowUserInteraction], animations: { 352 | toast.alpha = 1.0 353 | }) { _ in 354 | let timer = Timer(timeInterval: duration, target: self, selector: #selector(UIView.toastTimerDidFinish(_:)), userInfo: toast, repeats: false) 355 | RunLoop.main.add(timer, forMode: .common) 356 | objc_setAssociatedObject(toast, &ToastKeys.timer, timer, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 357 | } 358 | } 359 | 360 | private func hideToast(_ toast: UIView, fromTap: Bool) { 361 | if let timer = objc_getAssociatedObject(toast, &ToastKeys.timer) as? Timer { 362 | timer.invalidate() 363 | } 364 | 365 | UIView.animate(withDuration: ToastManager.shared.style.fadeDuration, delay: 0.0, options: [.curveEaseIn, .beginFromCurrentState], animations: { 366 | toast.alpha = 0.0 367 | }) { _ in 368 | toast.removeFromSuperview() 369 | self.activeToasts.remove(toast) 370 | 371 | if let wrapper = objc_getAssociatedObject(toast, &ToastKeys.completion) as? ToastCompletionWrapper, let completion = wrapper.completion { 372 | completion(fromTap) 373 | } 374 | 375 | if let nextToast = self.queue.firstObject as? UIView, let duration = objc_getAssociatedObject(nextToast, &ToastKeys.duration) as? NSNumber, let point = objc_getAssociatedObject(nextToast, &ToastKeys.point) as? NSValue { 376 | self.queue.removeObject(at: 0) 377 | self.showToast(nextToast, duration: duration.doubleValue, point: point.cgPointValue) 378 | } 379 | } 380 | } 381 | 382 | // MARK: - Events 383 | 384 | @objc 385 | private func handleToastTapped(_ recognizer: UITapGestureRecognizer) { 386 | guard let toast = recognizer.view else { return } 387 | hideToast(toast, fromTap: true) 388 | } 389 | 390 | @objc 391 | private func toastTimerDidFinish(_ timer: Timer) { 392 | guard let toast = timer.userInfo as? UIView else { return } 393 | hideToast(toast) 394 | } 395 | 396 | // MARK: - Toast Construction 397 | 398 | /** 399 | Creates a new toast view with any combination of message, title, and image. 400 | The look and feel is configured via the style. Unlike the `makeToast` methods, 401 | this method does not present the toast view automatically. One of the `showToast` 402 | methods must be used to present the resulting view. 403 | 404 | @warning if message, title, and image are all nil, this method will throw 405 | `ToastError.missingParameters` 406 | 407 | @param message The message to be displayed 408 | @param title The title 409 | @param image The image 410 | @param style The style. The shared style will be used when nil 411 | @throws `ToastError.missingParameters` when message, title, and image are all nil 412 | @return The newly created toast view 413 | */ 414 | func toastViewForMessage(_ message: String?, title: String?, image: UIImage?, style: ToastStyle) throws -> UIView { 415 | // sanity 416 | guard message != nil || title != nil || image != nil else { 417 | throw ToastError.missingParameters 418 | } 419 | 420 | var messageLabel: UILabel? 421 | var titleLabel: UILabel? 422 | var imageView: UIImageView? 423 | 424 | let wrapperView = UIView() 425 | wrapperView.backgroundColor = style.backgroundColor 426 | wrapperView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleTopMargin, .flexibleBottomMargin] 427 | wrapperView.layer.cornerRadius = style.cornerRadius 428 | 429 | if style.displayShadow { 430 | wrapperView.layer.shadowColor = UIColor.black.cgColor 431 | wrapperView.layer.shadowOpacity = style.shadowOpacity 432 | wrapperView.layer.shadowRadius = style.shadowRadius 433 | wrapperView.layer.shadowOffset = style.shadowOffset 434 | } 435 | 436 | if let image = image { 437 | imageView = UIImageView(image: image) 438 | imageView?.contentMode = .scaleAspectFit 439 | imageView?.frame = CGRect(x: style.horizontalPadding, y: style.verticalPadding, width: style.imageSize.width, height: style.imageSize.height) 440 | } 441 | 442 | var imageRect = CGRect.zero 443 | 444 | if let imageView = imageView { 445 | imageRect.origin.x = style.horizontalPadding 446 | imageRect.origin.y = style.verticalPadding 447 | imageRect.size.width = imageView.bounds.size.width 448 | imageRect.size.height = imageView.bounds.size.height 449 | } 450 | 451 | if let title = title { 452 | titleLabel = UILabel() 453 | titleLabel?.numberOfLines = style.titleNumberOfLines 454 | titleLabel?.font = style.titleFont 455 | titleLabel?.textAlignment = style.titleAlignment 456 | titleLabel?.lineBreakMode = .byTruncatingTail 457 | titleLabel?.textColor = style.titleColor 458 | titleLabel?.backgroundColor = UIColor.clear 459 | titleLabel?.text = title; 460 | 461 | let maxTitleSize = CGSize(width: (self.bounds.size.width * style.maxWidthPercentage) - imageRect.size.width, height: self.bounds.size.height * style.maxHeightPercentage) 462 | let titleSize = titleLabel?.sizeThatFits(maxTitleSize) 463 | if let titleSize = titleSize { 464 | titleLabel?.frame = CGRect(x: 0.0, y: 0.0, width: titleSize.width, height: titleSize.height) 465 | } 466 | } 467 | 468 | if let message = message { 469 | messageLabel = UILabel() 470 | messageLabel?.text = message 471 | messageLabel?.numberOfLines = style.messageNumberOfLines 472 | messageLabel?.font = style.messageFont 473 | messageLabel?.textAlignment = style.messageAlignment 474 | messageLabel?.lineBreakMode = .byTruncatingTail; 475 | messageLabel?.textColor = style.messageColor 476 | messageLabel?.backgroundColor = UIColor.clear 477 | 478 | let maxMessageSize = CGSize(width: (self.bounds.size.width * style.maxWidthPercentage) - imageRect.size.width, height: self.bounds.size.height * style.maxHeightPercentage) 479 | let messageSize = messageLabel?.sizeThatFits(maxMessageSize) 480 | if let messageSize = messageSize { 481 | let actualWidth = min(messageSize.width, maxMessageSize.width) 482 | let actualHeight = min(messageSize.height, maxMessageSize.height) 483 | messageLabel?.frame = CGRect(x: 0.0, y: 0.0, width: actualWidth, height: actualHeight) 484 | } 485 | } 486 | 487 | var titleRect = CGRect.zero 488 | 489 | if let titleLabel = titleLabel { 490 | titleRect.origin.x = imageRect.origin.x + imageRect.size.width + style.horizontalPadding 491 | titleRect.origin.y = style.verticalPadding 492 | titleRect.size.width = titleLabel.bounds.size.width 493 | titleRect.size.height = titleLabel.bounds.size.height 494 | } 495 | 496 | var messageRect = CGRect.zero 497 | 498 | if let messageLabel = messageLabel { 499 | messageRect.origin.x = imageRect.origin.x + imageRect.size.width + style.horizontalPadding 500 | messageRect.origin.y = titleRect.origin.y + titleRect.size.height + style.verticalPadding 501 | messageRect.size.width = messageLabel.bounds.size.width 502 | messageRect.size.height = messageLabel.bounds.size.height 503 | } 504 | 505 | let longerWidth = max(titleRect.size.width, messageRect.size.width) 506 | let longerX = max(titleRect.origin.x, messageRect.origin.x) 507 | let wrapperWidth = max((imageRect.size.width + (style.horizontalPadding * 2.0)), (longerX + longerWidth + style.horizontalPadding)) 508 | let wrapperHeight = max((messageRect.origin.y + messageRect.size.height + style.verticalPadding), (imageRect.size.height + (style.verticalPadding * 2.0))) 509 | 510 | wrapperView.frame = CGRect(x: 0.0, y: 0.0, width: wrapperWidth, height: wrapperHeight) 511 | 512 | if let titleLabel = titleLabel { 513 | titleRect.size.width = longerWidth 514 | titleLabel.frame = titleRect 515 | wrapperView.addSubview(titleLabel) 516 | } 517 | 518 | if let messageLabel = messageLabel { 519 | messageRect.size.width = longerWidth 520 | messageLabel.frame = messageRect 521 | wrapperView.addSubview(messageLabel) 522 | } 523 | 524 | if let imageView = imageView { 525 | wrapperView.addSubview(imageView) 526 | } 527 | 528 | return wrapperView 529 | } 530 | 531 | } 532 | 533 | // MARK: - Toast Style 534 | 535 | /** 536 | `ToastStyle` instances define the look and feel for toast views created via the 537 | `makeToast` methods as well for toast views created directly with 538 | `toastViewForMessage(message:title:image:style:)`. 539 | 540 | @warning `ToastStyle` offers relatively simple styling options for the default 541 | toast view. If you require a toast view with more complex UI, it probably makes more 542 | sense to create your own custom UIView subclass and present it with the `showToast` 543 | methods. 544 | */ 545 | public struct ToastStyle { 546 | 547 | public init() {} 548 | 549 | /** 550 | The background color. Default is `.black` at 80% opacity. 551 | */ 552 | public var backgroundColor: UIColor = UIColor.black.withAlphaComponent(0.8) 553 | 554 | /** 555 | The title color. Default is `UIColor.whiteColor()`. 556 | */ 557 | public var titleColor: UIColor = .white 558 | 559 | /** 560 | The message color. Default is `.white`. 561 | */ 562 | public var messageColor: UIColor = .white 563 | 564 | /** 565 | A percentage value from 0.0 to 1.0, representing the maximum width of the toast 566 | view relative to it's superview. Default is 0.8 (80% of the superview's width). 567 | */ 568 | public var maxWidthPercentage: CGFloat = 0.8 { 569 | didSet { 570 | maxWidthPercentage = max(min(maxWidthPercentage, 1.0), 0.0) 571 | } 572 | } 573 | 574 | /** 575 | A percentage value from 0.0 to 1.0, representing the maximum height of the toast 576 | view relative to it's superview. Default is 0.8 (80% of the superview's height). 577 | */ 578 | public var maxHeightPercentage: CGFloat = 0.8 { 579 | didSet { 580 | maxHeightPercentage = max(min(maxHeightPercentage, 1.0), 0.0) 581 | } 582 | } 583 | 584 | /** 585 | The spacing from the horizontal edge of the toast view to the content. When an image 586 | is present, this is also used as the padding between the image and the text. 587 | Default is 10.0. 588 | 589 | */ 590 | public var horizontalPadding: CGFloat = 10.0 591 | 592 | /** 593 | The spacing from the vertical edge of the toast view to the content. When a title 594 | is present, this is also used as the padding between the title and the message. 595 | Default is 10.0. On iOS11+, this value is added added to the `safeAreaInset.top` 596 | and `safeAreaInsets.bottom`. 597 | */ 598 | public var verticalPadding: CGFloat = 10.0 599 | 600 | /** 601 | The corner radius. Default is 10.0. 602 | */ 603 | public var cornerRadius: CGFloat = 10.0; 604 | 605 | /** 606 | The title font. Default is `.boldSystemFont(16.0)`. 607 | */ 608 | public var titleFont: UIFont = .boldSystemFont(ofSize: 16.0) 609 | 610 | /** 611 | The message font. Default is `.systemFont(ofSize: 16.0)`. 612 | */ 613 | public var messageFont: UIFont = .systemFont(ofSize: 16.0) 614 | 615 | /** 616 | The title text alignment. Default is `NSTextAlignment.Left`. 617 | */ 618 | public var titleAlignment: NSTextAlignment = .left 619 | 620 | /** 621 | The message text alignment. Default is `NSTextAlignment.Left`. 622 | */ 623 | public var messageAlignment: NSTextAlignment = .left 624 | 625 | /** 626 | The maximum number of lines for the title. The default is 0 (no limit). 627 | */ 628 | public var titleNumberOfLines = 0 629 | 630 | /** 631 | The maximum number of lines for the message. The default is 0 (no limit). 632 | */ 633 | public var messageNumberOfLines = 0 634 | 635 | /** 636 | Enable or disable a shadow on the toast view. Default is `false`. 637 | */ 638 | public var displayShadow = false 639 | 640 | /** 641 | The shadow color. Default is `.black`. 642 | */ 643 | public var shadowColor: UIColor = .black 644 | 645 | /** 646 | A value from 0.0 to 1.0, representing the opacity of the shadow. 647 | Default is 0.8 (80% opacity). 648 | */ 649 | public var shadowOpacity: Float = 0.8 { 650 | didSet { 651 | shadowOpacity = max(min(shadowOpacity, 1.0), 0.0) 652 | } 653 | } 654 | 655 | /** 656 | The shadow radius. Default is 6.0. 657 | */ 658 | public var shadowRadius: CGFloat = 6.0 659 | 660 | /** 661 | The shadow offset. The default is 4 x 4. 662 | */ 663 | public var shadowOffset = CGSize(width: 4.0, height: 4.0) 664 | 665 | /** 666 | The image size. The default is 80 x 80. 667 | */ 668 | public var imageSize = CGSize(width: 80.0, height: 80.0) 669 | 670 | /** 671 | The size of the toast activity view when `makeToastActivity(position:)` is called. 672 | Default is 100 x 100. 673 | */ 674 | public var activitySize = CGSize(width: 100.0, height: 100.0) 675 | 676 | /** 677 | The fade in/out animation duration. Default is 0.2. 678 | */ 679 | public var fadeDuration: TimeInterval = 0.2 680 | 681 | /** 682 | Activity indicator color. Default is `.white`. 683 | */ 684 | public var activityIndicatorColor: UIColor = .white 685 | 686 | /** 687 | Activity background color. Default is `.black` at 80% opacity. 688 | */ 689 | public var activityBackgroundColor: UIColor = UIColor.black.withAlphaComponent(0.8) 690 | 691 | } 692 | 693 | // MARK: - Toast Manager 694 | 695 | /** 696 | `ToastManager` provides general configuration options for all toast 697 | notifications. Backed by a singleton instance. 698 | */ 699 | public class ToastManager { 700 | 701 | /** 702 | The `ToastManager` singleton instance. 703 | 704 | */ 705 | public static let shared = ToastManager() 706 | 707 | /** 708 | The shared style. Used whenever toastViewForMessage(message:title:image:style:) is called 709 | with with a nil style. 710 | 711 | */ 712 | public var style = ToastStyle() 713 | 714 | /** 715 | Enables or disables tap to dismiss on toast views. Default is `true`. 716 | 717 | */ 718 | public var isTapToDismissEnabled = true 719 | 720 | /** 721 | Enables or disables queueing behavior for toast views. When `true`, 722 | toast views will appear one after the other. When `false`, multiple toast 723 | views will appear at the same time (potentially overlapping depending 724 | on their positions). This has no effect on the toast activity view, 725 | which operates independently of normal toast views. Default is `false`. 726 | 727 | */ 728 | public var isQueueEnabled = false 729 | 730 | /** 731 | The default duration. Used for the `makeToast` and 732 | `showToast` methods that don't require an explicit duration. 733 | Default is 3.0. 734 | 735 | */ 736 | public var duration: TimeInterval = 3.0 737 | 738 | /** 739 | Sets the default position. Used for the `makeToast` and 740 | `showToast` methods that don't require an explicit position. 741 | Default is `ToastPosition.Bottom`. 742 | 743 | */ 744 | public var position: ToastPosition = .bottom 745 | 746 | } 747 | 748 | // MARK: - ToastPosition 749 | 750 | public enum ToastPosition { 751 | case top 752 | case center 753 | case bottom 754 | 755 | fileprivate func centerPoint(forToast toast: UIView, inSuperview superview: UIView) -> CGPoint { 756 | let topPadding: CGFloat = ToastManager.shared.style.verticalPadding + superview.csSafeAreaInsets.top 757 | let bottomPadding: CGFloat = ToastManager.shared.style.verticalPadding + superview.csSafeAreaInsets.bottom 758 | 759 | switch self { 760 | case .top: 761 | return CGPoint(x: superview.bounds.size.width / 2.0, y: (toast.frame.size.height / 2.0) + topPadding) 762 | case .center: 763 | return CGPoint(x: superview.bounds.size.width / 2.0, y: superview.bounds.size.height / 2.0) 764 | case .bottom: 765 | return CGPoint(x: superview.bounds.size.width / 2.0, y: (superview.bounds.size.height - (toast.frame.size.height / 2.0)) - bottomPadding) 766 | } 767 | } 768 | } 769 | 770 | // MARK: - Private UIView Extensions 771 | 772 | private extension UIView { 773 | 774 | var csSafeAreaInsets: UIEdgeInsets { 775 | if #available(iOS 11.0, *) { 776 | return self.safeAreaInsets 777 | } else { 778 | return .zero 779 | } 780 | } 781 | 782 | } 783 | -------------------------------------------------------------------------------- /SwiftTemplate/Venders/ZZYQRCodeSwift/AVCaptureSessionManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AVCaptureSessionManager.swift 3 | // ZZYQRCodeSwift 4 | // 5 | // Created by 张泽宇 on 2017/5/23. 6 | // Copyright © 2017年 zzy. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | import Photos 12 | 13 | typealias SuccessBlock = (String?) -> Void 14 | typealias GrantBlock = () -> () 15 | typealias DeniedBlock = () -> () 16 | 17 | class AVCaptureSessionManager: AVCaptureSession, AVCaptureMetadataOutputObjectsDelegate { 18 | 19 | /// 音效名称 20 | var soundName:String? 21 | /// 是否播放音效 22 | var isPlaySound = false 23 | 24 | private var block: SuccessBlock? 25 | 26 | private lazy var device: AVCaptureDevice? = { 27 | return AVCaptureDevice.default(for:.video) 28 | }() 29 | 30 | private lazy var preViewLayer: AVCaptureVideoPreviewLayer = { 31 | return AVCaptureVideoPreviewLayer(session: self) 32 | }() 33 | 34 | 35 | /// 创建sessionManager 36 | /// 37 | /// - Parameters: 38 | /// - captureType: 需要扫描的类型 39 | /// - scanRect: 扫描区域这里的Rect(x,y,w,h)分别的取值范围都是0-1 如果需要全屏传入React.null 40 | /// - success: 成功回调 41 | convenience init(captureType: AVCaptureType, 42 | scanRect: CGRect, 43 | success: @escaping SuccessBlock) { 44 | self.init() 45 | block = success 46 | 47 | var input: AVCaptureDeviceInput? 48 | do { 49 | if let device = device { 50 | input = try AVCaptureDeviceInput(device: device) 51 | } 52 | } catch let error as NSError { 53 | print("AVCaputreDeviceError \(error)") 54 | } 55 | 56 | let output = AVCaptureMetadataOutput() 57 | output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main) 58 | 59 | if !scanRect.equalTo(CGRect.null) { 60 | output.rectOfInterest = scanRect 61 | } 62 | 63 | sessionPreset = AVCaptureSession.Preset.high 64 | if let input = input { 65 | if canAddInput(input) { 66 | addInput(input) 67 | } 68 | 69 | } 70 | 71 | if canAddOutput(output) { 72 | addOutput(output) 73 | } 74 | 75 | output.metadataObjectTypes = captureType.supportTypes() 76 | NotificationCenter.default.addObserver(self, 77 | selector: #selector(stop), 78 | name: UIApplication.didEnterBackgroundNotification, 79 | object: nil) 80 | 81 | NotificationCenter.default.addObserver(self, 82 | selector: #selector(start), 83 | name: UIApplication.willEnterForegroundNotification, 84 | object: nil) 85 | 86 | } 87 | 88 | 89 | /// 创建sessionManager 90 | /// 91 | /// - Parameters: 92 | /// - captureType: 需要扫描的类型 93 | /// - scanRect: 扫描区域这里的Rect(x,y,w,h)分别的取值范围都是0-1 如果需要全屏传入React.null 94 | /// - success: 成功回调 95 | /// - Returns: manager 96 | class func createSessionManager(captureType: AVCaptureType, 97 | scanRect: CGRect, 98 | success: @escaping SuccessBlock) ->AVCaptureSessionManager { 99 | let result = AVCaptureSessionManager(captureType: captureType, scanRect: scanRect, success: success) 100 | return result 101 | } 102 | 103 | 104 | /// 监测相机权限 105 | /// 106 | /// - Parameters: 107 | /// - grant: 同意回调 108 | /// - denied: 拒绝回调 109 | class func checkAuthorizationStatusForCamera(grant:@escaping GrantBlock, denied:DeniedBlock) { 110 | if AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: .back).devices.count > 0 { 111 | let status = AVCaptureDevice.authorizationStatus(for: AVMediaType.video) 112 | switch status { 113 | case .notDetermined: 114 | AVCaptureDevice.requestAccess(for: AVMediaType.video, completionHandler: { (granted) in 115 | if granted { 116 | DispatchQueue.main.async(execute: { 117 | grant() 118 | }) 119 | } 120 | }) 121 | case .authorized: 122 | grant() 123 | case .denied: 124 | denied() 125 | default: 126 | break 127 | } 128 | } 129 | } 130 | 131 | /// 监测相册权限 132 | /// 133 | /// - Parameters: 134 | /// - grant: 同意回调 135 | /// - denied: 拒绝回调 136 | class func checkAuthorizationStatusForPhotoLibrary(grant:@escaping GrantBlock, denied:DeniedBlock) { 137 | let status = PHPhotoLibrary.authorizationStatus() 138 | switch status { 139 | case .notDetermined: 140 | PHPhotoLibrary.requestAuthorization({ (status) in 141 | if status == PHAuthorizationStatus.authorized { 142 | DispatchQueue.main.async(execute: { 143 | grant() 144 | }) 145 | } 146 | }) 147 | 148 | case .authorized: 149 | grant() 150 | case .denied: 151 | denied() 152 | default: 153 | break 154 | } 155 | } 156 | 157 | /// 扫描相册中的二维码 158 | /// 159 | /// - Parameters: 160 | /// - image: 图片 161 | /// - success: 成功回调 162 | func scanPhoto(image: UIImage, success: SuccessBlock) { 163 | let detector = CIDetector(ofType: CIDetectorTypeQRCode, 164 | context: nil, 165 | options: [CIDetectorAccuracy : CIDetectorAccuracyHigh]) 166 | if detector != nil { 167 | let features = detector!.features(in: CIImage(cgImage: image.cgImage!)) 168 | for temp in features { 169 | let result = (temp as! CIQRCodeFeature).messageString 170 | success(result) 171 | return 172 | } 173 | success(nil) 174 | }else { 175 | success(nil) 176 | } 177 | 178 | } 179 | 180 | /// 显示扫描 181 | /// 182 | /// - Parameter view: 需要在哪个View中显示 183 | func showPreViewLayerIn(view :UIView) { 184 | preViewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill 185 | preViewLayer.frame = view.bounds 186 | view.layer.insertSublayer(preViewLayer, at: 0) 187 | start() 188 | } 189 | 190 | 191 | /// 开关闪光灯 192 | /// 193 | /// - Parameter state: 闪光灯状态 194 | func turnTorch(state:Bool) { 195 | if let device = device { 196 | if (device.hasTorch) { 197 | do { 198 | try device.lockForConfiguration() 199 | } catch let error as NSError { 200 | print("TorchError \(error)") 201 | } 202 | if (state) { 203 | device.torchMode = AVCaptureDevice.TorchMode.on 204 | } else { 205 | device.torchMode = AVCaptureDevice.TorchMode.off 206 | } 207 | device.unlockForConfiguration() 208 | } 209 | } 210 | } 211 | 212 | 213 | /// 播放音效 214 | func playSound() { 215 | if isPlaySound { 216 | var result = "ZZYQRCode.bundle/sound.caf" 217 | if let temp = soundName, temp != ""{ 218 | result = temp 219 | } 220 | 221 | if let urlstr = Bundle.main.path(forResource: result, ofType: nil) { 222 | let fileURL = NSURL(fileURLWithPath: urlstr) 223 | var soundID:SystemSoundID = 0 224 | AudioServicesCreateSystemSoundID(fileURL, &soundID) 225 | AudioServicesPlaySystemSound(soundID) 226 | } 227 | } 228 | } 229 | 230 | /// 开启扫描 231 | @objc func start() { 232 | startRunning() 233 | } 234 | 235 | /// 停止扫描 236 | @objc func stop() { 237 | stopRunning() 238 | } 239 | 240 | 241 | func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { 242 | if (metadataObjects.count > 0) { 243 | // 停止扫描 244 | stop() 245 | playSound() 246 | // 获取信息 247 | let result = metadataObjects.last as! AVMetadataMachineReadableCodeObject 248 | block!(result.stringValue) 249 | } 250 | } 251 | } 252 | 253 | enum AVCaptureType { 254 | case qrCode 255 | case barCode 256 | case both 257 | func supportTypes() -> [AVMetadataObject.ObjectType] { 258 | switch self { 259 | case .qrCode: 260 | return [.qr] 261 | case .barCode: 262 | return [.ean13, .upce, .ean8, .code128, .code39, .code93] 263 | case .both: 264 | return [.qr, .ean13, .upce, .ean8, .code128, .code39, .code93] 265 | } 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /SwiftTemplate/Venders/ZZYQRCodeSwift/UIImage+ZZYQRCodeImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+ZZYQRCodeImage.swift 3 | // ZZYQRCodeSwift 4 | // 5 | // Created by 张泽宇 on 2017/5/22. 6 | // Copyright © 2017年 zzy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import CoreImage 12 | extension UIImage { 13 | 14 | enum QRCodeImageType { 15 | case CircularImage 16 | case SquareImage 17 | } 18 | 19 | /// 创建普通二维码 20 | /// 21 | /// - Parameters: 22 | /// - size: 二维码大小 23 | /// - dataStr: 二维码包含字符 24 | /// - Returns: 二维码图片 25 | class func createQRCode(size: CGFloat, dataStr: String) -> UIImage? { 26 | 27 | let filter = CIFilter(name: "CIQRCodeGenerator") 28 | filter?.setDefaults() 29 | 30 | let data = dataStr.data(using: .utf8) 31 | filter?.setValue(data, forKey: "inputMessage") 32 | guard let cIImage = filter?.outputImage else { 33 | return nil 34 | } 35 | return self .createNonInterpolatedUIImage(image: cIImage, size: size) 36 | } 37 | 38 | 39 | 40 | /// 生成自定义二维码 41 | /// 42 | /// - Parameters: 43 | /// - size: 二维码大小 44 | /// - dataStr: 二维码包含字符 45 | /// - imageType: 二维码中自定义图片类型 46 | /// - iconImage: 二维码中包含的小图片 47 | /// - iconImageSize: 小图片的大小 48 | /// - Returns: 自定义二维码 49 | class func createCustomizeQRCode(size: CGFloat, 50 | dataStr: String, 51 | imageType: QRCodeImageType, 52 | iconImage: UIImage, 53 | iconImageSize: CGFloat) -> UIImage? { 54 | guard let bgImage = UIImage.createQRCode(size: size, dataStr: dataStr) else { 55 | return nil 56 | } 57 | var tempImage: UIImage? = iconImage 58 | if imageType == .CircularImage { 59 | tempImage = UIImage.createCircularImage(image: iconImage) 60 | } 61 | var result :UIImage? 62 | if let t = tempImage { 63 | result = UIImage.createNewImage(bgImage: bgImage, iconImage: t, iconSize: iconImageSize) 64 | } 65 | return result 66 | } 67 | 68 | 69 | 70 | /// 为二维码添加背景 71 | /// 72 | /// - Parameters: 73 | /// - bgImage: 背景图片 74 | /// - bgImageSize: 背景图片大小 75 | /// - QRImage: 二维码图片 76 | /// - Returns: 添加过背景的二维码 77 | class func addQRCodeBg(bgImage: UIImage, bgImageSize:CGFloat, QRImage: UIImage) -> UIImage? { 78 | let tempImage = UIImage.imageCompress(sourceImage: bgImage, size: CGSize(width: bgImageSize, height: bgImageSize)) 79 | return UIImage.createNewImage(bgImage: tempImage!, iconImage: QRImage, iconSize: QRImage.size.width) 80 | } 81 | 82 | /// 拼接图片 83 | /// 84 | /// - Parameters: 85 | /// - bgImage: 背景图片 86 | /// - iconImage: icon图片 87 | /// - iconSize: icon的大小 88 | private class func createNewImage(bgImage: UIImage, iconImage:UIImage, iconSize:CGFloat) -> UIImage? { 89 | UIGraphicsBeginImageContext(bgImage.size) 90 | bgImage.draw(in: CGRect(x: 0, y: 0, width: bgImage.size.width, height: bgImage.size.height)) 91 | let imageX: CGFloat = (bgImage.size.width - iconSize) * 0.5 92 | let imageY: CGFloat = (bgImage.size.height - iconSize) * 0.5 93 | iconImage.draw(in: CGRect(x: imageX, y: imageY, width: iconSize, height: iconSize)) 94 | let result = UIGraphicsGetImageFromCurrentImageContext() 95 | UIGraphicsEndImageContext() 96 | return result 97 | } 98 | 99 | /// 剪切圆形图片 100 | /// 101 | /// - Parameter image: 需要剪裁的图片 102 | /// - Returns: 处理好的图片 103 | private class func createCircularImage(image: UIImage) -> UIImage?{ 104 | UIGraphicsBeginImageContextWithOptions(image.size, false, 0) 105 | let ctx = UIGraphicsGetCurrentContext() 106 | ctx?.addEllipse(in: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)) 107 | ctx?.clip() 108 | image.draw(in: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)) 109 | let result = UIGraphicsGetImageFromCurrentImageContext() 110 | UIGraphicsEndImageContext() 111 | return result 112 | } 113 | 114 | 115 | 116 | /// 根据CIImage生成指定大小的图片 117 | /// 118 | /// - Parameters: 119 | /// - image: CIImage 120 | /// - size: 图片大小 121 | /// - Returns: 图片 122 | private class func createNonInterpolatedUIImage(image: CIImage, size: CGFloat) -> UIImage? { 123 | let extent = image.extent.integral 124 | let scale = min(size/extent.width, size/extent.height) 125 | let width = extent.width * scale 126 | let height = extent.height * scale 127 | let cs = CGColorSpaceCreateDeviceGray() 128 | 129 | let context = CIContext(options: nil) 130 | let bitmapImage = context.createCGImage(image, from: extent) 131 | let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue) 132 | guard let bitmapRef = CGContext(data: nil, 133 | width: Int(width), 134 | height: Int(height), 135 | bitsPerComponent: 8, 136 | bytesPerRow: 0, 137 | space: cs, 138 | bitmapInfo: bitmapInfo.rawValue)else { 139 | return nil 140 | } 141 | 142 | bitmapRef.interpolationQuality = CGInterpolationQuality.none 143 | bitmapRef.scaleBy(x: scale,y: scale) 144 | bitmapRef.draw(bitmapImage!, in: extent) 145 | 146 | guard let scaledImage = bitmapRef.makeImage() else { 147 | return nil 148 | } 149 | 150 | return UIImage(cgImage: scaledImage) 151 | } 152 | 153 | 154 | 155 | /// 把图片按比例缩放 156 | /// 157 | /// - Parameters: 158 | /// - sourceImage: 需要处理的图片 159 | /// - size: 显示到多大的区域 160 | /// - Returns: 处理好的图片 161 | private class func imageCompress(sourceImage: UIImage, size:CGSize) -> UIImage? { 162 | let imageSize = sourceImage.size 163 | let width = imageSize.width 164 | let height = imageSize.height 165 | let targetWidth = size.width 166 | let targetHeight = size.height 167 | var scaleFactor:CGFloat = 0 168 | var scaledWidth = targetWidth 169 | var scaledHeight = targetHeight 170 | var thumbnailPoint = CGPoint(x: 0, y: 0) 171 | if !__CGSizeEqualToSize(imageSize, size) { 172 | let widthFactor:CGFloat = CGFloat(targetWidth / width) 173 | let heightFactor:CGFloat = CGFloat(targetHeight / height) 174 | 175 | if(widthFactor > heightFactor){ 176 | scaleFactor = widthFactor 177 | 178 | } 179 | else{ 180 | 181 | scaleFactor = heightFactor 182 | } 183 | scaledWidth = width * scaleFactor 184 | scaledHeight = height * scaleFactor 185 | 186 | if(widthFactor > heightFactor){ 187 | 188 | thumbnailPoint.y = (targetHeight - scaledHeight) * 0.5 189 | }else if(widthFactor < heightFactor){ 190 | 191 | thumbnailPoint.x = (targetWidth - scaledWidth) * 0.5 192 | } 193 | } 194 | 195 | UIGraphicsBeginImageContext(size) 196 | 197 | let thumbnailRect = CGRect(origin: thumbnailPoint, size: CGSize(width: scaledWidth, height: scaledHeight)) 198 | 199 | sourceImage.draw(in: thumbnailRect) 200 | let result = UIGraphicsGetImageFromCurrentImageContext() 201 | UIGraphicsEndImageContext() 202 | return result 203 | } 204 | 205 | } 206 | -------------------------------------------------------------------------------- /SwiftTemplate/Venders/ZZYQRCodeSwift/ZZYQRCode.bundle/Info.plist: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pupboss/SwiftTemplate/cb6e2641d60c143d3b5e01519fce6cd815c36ed2/SwiftTemplate/Venders/ZZYQRCodeSwift/ZZYQRCode.bundle/Info.plist -------------------------------------------------------------------------------- /SwiftTemplate/Venders/ZZYQRCodeSwift/ZZYQRCode.bundle/sound.caf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pupboss/SwiftTemplate/cb6e2641d60c143d3b5e01519fce6cd815c36ed2/SwiftTemplate/Venders/ZZYQRCodeSwift/ZZYQRCode.bundle/sound.caf -------------------------------------------------------------------------------- /SwiftTemplate/Views/BadgeButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BadgeButton.swift 3 | // SwiftTemplate 4 | // 5 | // Created by Jie Li on 3/6/18. 6 | // Copyright © 2018 Meltdown Research. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class BadgeButton: UIButton { 12 | 13 | var badgeLabel = UILabel() 14 | 15 | var badge: String? { 16 | didSet { 17 | addBadgeToButon(badge: badge) 18 | } 19 | } 20 | 21 | public var badgeBackgroundColor = UIColor.red { 22 | didSet { 23 | badgeLabel.backgroundColor = badgeBackgroundColor 24 | } 25 | } 26 | 27 | public var badgeTextColor = UIColor.white { 28 | didSet { 29 | badgeLabel.textColor = badgeTextColor 30 | } 31 | } 32 | 33 | public var badgeFont = UIFont.systemFont(ofSize: 12.0) { 34 | didSet { 35 | badgeLabel.font = badgeFont 36 | } 37 | } 38 | 39 | public var badgeEdgeInsets: UIEdgeInsets? { 40 | didSet { 41 | addBadgeToButon(badge: badge) 42 | } 43 | } 44 | 45 | override init(frame: CGRect) { 46 | super.init(frame: frame) 47 | addBadgeToButon(badge: nil) 48 | } 49 | 50 | required init?(coder aDecoder: NSCoder) { 51 | fatalError("init(coder:) has not been implemented") 52 | } 53 | 54 | func addBadgeToButon(badge: String?) { 55 | badgeLabel.text = badge 56 | badgeLabel.textColor = badgeTextColor 57 | badgeLabel.backgroundColor = badgeBackgroundColor 58 | badgeLabel.font = badgeFont 59 | badgeLabel.sizeToFit() 60 | badgeLabel.textAlignment = .center 61 | let badgeSize = badgeLabel.frame.size 62 | 63 | let height = max(18, Double(badgeSize.height) + 5.0) 64 | let width = max(height, Double(badgeSize.width) + 10.0) 65 | 66 | var vertical: Double?, horizontal: Double? 67 | if let badgeInset = self.badgeEdgeInsets { 68 | vertical = Double(badgeInset.top) - Double(badgeInset.bottom) 69 | horizontal = Double(badgeInset.left) - Double(badgeInset.right) 70 | 71 | let x = (Double(bounds.size.width) - 10 + horizontal!) 72 | let y = -(Double(badgeSize.height) / 2) - 10 + vertical! 73 | badgeLabel.frame = CGRect(x: x, y: y, width: width, height: height) 74 | } else { 75 | let x = self.frame.width - CGFloat((width / 2.0)) 76 | let y = CGFloat(-(height / 2.0)) 77 | badgeLabel.frame = CGRect(x: x, y: y, width: CGFloat(width), height: CGFloat(height)) 78 | } 79 | 80 | badgeLabel.layer.cornerRadius = badgeLabel.frame.height / 2 81 | badgeLabel.layer.masksToBounds = true 82 | addSubview(badgeLabel) 83 | badgeLabel.isHidden = badge != nil ? false : true 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /SwiftTemplate/Views/NewsTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NewsTableViewCell.swift 3 | // MeltdownPlatform 4 | // 5 | // Created by Jie Li on 29/6/20. 6 | // Copyright © 2020 Meltdown Research. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Kingfisher 11 | 12 | class NewsTableViewCell: UITableViewCell { 13 | 14 | var new: NewsModel? { 15 | didSet { 16 | if let new = new { 17 | titleLabel.text = new.title 18 | contentLabel.text = new.description 19 | descLabel.text = new.sourceName + " · " + new.publishedAt.timeAgoString 20 | 21 | if let imageUrl = new.imageUrl { 22 | headImageView.kf.setImage(with: URL(string: imageUrl)) 23 | headImageView.snp.remakeConstraints { make in 24 | make.trailing.equalTo(bgView).offset(-8) 25 | make.centerY.equalTo(bgView) 26 | make.width.equalTo(80) 27 | make.height.equalTo(80) 28 | } 29 | } else { 30 | headImageView.snp.remakeConstraints { make in 31 | make.trailing.equalTo(bgView).offset(-8) 32 | make.centerY.equalTo(bgView) 33 | make.width.equalTo(0) 34 | make.height.equalTo(80) 35 | } 36 | } 37 | 38 | DispatchQueue.main.asyncAfter(deadline: .now()) { 39 | 40 | self.bgView.layer.cornerRadius = 8 41 | self.bgView.layer.masksToBounds = true 42 | 43 | self.bgView.layer.shadowOffset = CGSize.init(width: 0, height: 0.5) 44 | self.bgView.layer.shadowColor = UIColor.black.cgColor 45 | self.bgView.layer.shadowOpacity = 0.1 46 | self.bgView.layer.masksToBounds = false 47 | self.bgView.layer.shadowPath = UIBezierPath(rect: self.bgView.layer.bounds).cgPath 48 | } 49 | } 50 | } 51 | } 52 | 53 | lazy var bgView: UIView = { 54 | let bgView = UIView() 55 | bgView.backgroundColor = UIColor.white 56 | return bgView 57 | }() 58 | 59 | lazy var headImageView: UIImageView = { 60 | let headImageView = UIImageView() 61 | headImageView.contentMode = .scaleAspectFill 62 | headImageView.clipsToBounds = true 63 | return headImageView 64 | }() 65 | 66 | lazy var titleLabel: UILabel = { 67 | let titleLabel = UILabel() 68 | titleLabel.textColor = UIColor.tc.bodyText 69 | titleLabel.font = UIFont.systemFont(ofSize: 15) 70 | titleLabel.numberOfLines = 0 71 | return titleLabel 72 | }() 73 | 74 | lazy var contentLabel: UILabel = { 75 | let contentLabel = UILabel() 76 | contentLabel.textColor = UIColor.tc.descText 77 | contentLabel.font = UIFont.systemFont(ofSize: 12) 78 | contentLabel.numberOfLines = 4 79 | return contentLabel 80 | }() 81 | 82 | lazy var descLabel: UILabel = { 83 | let descLabel = UILabel() 84 | descLabel.textColor = UIColor.tc.descText 85 | descLabel.font = UIFont.systemFont(ofSize: 12) 86 | descLabel.numberOfLines = 1 87 | return descLabel 88 | }() 89 | 90 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 91 | super.init(style: style, reuseIdentifier: reuseIdentifier) 92 | 93 | selectionStyle = .none 94 | addSubview(bgView) 95 | bgView.addSubview(headImageView) 96 | bgView.addSubview(titleLabel) 97 | bgView.addSubview(contentLabel) 98 | bgView.addSubview(descLabel) 99 | setupConstraints() 100 | } 101 | 102 | required init?(coder aDecoder: NSCoder) { 103 | fatalError("init(coder:) has not been implemented") 104 | } 105 | 106 | func setupConstraints() { 107 | let padding = UIEdgeInsets(top: 10, left: 15, bottom: 10, right: 15) 108 | bgView.snp.makeConstraints { (make) in 109 | make.edges.equalTo(self).inset(padding) 110 | } 111 | headImageView.snp.makeConstraints { (make) in 112 | make.trailing.equalTo(bgView).offset(-8) 113 | make.centerY.equalTo(bgView) 114 | make.width.equalTo(80) 115 | make.height.equalTo(80) 116 | } 117 | titleLabel.snp.makeConstraints { (make) in 118 | make.leading.equalTo(bgView).offset(8) 119 | make.top.equalTo(bgView).offset(8) 120 | make.trailing.equalTo(headImageView.snp.leading).offset(-8) 121 | } 122 | contentLabel.snp.makeConstraints { (make) in 123 | make.leading.equalTo(titleLabel) 124 | make.top.equalTo(titleLabel.snp.bottom).offset(8) 125 | make.trailing.equalTo(titleLabel) 126 | } 127 | descLabel.snp.makeConstraints { (make) in 128 | make.leading.equalTo(titleLabel) 129 | make.top.equalTo(contentLabel.snp.bottom).offset(8) 130 | make.bottom.equalTo(bgView).offset(-8) 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /SwiftTemplate/Views/SendCodeButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SendCodeButton.swift 3 | // SwiftTemplate 4 | // 5 | // Created by Jie Li on 3/6/18. 6 | // Copyright © 2018 Meltdown Research. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SendCodeButton: UIButton { 12 | 13 | private(set) var isWaiting: Bool = false 14 | 15 | override init(frame: CGRect) { 16 | super.init(frame: frame) 17 | 18 | setTitle("Send", for: .normal) 19 | setTitleColor(UIColor.tc.background, for: .normal) 20 | setTitleColor(UIColor.tc.disableText, for: .disabled) 21 | 22 | setBackgroundImage(UIImage.fromColor(.clear), for: .disabled) 23 | layer.borderWidth = 1 24 | layer.borderColor = UIColor.tc.tintTheme.cgColor 25 | } 26 | 27 | required init?(coder aDecoder: NSCoder) { 28 | fatalError("init(coder:) has not been implemented") 29 | } 30 | 31 | func enterDisableMode() { 32 | isEnabled = false 33 | isWaiting = true 34 | 35 | var seconds = 59 36 | 37 | let timer = DispatchSource.makeTimerSource(flags: [], queue:DispatchQueue.main) 38 | timer.schedule(deadline: .now(), repeating: .seconds(1) ,leeway:.seconds(seconds)) 39 | timer.setEventHandler { 40 | if seconds <= 0 { 41 | timer.cancel() 42 | DispatchQueue.main.sync { 43 | self.setTitle("Resend", for: .normal) 44 | self.isEnabled = true 45 | self.isWaiting = false 46 | } 47 | } else { 48 | DispatchQueue.main.sync { 49 | self.setTitle(String(format: "%.2ds", seconds), for: .normal) 50 | self.isEnabled = false 51 | self.isWaiting = true 52 | } 53 | seconds -= 1 54 | } 55 | } 56 | timer.resume() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /SwiftTemplateTests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /SwiftTemplateTests/SwiftTemplateTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftTemplateTests.swift 3 | // SwiftTemplateTests 4 | // 5 | // Created by Jie Li on 5/6/18. 6 | // Copyright © 2018 Meltdown Research. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import SwiftTemplate 11 | 12 | class SwiftTemplateTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /SwiftTemplateUITests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /SwiftTemplateUITests/SwiftTemplateUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftTemplateUITests.swift 3 | // SwiftTemplateUITests 4 | // 5 | // Created by Jie Li on 5/6/18. 6 | // Copyright © 2018 Meltdown Research. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class SwiftTemplateUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | 18 | // In UI tests it is usually best to stop immediately when a failure occurs. 19 | continueAfterFailure = false 20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 21 | XCUIApplication().launch() 22 | 23 | // 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. 24 | } 25 | 26 | override func tearDown() { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | super.tearDown() 29 | } 30 | 31 | func testExample() { 32 | // Use recording to get started writing UI tests. 33 | // Use XCTAssert and related functions to verify your tests produce the correct results. 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /fastlane/Appfile: -------------------------------------------------------------------------------- 1 | app_identifier ENV["APP_IDENTIFIER"] # The bundle identifier of your app 2 | apple_id "pupboss.i@gmail.com" # Your Apple email address 3 | 4 | team_id "BH46MT8H22" # Developer Portal Team ID 5 | 6 | # you can even provide different app identifiers, Apple IDs and team names per lane: 7 | # More information: https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Appfile.md 8 | 9 | 10 | -------------------------------------------------------------------------------- /fastlane/Deliverfile: -------------------------------------------------------------------------------- 1 | ###################### More Options ###################### 2 | # If you want to have even more control, check out the documentation 3 | # https://github.com/fastlane/fastlane/blob/master/deliver/Deliverfile.md 4 | 5 | 6 | ###################### Automatically generated ###################### 7 | # Feel free to remove the following line if you use fastlane (which you should) 8 | 9 | app_identifier "com.pupboss.SwiftTemplate" # The bundle identifier of your app 10 | username "pupboss.i@gmail.com" # your Apple ID user 11 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | # Customise this file, documentation can be found here: 2 | # https://github.com/fastlane/fastlane/tree/master/fastlane/docs 3 | # All available actions: https://docs.fastlane.tools/actions 4 | # can also be listed using the `fastlane actions` command 5 | 6 | # Change the syntax highlighting to Ruby 7 | # All lines starting with a # are ignored when running `fastlane` 8 | 9 | # If you want to automatically update fastlane if a new version is available: 10 | # update_fastlane 11 | 12 | # This is the minimum version number required. 13 | # Update this, if you use features of a newer version 14 | fastlane_version "2.54.0" 15 | 16 | default_platform :ios 17 | 18 | platform :ios do 19 | before_all do 20 | # ENV["SLACK_URL"] = "https://hooks.slack.com/services/..." 21 | cocoapods 22 | 23 | end 24 | 25 | desc "Runs all the tests" 26 | lane :test do 27 | scan 28 | end 29 | 30 | desc "Submit a Dev Build to HockeyApp" 31 | lane :dev do 32 | sh 'gsed -i "s/:\/\/api.*.google.com/:\/\/api.dev.google.com/g" ../SwiftTemplate/Utils/Constant.swift' 33 | 34 | ENV["APP_IDENTIFIER"] = "com.pupboss.SwiftTemplate" 35 | update_info_plist( plist_path: "SwiftTemplate/Supporting Files/Info.plist", display_name: "Swift.a") 36 | cert 37 | pem 38 | sigh(adhoc: true) 39 | gym(scheme: "SwiftTemplate", export_method: "ad-hoc", export_xcargs: "-allowProvisioningUpdates") 40 | upload_symbols_to_crashlytics(api_token: "") # for avoiding a warning. 41 | hockey( 42 | release_type: "2", #alpha 43 | api_token: "" 44 | ) 45 | end 46 | 47 | desc "Submit a Beta Build to HockeyApp" 48 | lane :beta do 49 | sh 'gsed -i "s/:\/\/api.*.google.com/:\/\/api.beta.google.com/g" ../SwiftTemplate/Utils/Constant.swift' 50 | 51 | ENV["APP_IDENTIFIER"] = "com.pupboss.SwiftTemplate" 52 | update_info_plist( plist_path: "SwiftTemplate/Supporting Files/Info.plist", display_name: "Swift.b") 53 | cert 54 | pem 55 | sigh(adhoc: true) 56 | gym(scheme: "SwiftTemplate", export_method: "ad-hoc", export_xcargs: "-allowProvisioningUpdates") 57 | upload_symbols_to_crashlytics(api_token: "") 58 | hockey( 59 | api_token: "" 60 | ) 61 | end 62 | 63 | 64 | lane :prod do 65 | sh 'gsed -i "s/:\/\/api.*.google.com/:\/\/api.google.com/g" ../SwiftTemplate/Utils/Constant.swift' 66 | 67 | ENV["APP_IDENTIFIER"] = "com.pupboss.SwiftTemplate" 68 | update_info_plist( plist_path: "SwiftTemplate/Supporting Files/Info.plist", display_name: "Swift") 69 | #precheck 70 | produce 71 | cert 72 | pem 73 | sigh(adhoc: true) 74 | gym(scheme: "SwiftTemplate", export_method: "ad-hoc", export_xcargs: "-allowProvisioningUpdates") 75 | upload_symbols_to_crashlytics(api_token: "") 76 | hockey( 77 | release_type: "3", 78 | api_token: "" 79 | ) 80 | end 81 | 82 | desc "Deploy a new version to the App Store" 83 | lane :release do 84 | # match(type: "appstore") 85 | # snapshot 86 | sh 'gsed -i "s/:\/\/api.*.google.com/:\/\/api.google.com/g" ../SwiftTemplate/Utils/Constant.swift' 87 | 88 | ENV["APP_IDENTIFIER"] = "com.pupboss.SwiftTemplate" 89 | update_info_plist( plist_path: "SwiftTemplate/Supporting Files/Info.plist", display_name: "Swift") 90 | #precheck 91 | produce 92 | cert 93 | pem 94 | sigh 95 | gym(scheme: "SwiftTemplate", export_xcargs: "-allowProvisioningUpdates") 96 | upload_symbols_to_crashlytics(api_token: "") 97 | deliver(force: true) 98 | # frameit 99 | end 100 | 101 | # You can define as many lanes as you want 102 | 103 | after_all do |lane| 104 | # This block is called, only if the executed lane was successful 105 | 106 | # slack( 107 | # message: "Successfully deployed new App Update." 108 | # ) 109 | end 110 | 111 | error do |lane, exception| 112 | # slack( 113 | # message: exception.message, 114 | # success: false 115 | # ) 116 | end 117 | end 118 | 119 | 120 | # More information about multiple platforms in fastlane: https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Platforms.md 121 | # All available actions: https://docs.fastlane.tools/actions 122 | 123 | # fastlane reports which actions are used. No personal data is recorded. 124 | # Learn more at https://github.com/fastlane/fastlane#metrics 125 | -------------------------------------------------------------------------------- /fastlane/README.md: -------------------------------------------------------------------------------- 1 | fastlane documentation 2 | ================ 3 | # Installation 4 | 5 | Make sure you have the latest version of the Xcode command line tools installed: 6 | 7 | ``` 8 | xcode-select --install 9 | ``` 10 | 11 | Install _fastlane_ using 12 | ``` 13 | [sudo] gem install fastlane -NV 14 | ``` 15 | or alternatively using `brew cask install fastlane` 16 | 17 | # Available Actions 18 | ## iOS 19 | ### ios test 20 | ``` 21 | fastlane ios test 22 | ``` 23 | Runs all the tests 24 | ### ios dev 25 | ``` 26 | fastlane ios dev 27 | ``` 28 | Submit a Dev Build to HockeyApp 29 | ### ios beta 30 | ``` 31 | fastlane ios beta 32 | ``` 33 | Submit a Beta Build to HockeyApp 34 | ### ios prod 35 | ``` 36 | fastlane ios prod 37 | ``` 38 | 39 | ### ios release 40 | ``` 41 | fastlane ios release 42 | ``` 43 | Deploy a new version to the App Store 44 | 45 | ---- 46 | 47 | This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. 48 | More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). 49 | The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). 50 | --------------------------------------------------------------------------------