├── .gitignore ├── .swift-version ├── LICENSE ├── README.md ├── SweetKit.podspec ├── SweetKit.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata └── SweetKit ├── AppDelegate.swift ├── Assets.xcassets └── AppIcon.appiconset │ └── Contents.json ├── Base.lproj ├── LaunchScreen.storyboard └── Main.storyboard ├── Info.plist ├── SweetKit ├── Extensions │ ├── Foundation │ │ ├── Collection+Extension.swift │ │ ├── Date+Extension.swift │ │ ├── Dictionary+Extension.swift │ │ ├── FileManager+Extension.swift │ │ ├── String+Extension.swift │ │ └── String+Validate.swift │ └── UIKit │ │ ├── Language+Extension.swift │ │ ├── UIButton+Utils.swift │ │ ├── UICollectionView+Extension.swift │ │ ├── UIColor+Extension.swift │ │ ├── UIDevice+Extension.swift │ │ ├── UIImage+Extension.swift │ │ ├── UIKit+Extension.swift │ │ ├── UILable+Extension.swift │ │ ├── UINavigationController+Navigation.swift │ │ ├── UIScrollView+Extension.swift │ │ ├── UITableView+Extension.swift │ │ ├── UITextField+Extension.swift │ │ ├── UIView+Border.swift │ │ ├── UIView+Extension.swift │ │ ├── UIView+Positioning.swift │ │ └── UIViewController+Extension.swift ├── Helper │ ├── GCDHelper.swift │ ├── LocalNotificationHelper.swift │ ├── SKLog.swift │ ├── SKSystemSound.swift │ ├── SKTouchID.swift │ ├── SweetFunc.swift │ └── SwiftTimer.swift └── Views │ ├── UIInsetLabel.swift │ └── UIPlaceholderTextView.swift └── ViewController.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | .build/ 41 | 42 | # CocoaPods 43 | # 44 | # We recommend against adding the Pods directory to your .gitignore. However 45 | # you should judge for yourself, the pros and cons are mentioned at: 46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 47 | # 48 | # Pods/ 49 | 50 | # Carthage 51 | # 52 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 53 | # Carthage/Checkouts 54 | 55 | Carthage/Build 56 | 57 | # fastlane 58 | # 59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 60 | # screenshots whenever they are needed. 61 | # For more information about the recommended setup visit: 62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 63 | 64 | fastlane/report.xml 65 | fastlane/Preview.html 66 | fastlane/screenshots 67 | fastlane/test_output 68 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 devjoe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SweetKit 2 | 3 | A wonderful collection of Swift extensions to help speed up development, and constantly improve and update, welcome to add 4 | 一个美妙的Swift扩展集合,帮助提高开发速度,不断完善更新,欢迎补充 5 | 6 | ## Directory structure 目录结构 7 | - Extensions 8 | - Foundation 9 | - UIKit 10 | - Helper 11 | - GCDHelper 12 | - SKLog 13 | - TouchID 14 | - Timer 15 | - ... 16 | - Views 17 | - UIInserLabel 18 | - UIPlaceholderTextView 19 | 20 | 21 | 22 | 23 | ## Usage 使用 24 | 25 | 1. Drag the SweetKit directory directly into the project 将 SweetKit目录 直接拖入到项目中 26 | 2. Using Cocoapods 使用 Cocoapods 27 | 28 | ``` 29 | pod 'SweetKit' 30 | ``` 31 | Then, run pod install. 然后,运行 pod install. 32 | 33 | 34 | 35 | ## License 36 | MIT 37 | 38 | -------------------------------------------------------------------------------- /SweetKit.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = 'SweetKit' 3 | spec.version = '0.0.2' 4 | spec.license = { :type => 'MIT' } 5 | spec.homepage = 'https://github.com/Joe0708/SweetKit' 6 | spec.authors = { 'Joe' => 'joesir7@foxmail.com' } 7 | spec.summary = "一个美妙的Swift扩展集合." 8 | spec.source = { :git => "https://github.com/Joe0708/SweetKit.git", :tag => spec.version } 9 | spec.platform = :ios, "8.0" 10 | spec.source_files = 'SweetKit/SweetKit/**/*.{swift}' 11 | spec.requires_arc = true 12 | end 13 | -------------------------------------------------------------------------------- /SweetKit.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | BF303F5F1F564E72008E6A89 /* SKSystemSound.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF303F5E1F564E72008E6A89 /* SKSystemSound.swift */; }; 11 | BF303F611F5658ED008E6A89 /* SweetFunc.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF303F601F5658ED008E6A89 /* SweetFunc.swift */; }; 12 | BF303F641F565D9C008E6A89 /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF303F621F565D9C008E6A89 /* String+Extension.swift */; }; 13 | BF303F651F565D9C008E6A89 /* String+Validate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF303F631F565D9C008E6A89 /* String+Validate.swift */; }; 14 | BF303F671F566335008E6A89 /* SKLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF303F661F566335008E6A89 /* SKLog.swift */; }; 15 | BF303F691F5667DB008E6A89 /* UIDevice+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF303F681F5667DB008E6A89 /* UIDevice+Extension.swift */; }; 16 | BF303F6B1F566B42008E6A89 /* SKTouchID.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF303F6A1F566B42008E6A89 /* SKTouchID.swift */; }; 17 | BFC7BEE21F56749F0087463A /* Dictionary+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC7BEE11F56749F0087463A /* Dictionary+Extension.swift */; }; 18 | BFF95FCE1F53DCF6005BC83D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF95FCD1F53DCF6005BC83D /* AppDelegate.swift */; }; 19 | BFF95FD01F53DCF6005BC83D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF95FCF1F53DCF6005BC83D /* ViewController.swift */; }; 20 | BFF95FD31F53DCF6005BC83D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BFF95FD11F53DCF6005BC83D /* Main.storyboard */; }; 21 | BFF95FD51F53DCF6005BC83D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BFF95FD41F53DCF6005BC83D /* Assets.xcassets */; }; 22 | BFF95FD81F53DCF6005BC83D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BFF95FD61F53DCF6005BC83D /* LaunchScreen.storyboard */; }; 23 | BFF95FFE1F53DD18005BC83D /* Date+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF95FE21F53DD18005BC83D /* Date+Extension.swift */; }; 24 | BFF95FFF1F53DD18005BC83D /* Collection+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF95FE31F53DD18005BC83D /* Collection+Extension.swift */; }; 25 | BFF960001F53DD18005BC83D /* FileManager+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF95FE41F53DD18005BC83D /* FileManager+Extension.swift */; }; 26 | BFF960011F53DD18005BC83D /* Language+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF95FE61F53DD18005BC83D /* Language+Extension.swift */; }; 27 | BFF960041F53DD18005BC83D /* UIButton+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF95FE91F53DD18005BC83D /* UIButton+Utils.swift */; }; 28 | BFF960051F53DD18005BC83D /* UICollectionView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF95FEA1F53DD18005BC83D /* UICollectionView+Extension.swift */; }; 29 | BFF960061F53DD18005BC83D /* UIColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF95FEB1F53DD18005BC83D /* UIColor+Extension.swift */; }; 30 | BFF960071F53DD18005BC83D /* UIImage+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF95FEC1F53DD18005BC83D /* UIImage+Extension.swift */; }; 31 | BFF960081F53DD18005BC83D /* UIKit+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF95FED1F53DD18005BC83D /* UIKit+Extension.swift */; }; 32 | BFF960091F53DD18005BC83D /* UILable+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF95FEE1F53DD18005BC83D /* UILable+Extension.swift */; }; 33 | BFF9600A1F53DD18005BC83D /* UINavigationController+Navigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF95FEF1F53DD18005BC83D /* UINavigationController+Navigation.swift */; }; 34 | BFF9600B1F53DD18005BC83D /* UIScrollView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF95FF01F53DD18005BC83D /* UIScrollView+Extension.swift */; }; 35 | BFF9600C1F53DD18005BC83D /* UITableView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF95FF11F53DD18005BC83D /* UITableView+Extension.swift */; }; 36 | BFF9600D1F53DD18005BC83D /* UITextField+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF95FF21F53DD18005BC83D /* UITextField+Extension.swift */; }; 37 | BFF9600E1F53DD18005BC83D /* UIView+Border.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF95FF31F53DD18005BC83D /* UIView+Border.swift */; }; 38 | BFF9600F1F53DD18005BC83D /* UIView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF95FF41F53DD18005BC83D /* UIView+Extension.swift */; }; 39 | BFF960101F53DD18005BC83D /* UIView+Positioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF95FF51F53DD18005BC83D /* UIView+Positioning.swift */; }; 40 | BFF960111F53DD18005BC83D /* UIViewController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF95FF61F53DD18005BC83D /* UIViewController+Extension.swift */; }; 41 | BFF960121F53DD18005BC83D /* GCDHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF95FF81F53DD18005BC83D /* GCDHelper.swift */; }; 42 | BFF960131F53DD18005BC83D /* LocalNotificationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF95FF91F53DD18005BC83D /* LocalNotificationHelper.swift */; }; 43 | BFF960141F53DD18005BC83D /* SwiftTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF95FFA1F53DD18005BC83D /* SwiftTimer.swift */; }; 44 | BFF960151F53DD18005BC83D /* UIInsetLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF95FFC1F53DD18005BC83D /* UIInsetLabel.swift */; }; 45 | BFF960161F53DD18005BC83D /* UIPlaceholderTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF95FFD1F53DD18005BC83D /* UIPlaceholderTextView.swift */; }; 46 | /* End PBXBuildFile section */ 47 | 48 | /* Begin PBXFileReference section */ 49 | BF303F5E1F564E72008E6A89 /* SKSystemSound.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SKSystemSound.swift; sourceTree = ""; }; 50 | BF303F601F5658ED008E6A89 /* SweetFunc.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SweetFunc.swift; sourceTree = ""; }; 51 | BF303F621F565D9C008E6A89 /* String+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = ""; }; 52 | BF303F631F565D9C008E6A89 /* String+Validate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Validate.swift"; sourceTree = ""; }; 53 | BF303F661F566335008E6A89 /* SKLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SKLog.swift; sourceTree = ""; }; 54 | BF303F681F5667DB008E6A89 /* UIDevice+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIDevice+Extension.swift"; sourceTree = ""; }; 55 | BF303F6A1F566B42008E6A89 /* SKTouchID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SKTouchID.swift; sourceTree = ""; }; 56 | BFC7BEE11F56749F0087463A /* Dictionary+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Dictionary+Extension.swift"; sourceTree = ""; }; 57 | BFF95FCA1F53DCF6005BC83D /* SweetKit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SweetKit.app; sourceTree = BUILT_PRODUCTS_DIR; }; 58 | BFF95FCD1F53DCF6005BC83D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 59 | BFF95FCF1F53DCF6005BC83D /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 60 | BFF95FD21F53DCF6005BC83D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 61 | BFF95FD41F53DCF6005BC83D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 62 | BFF95FD71F53DCF6005BC83D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 63 | BFF95FD91F53DCF6005BC83D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 64 | BFF95FE21F53DD18005BC83D /* Date+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+Extension.swift"; sourceTree = ""; }; 65 | BFF95FE31F53DD18005BC83D /* Collection+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Collection+Extension.swift"; sourceTree = ""; }; 66 | BFF95FE41F53DD18005BC83D /* FileManager+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "FileManager+Extension.swift"; sourceTree = ""; }; 67 | BFF95FE61F53DD18005BC83D /* Language+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Language+Extension.swift"; sourceTree = ""; }; 68 | BFF95FE91F53DD18005BC83D /* UIButton+Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIButton+Utils.swift"; sourceTree = ""; }; 69 | BFF95FEA1F53DD18005BC83D /* UICollectionView+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UICollectionView+Extension.swift"; sourceTree = ""; }; 70 | BFF95FEB1F53DD18005BC83D /* UIColor+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Extension.swift"; sourceTree = ""; }; 71 | BFF95FEC1F53DD18005BC83D /* UIImage+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Extension.swift"; sourceTree = ""; }; 72 | BFF95FED1F53DD18005BC83D /* UIKit+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIKit+Extension.swift"; sourceTree = ""; }; 73 | BFF95FEE1F53DD18005BC83D /* UILable+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UILable+Extension.swift"; sourceTree = ""; }; 74 | BFF95FEF1F53DD18005BC83D /* UINavigationController+Navigation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UINavigationController+Navigation.swift"; sourceTree = ""; }; 75 | BFF95FF01F53DD18005BC83D /* UIScrollView+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIScrollView+Extension.swift"; sourceTree = ""; }; 76 | BFF95FF11F53DD18005BC83D /* UITableView+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITableView+Extension.swift"; sourceTree = ""; }; 77 | BFF95FF21F53DD18005BC83D /* UITextField+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITextField+Extension.swift"; sourceTree = ""; }; 78 | BFF95FF31F53DD18005BC83D /* UIView+Border.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Border.swift"; sourceTree = ""; }; 79 | BFF95FF41F53DD18005BC83D /* UIView+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Extension.swift"; sourceTree = ""; }; 80 | BFF95FF51F53DD18005BC83D /* UIView+Positioning.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Positioning.swift"; sourceTree = ""; }; 81 | BFF95FF61F53DD18005BC83D /* UIViewController+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewController+Extension.swift"; sourceTree = ""; }; 82 | BFF95FF81F53DD18005BC83D /* GCDHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GCDHelper.swift; sourceTree = ""; }; 83 | BFF95FF91F53DD18005BC83D /* LocalNotificationHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalNotificationHelper.swift; sourceTree = ""; }; 84 | BFF95FFA1F53DD18005BC83D /* SwiftTimer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftTimer.swift; sourceTree = ""; }; 85 | BFF95FFC1F53DD18005BC83D /* UIInsetLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIInsetLabel.swift; sourceTree = ""; }; 86 | BFF95FFD1F53DD18005BC83D /* UIPlaceholderTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIPlaceholderTextView.swift; sourceTree = ""; }; 87 | /* End PBXFileReference section */ 88 | 89 | /* Begin PBXFrameworksBuildPhase section */ 90 | BFF95FC71F53DCF6005BC83D /* Frameworks */ = { 91 | isa = PBXFrameworksBuildPhase; 92 | buildActionMask = 2147483647; 93 | files = ( 94 | ); 95 | runOnlyForDeploymentPostprocessing = 0; 96 | }; 97 | /* End PBXFrameworksBuildPhase section */ 98 | 99 | /* Begin PBXGroup section */ 100 | BFF95FC11F53DCF6005BC83D = { 101 | isa = PBXGroup; 102 | children = ( 103 | BFF95FCC1F53DCF6005BC83D /* SweetKit */, 104 | BFF95FCB1F53DCF6005BC83D /* Products */, 105 | ); 106 | sourceTree = ""; 107 | }; 108 | BFF95FCB1F53DCF6005BC83D /* Products */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | BFF95FCA1F53DCF6005BC83D /* SweetKit.app */, 112 | ); 113 | name = Products; 114 | sourceTree = ""; 115 | }; 116 | BFF95FCC1F53DCF6005BC83D /* SweetKit */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | BFF95FDF1F53DD18005BC83D /* SweetKit */, 120 | BFF95FCD1F53DCF6005BC83D /* AppDelegate.swift */, 121 | BFF95FCF1F53DCF6005BC83D /* ViewController.swift */, 122 | BFF95FD11F53DCF6005BC83D /* Main.storyboard */, 123 | BFF95FD41F53DCF6005BC83D /* Assets.xcassets */, 124 | BFF95FD61F53DCF6005BC83D /* LaunchScreen.storyboard */, 125 | BFF95FD91F53DCF6005BC83D /* Info.plist */, 126 | ); 127 | path = SweetKit; 128 | sourceTree = ""; 129 | }; 130 | BFF95FDF1F53DD18005BC83D /* SweetKit */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | BFF95FE01F53DD18005BC83D /* Extensions */, 134 | BFF95FF71F53DD18005BC83D /* Helper */, 135 | BFF95FFB1F53DD18005BC83D /* Views */, 136 | ); 137 | path = SweetKit; 138 | sourceTree = ""; 139 | }; 140 | BFF95FE01F53DD18005BC83D /* Extensions */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | BFF95FE11F53DD18005BC83D /* Foundation */, 144 | BFF95FE51F53DD18005BC83D /* UIKit */, 145 | ); 146 | path = Extensions; 147 | sourceTree = ""; 148 | }; 149 | BFF95FE11F53DD18005BC83D /* Foundation */ = { 150 | isa = PBXGroup; 151 | children = ( 152 | BFF95FE21F53DD18005BC83D /* Date+Extension.swift */, 153 | BFF95FE31F53DD18005BC83D /* Collection+Extension.swift */, 154 | BFF95FE41F53DD18005BC83D /* FileManager+Extension.swift */, 155 | BF303F621F565D9C008E6A89 /* String+Extension.swift */, 156 | BF303F631F565D9C008E6A89 /* String+Validate.swift */, 157 | BFC7BEE11F56749F0087463A /* Dictionary+Extension.swift */, 158 | ); 159 | path = Foundation; 160 | sourceTree = ""; 161 | }; 162 | BFF95FE51F53DD18005BC83D /* UIKit */ = { 163 | isa = PBXGroup; 164 | children = ( 165 | BFF95FE61F53DD18005BC83D /* Language+Extension.swift */, 166 | BFF95FE91F53DD18005BC83D /* UIButton+Utils.swift */, 167 | BFF95FEA1F53DD18005BC83D /* UICollectionView+Extension.swift */, 168 | BFF95FEB1F53DD18005BC83D /* UIColor+Extension.swift */, 169 | BFF95FEC1F53DD18005BC83D /* UIImage+Extension.swift */, 170 | BFF95FED1F53DD18005BC83D /* UIKit+Extension.swift */, 171 | BFF95FEE1F53DD18005BC83D /* UILable+Extension.swift */, 172 | BFF95FEF1F53DD18005BC83D /* UINavigationController+Navigation.swift */, 173 | BFF95FF01F53DD18005BC83D /* UIScrollView+Extension.swift */, 174 | BFF95FF11F53DD18005BC83D /* UITableView+Extension.swift */, 175 | BFF95FF21F53DD18005BC83D /* UITextField+Extension.swift */, 176 | BFF95FF31F53DD18005BC83D /* UIView+Border.swift */, 177 | BFF95FF41F53DD18005BC83D /* UIView+Extension.swift */, 178 | BFF95FF51F53DD18005BC83D /* UIView+Positioning.swift */, 179 | BFF95FF61F53DD18005BC83D /* UIViewController+Extension.swift */, 180 | BF303F681F5667DB008E6A89 /* UIDevice+Extension.swift */, 181 | ); 182 | path = UIKit; 183 | sourceTree = ""; 184 | }; 185 | BFF95FF71F53DD18005BC83D /* Helper */ = { 186 | isa = PBXGroup; 187 | children = ( 188 | BF303F6A1F566B42008E6A89 /* SKTouchID.swift */, 189 | BF303F5E1F564E72008E6A89 /* SKSystemSound.swift */, 190 | BF303F661F566335008E6A89 /* SKLog.swift */, 191 | BFF95FF81F53DD18005BC83D /* GCDHelper.swift */, 192 | BFF95FF91F53DD18005BC83D /* LocalNotificationHelper.swift */, 193 | BFF95FFA1F53DD18005BC83D /* SwiftTimer.swift */, 194 | BF303F601F5658ED008E6A89 /* SweetFunc.swift */, 195 | ); 196 | path = Helper; 197 | sourceTree = ""; 198 | }; 199 | BFF95FFB1F53DD18005BC83D /* Views */ = { 200 | isa = PBXGroup; 201 | children = ( 202 | BFF95FFC1F53DD18005BC83D /* UIInsetLabel.swift */, 203 | BFF95FFD1F53DD18005BC83D /* UIPlaceholderTextView.swift */, 204 | ); 205 | path = Views; 206 | sourceTree = ""; 207 | }; 208 | /* End PBXGroup section */ 209 | 210 | /* Begin PBXNativeTarget section */ 211 | BFF95FC91F53DCF6005BC83D /* SweetKit */ = { 212 | isa = PBXNativeTarget; 213 | buildConfigurationList = BFF95FDC1F53DCF6005BC83D /* Build configuration list for PBXNativeTarget "SweetKit" */; 214 | buildPhases = ( 215 | BFF95FC61F53DCF6005BC83D /* Sources */, 216 | BFF95FC71F53DCF6005BC83D /* Frameworks */, 217 | BFF95FC81F53DCF6005BC83D /* Resources */, 218 | ); 219 | buildRules = ( 220 | ); 221 | dependencies = ( 222 | ); 223 | name = SweetKit; 224 | productName = SweetKit; 225 | productReference = BFF95FCA1F53DCF6005BC83D /* SweetKit.app */; 226 | productType = "com.apple.product-type.application"; 227 | }; 228 | /* End PBXNativeTarget section */ 229 | 230 | /* Begin PBXProject section */ 231 | BFF95FC21F53DCF6005BC83D /* Project object */ = { 232 | isa = PBXProject; 233 | attributes = { 234 | LastSwiftUpdateCheck = 0830; 235 | LastUpgradeCheck = 0830; 236 | ORGANIZATIONNAME = DanXiao; 237 | TargetAttributes = { 238 | BFF95FC91F53DCF6005BC83D = { 239 | CreatedOnToolsVersion = 8.3.3; 240 | DevelopmentTeam = KHQ2YPM72K; 241 | ProvisioningStyle = Automatic; 242 | }; 243 | }; 244 | }; 245 | buildConfigurationList = BFF95FC51F53DCF6005BC83D /* Build configuration list for PBXProject "SweetKit" */; 246 | compatibilityVersion = "Xcode 3.2"; 247 | developmentRegion = English; 248 | hasScannedForEncodings = 0; 249 | knownRegions = ( 250 | en, 251 | Base, 252 | ); 253 | mainGroup = BFF95FC11F53DCF6005BC83D; 254 | productRefGroup = BFF95FCB1F53DCF6005BC83D /* Products */; 255 | projectDirPath = ""; 256 | projectRoot = ""; 257 | targets = ( 258 | BFF95FC91F53DCF6005BC83D /* SweetKit */, 259 | ); 260 | }; 261 | /* End PBXProject section */ 262 | 263 | /* Begin PBXResourcesBuildPhase section */ 264 | BFF95FC81F53DCF6005BC83D /* Resources */ = { 265 | isa = PBXResourcesBuildPhase; 266 | buildActionMask = 2147483647; 267 | files = ( 268 | BFF95FD81F53DCF6005BC83D /* LaunchScreen.storyboard in Resources */, 269 | BFF95FD51F53DCF6005BC83D /* Assets.xcassets in Resources */, 270 | BFF95FD31F53DCF6005BC83D /* Main.storyboard in Resources */, 271 | ); 272 | runOnlyForDeploymentPostprocessing = 0; 273 | }; 274 | /* End PBXResourcesBuildPhase section */ 275 | 276 | /* Begin PBXSourcesBuildPhase section */ 277 | BFF95FC61F53DCF6005BC83D /* Sources */ = { 278 | isa = PBXSourcesBuildPhase; 279 | buildActionMask = 2147483647; 280 | files = ( 281 | BFF960001F53DD18005BC83D /* FileManager+Extension.swift in Sources */, 282 | BFF9600A1F53DD18005BC83D /* UINavigationController+Navigation.swift in Sources */, 283 | BF303F6B1F566B42008E6A89 /* SKTouchID.swift in Sources */, 284 | BFF960011F53DD18005BC83D /* Language+Extension.swift in Sources */, 285 | BF303F691F5667DB008E6A89 /* UIDevice+Extension.swift in Sources */, 286 | BFC7BEE21F56749F0087463A /* Dictionary+Extension.swift in Sources */, 287 | BFF960071F53DD18005BC83D /* UIImage+Extension.swift in Sources */, 288 | BFF960101F53DD18005BC83D /* UIView+Positioning.swift in Sources */, 289 | BFF960061F53DD18005BC83D /* UIColor+Extension.swift in Sources */, 290 | BFF9600C1F53DD18005BC83D /* UITableView+Extension.swift in Sources */, 291 | BFF95FD01F53DCF6005BC83D /* ViewController.swift in Sources */, 292 | BFF9600B1F53DD18005BC83D /* UIScrollView+Extension.swift in Sources */, 293 | BFF9600D1F53DD18005BC83D /* UITextField+Extension.swift in Sources */, 294 | BFF960121F53DD18005BC83D /* GCDHelper.swift in Sources */, 295 | BFF960081F53DD18005BC83D /* UIKit+Extension.swift in Sources */, 296 | BFF960161F53DD18005BC83D /* UIPlaceholderTextView.swift in Sources */, 297 | BFF960111F53DD18005BC83D /* UIViewController+Extension.swift in Sources */, 298 | BFF960151F53DD18005BC83D /* UIInsetLabel.swift in Sources */, 299 | BFF960041F53DD18005BC83D /* UIButton+Utils.swift in Sources */, 300 | BFF960091F53DD18005BC83D /* UILable+Extension.swift in Sources */, 301 | BF303F651F565D9C008E6A89 /* String+Validate.swift in Sources */, 302 | BFF9600E1F53DD18005BC83D /* UIView+Border.swift in Sources */, 303 | BF303F5F1F564E72008E6A89 /* SKSystemSound.swift in Sources */, 304 | BFF960131F53DD18005BC83D /* LocalNotificationHelper.swift in Sources */, 305 | BFF960051F53DD18005BC83D /* UICollectionView+Extension.swift in Sources */, 306 | BF303F611F5658ED008E6A89 /* SweetFunc.swift in Sources */, 307 | BFF95FCE1F53DCF6005BC83D /* AppDelegate.swift in Sources */, 308 | BFF960141F53DD18005BC83D /* SwiftTimer.swift in Sources */, 309 | BFF95FFE1F53DD18005BC83D /* Date+Extension.swift in Sources */, 310 | BF303F671F566335008E6A89 /* SKLog.swift in Sources */, 311 | BFF9600F1F53DD18005BC83D /* UIView+Extension.swift in Sources */, 312 | BF303F641F565D9C008E6A89 /* String+Extension.swift in Sources */, 313 | BFF95FFF1F53DD18005BC83D /* Collection+Extension.swift in Sources */, 314 | ); 315 | runOnlyForDeploymentPostprocessing = 0; 316 | }; 317 | /* End PBXSourcesBuildPhase section */ 318 | 319 | /* Begin PBXVariantGroup section */ 320 | BFF95FD11F53DCF6005BC83D /* Main.storyboard */ = { 321 | isa = PBXVariantGroup; 322 | children = ( 323 | BFF95FD21F53DCF6005BC83D /* Base */, 324 | ); 325 | name = Main.storyboard; 326 | sourceTree = ""; 327 | }; 328 | BFF95FD61F53DCF6005BC83D /* LaunchScreen.storyboard */ = { 329 | isa = PBXVariantGroup; 330 | children = ( 331 | BFF95FD71F53DCF6005BC83D /* Base */, 332 | ); 333 | name = LaunchScreen.storyboard; 334 | sourceTree = ""; 335 | }; 336 | /* End PBXVariantGroup section */ 337 | 338 | /* Begin XCBuildConfiguration section */ 339 | BFF95FDA1F53DCF6005BC83D /* Debug */ = { 340 | isa = XCBuildConfiguration; 341 | buildSettings = { 342 | ALWAYS_SEARCH_USER_PATHS = NO; 343 | CLANG_ANALYZER_NONNULL = YES; 344 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 345 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 346 | CLANG_CXX_LIBRARY = "libc++"; 347 | CLANG_ENABLE_MODULES = YES; 348 | CLANG_ENABLE_OBJC_ARC = YES; 349 | CLANG_WARN_BOOL_CONVERSION = YES; 350 | CLANG_WARN_CONSTANT_CONVERSION = YES; 351 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 352 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 353 | CLANG_WARN_EMPTY_BODY = YES; 354 | CLANG_WARN_ENUM_CONVERSION = YES; 355 | CLANG_WARN_INFINITE_RECURSION = YES; 356 | CLANG_WARN_INT_CONVERSION = YES; 357 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 358 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 359 | CLANG_WARN_UNREACHABLE_CODE = YES; 360 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 361 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 362 | COPY_PHASE_STRIP = NO; 363 | DEBUG_INFORMATION_FORMAT = dwarf; 364 | ENABLE_STRICT_OBJC_MSGSEND = YES; 365 | ENABLE_TESTABILITY = YES; 366 | GCC_C_LANGUAGE_STANDARD = gnu99; 367 | GCC_DYNAMIC_NO_PIC = NO; 368 | GCC_NO_COMMON_BLOCKS = YES; 369 | GCC_OPTIMIZATION_LEVEL = 0; 370 | GCC_PREPROCESSOR_DEFINITIONS = ( 371 | "DEBUG=1", 372 | "$(inherited)", 373 | ); 374 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 375 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 376 | GCC_WARN_UNDECLARED_SELECTOR = YES; 377 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 378 | GCC_WARN_UNUSED_FUNCTION = YES; 379 | GCC_WARN_UNUSED_VARIABLE = YES; 380 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 381 | MTL_ENABLE_DEBUG_INFO = YES; 382 | ONLY_ACTIVE_ARCH = YES; 383 | SDKROOT = iphoneos; 384 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 385 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 386 | }; 387 | name = Debug; 388 | }; 389 | BFF95FDB1F53DCF6005BC83D /* Release */ = { 390 | isa = XCBuildConfiguration; 391 | buildSettings = { 392 | ALWAYS_SEARCH_USER_PATHS = NO; 393 | CLANG_ANALYZER_NONNULL = YES; 394 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 395 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 396 | CLANG_CXX_LIBRARY = "libc++"; 397 | CLANG_ENABLE_MODULES = YES; 398 | CLANG_ENABLE_OBJC_ARC = YES; 399 | CLANG_WARN_BOOL_CONVERSION = YES; 400 | CLANG_WARN_CONSTANT_CONVERSION = YES; 401 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 402 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 403 | CLANG_WARN_EMPTY_BODY = YES; 404 | CLANG_WARN_ENUM_CONVERSION = YES; 405 | CLANG_WARN_INFINITE_RECURSION = YES; 406 | CLANG_WARN_INT_CONVERSION = YES; 407 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 408 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 409 | CLANG_WARN_UNREACHABLE_CODE = YES; 410 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 411 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 412 | COPY_PHASE_STRIP = NO; 413 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 414 | ENABLE_NS_ASSERTIONS = NO; 415 | ENABLE_STRICT_OBJC_MSGSEND = YES; 416 | GCC_C_LANGUAGE_STANDARD = gnu99; 417 | GCC_NO_COMMON_BLOCKS = YES; 418 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 419 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 420 | GCC_WARN_UNDECLARED_SELECTOR = YES; 421 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 422 | GCC_WARN_UNUSED_FUNCTION = YES; 423 | GCC_WARN_UNUSED_VARIABLE = YES; 424 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 425 | MTL_ENABLE_DEBUG_INFO = NO; 426 | SDKROOT = iphoneos; 427 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 428 | VALIDATE_PRODUCT = YES; 429 | }; 430 | name = Release; 431 | }; 432 | BFF95FDD1F53DCF6005BC83D /* Debug */ = { 433 | isa = XCBuildConfiguration; 434 | buildSettings = { 435 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 436 | DEVELOPMENT_TEAM = KHQ2YPM72K; 437 | INFOPLIST_FILE = SweetKit/Info.plist; 438 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 439 | PRODUCT_BUNDLE_IDENTIFIER = com.lovonse.SweetKit; 440 | PRODUCT_NAME = "$(TARGET_NAME)"; 441 | SWIFT_VERSION = 3.0; 442 | }; 443 | name = Debug; 444 | }; 445 | BFF95FDE1F53DCF6005BC83D /* Release */ = { 446 | isa = XCBuildConfiguration; 447 | buildSettings = { 448 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 449 | DEVELOPMENT_TEAM = KHQ2YPM72K; 450 | INFOPLIST_FILE = SweetKit/Info.plist; 451 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 452 | PRODUCT_BUNDLE_IDENTIFIER = com.lovonse.SweetKit; 453 | PRODUCT_NAME = "$(TARGET_NAME)"; 454 | SWIFT_VERSION = 3.0; 455 | }; 456 | name = Release; 457 | }; 458 | /* End XCBuildConfiguration section */ 459 | 460 | /* Begin XCConfigurationList section */ 461 | BFF95FC51F53DCF6005BC83D /* Build configuration list for PBXProject "SweetKit" */ = { 462 | isa = XCConfigurationList; 463 | buildConfigurations = ( 464 | BFF95FDA1F53DCF6005BC83D /* Debug */, 465 | BFF95FDB1F53DCF6005BC83D /* Release */, 466 | ); 467 | defaultConfigurationIsVisible = 0; 468 | defaultConfigurationName = Release; 469 | }; 470 | BFF95FDC1F53DCF6005BC83D /* Build configuration list for PBXNativeTarget "SweetKit" */ = { 471 | isa = XCConfigurationList; 472 | buildConfigurations = ( 473 | BFF95FDD1F53DCF6005BC83D /* Debug */, 474 | BFF95FDE1F53DCF6005BC83D /* Release */, 475 | ); 476 | defaultConfigurationIsVisible = 0; 477 | defaultConfigurationName = Release; 478 | }; 479 | /* End XCConfigurationList section */ 480 | }; 481 | rootObject = BFF95FC21F53DCF6005BC83D /* Project object */; 482 | } 483 | -------------------------------------------------------------------------------- /SweetKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SweetKit/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SweetKit 4 | // 5 | // Created by danxiao on 2017/8/28. 6 | // Copyright © 2017年 DanXiao. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 17 | 18 | SKLog.info(window?.frame, "121", "21212", self, self.window?.rootViewController) 19 | SKLog.info(window?.frame, "121", "21212") 20 | SKLog.info(window?.frame, "121", "21212") 21 | SKLog.info(window?.frame, "121", "21212") 22 | SKLog.info(window?.frame, "121", "21212") 23 | SKLog.info(window?.frame, "121", "21212") 24 | SKLog.info(window?.frame, "121", "21212") 25 | return true 26 | } 27 | 28 | func applicationWillResignActive(_ application: UIApplication) { 29 | // 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. 30 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 31 | } 32 | 33 | func applicationDidEnterBackground(_ application: UIApplication) { 34 | // 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. 35 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 36 | 37 | SKLog.saveLog() 38 | SKLog.clear() 39 | } 40 | 41 | func applicationWillEnterForeground(_ application: UIApplication) { 42 | // 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. 43 | } 44 | 45 | func applicationDidBecomeActive(_ application: UIApplication) { 46 | // 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. 47 | } 48 | 49 | func applicationWillTerminate(_ application: UIApplication) { 50 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 51 | } 52 | 53 | 54 | } 55 | 56 | -------------------------------------------------------------------------------- /SweetKit/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /SweetKit/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 | -------------------------------------------------------------------------------- /SweetKit/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 | -------------------------------------------------------------------------------- /SweetKit/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /SweetKit/SweetKit/Extensions/Foundation/Collection+Extension.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension Collection { 4 | 5 | public func toJSONString(prettify: Bool = false) -> String? { 6 | guard JSONSerialization.isValidJSONObject(self) else { return nil } 7 | let options = (prettify == true) ? JSONSerialization.WritingOptions.prettyPrinted : JSONSerialization.WritingOptions() 8 | let data = try? JSONSerialization.data(withJSONObject: self, options: options) 9 | return String(data: data!, encoding: .utf8) 10 | } 11 | 12 | public func toJSONData(prettify: Bool = false) -> Data? { 13 | guard JSONSerialization.isValidJSONObject(self) else { 14 | return nil 15 | } 16 | let options = (prettify == true) ? JSONSerialization.WritingOptions.prettyPrinted : JSONSerialization.WritingOptions() 17 | return try? JSONSerialization.data(withJSONObject: self, options: options) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /SweetKit/SweetKit/Extensions/Foundation/Date+Extension.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension Date { 4 | 5 | /// 设置、获得当前年度 6 | public var year: Int { 7 | get { 8 | let calendar = Calendar.autoupdatingCurrent 9 | let components = calendar.dateComponents([.year], from: self) 10 | 11 | guard let year = components.year else { 12 | return 0 13 | } 14 | 15 | return year 16 | } 17 | set { 18 | update(components: [.year: newValue]) 19 | } 20 | } 21 | 22 | /// 设置、获得当前月 23 | public var month: Int { 24 | get { 25 | let calendar = Calendar.autoupdatingCurrent 26 | let components = calendar.dateComponents([.month], from: self) 27 | 28 | guard let month = components.month else { 29 | return 0 30 | } 31 | 32 | return month 33 | } 34 | set { 35 | update(components: [.month: newValue]) 36 | } 37 | } 38 | 39 | /// 设置、获得当前天 40 | public var day: Int { 41 | get { 42 | let calendar = Calendar.autoupdatingCurrent 43 | let components = calendar.dateComponents([.day], from: self) 44 | 45 | guard let day = components.day else { 46 | return 0 47 | } 48 | 49 | return day 50 | } 51 | set { 52 | update(components: [.day: newValue]) 53 | } 54 | } 55 | 56 | /// 设置、获得当前小时 57 | public var hour: Int { 58 | get { 59 | let calendar = Calendar.autoupdatingCurrent 60 | let components = calendar.dateComponents([.hour], from: self) 61 | 62 | guard let hour = components.hour else { 63 | return 0 64 | } 65 | 66 | return hour 67 | } 68 | set { 69 | update(components: [.hour: newValue]) 70 | } 71 | } 72 | 73 | /// 设置、获得当前分 74 | public var minute: Int { 75 | get { 76 | let calendar = Calendar.autoupdatingCurrent 77 | let components = calendar.dateComponents([.minute], from: self) 78 | 79 | guard let minute = components.minute else { 80 | return 0 81 | } 82 | 83 | return minute 84 | } 85 | set { 86 | update(components: [.minute: newValue]) 87 | } 88 | } 89 | 90 | /// 设置、获得当前秒 91 | public var second: Int { 92 | get { 93 | let calendar = Calendar.autoupdatingCurrent 94 | let components = calendar.dateComponents([.second], from: self) 95 | 96 | guard let second = components.second else { 97 | return 0 98 | } 99 | 100 | return second 101 | } 102 | set { 103 | update(components: [.second: newValue]) 104 | } 105 | } 106 | 107 | /// 设置、获得当前纳秒 108 | public var nanosecond: Int { 109 | let calendar = Calendar.autoupdatingCurrent 110 | let components = calendar.dateComponents([.nanosecond], from: self) 111 | 112 | guard let nanosecond = components.nanosecond else { 113 | return 0 114 | } 115 | 116 | return nanosecond 117 | } 118 | 119 | /// 当前星期 120 | /// - 1 - Sunday. 121 | /// - 2 - Monday. 122 | /// - 3 - Tuerday. 123 | /// - 4 - Wednesday. 124 | /// - 5 - Thursday. 125 | /// - 6 - Friday. 126 | /// - 7 - Saturday. 127 | public var weekday: Int { 128 | let calendar = Calendar.autoupdatingCurrent 129 | let components = calendar.dateComponents([.weekday], from: self) 130 | 131 | guard let weekday = components.weekday else { 132 | return 0 133 | } 134 | 135 | return weekday 136 | } 137 | 138 | /// 编辑日期组件 139 | /// 140 | /// - year: Year component. 141 | /// - month: Month component. 142 | /// - day: Day component. 143 | /// - hour: Hour component. 144 | /// - minute: Minute component. 145 | /// - second: Second component. 146 | public enum EditableDateComponents: Int { 147 | case year 148 | case month 149 | case day 150 | case hour 151 | case minute 152 | case second 153 | } 154 | 155 | /// 更新当前日期组件。 156 | /// 157 | /// - components: 需要更新的组件和值的字典 158 | public mutating func update(components: [EditableDateComponents: Int]) { 159 | let autoupdatingCalendar = Calendar.autoupdatingCurrent 160 | var dateComponents = autoupdatingCalendar.dateComponents([.year, .month, .day, .weekday, .hour, .minute, .second, .nanosecond], from: self) 161 | 162 | for (component, value) in components { 163 | switch component { 164 | case .year: 165 | dateComponents.year = value 166 | case .month: 167 | dateComponents.month = value 168 | case .day: 169 | dateComponents.day = value 170 | case .hour: 171 | dateComponents.hour = value 172 | case .minute: 173 | dateComponents.minute = value 174 | case .second: 175 | dateComponents.second = value 176 | } 177 | } 178 | 179 | let calendar = Calendar(identifier: autoupdatingCalendar.identifier) 180 | guard let date = calendar.date(from: dateComponents) else { 181 | return 182 | } 183 | 184 | self = date 185 | } 186 | 187 | 188 | /// 从年、月和日创建一个日期对象。 189 | /// 190 | /// - Parameters: 191 | /// - year: Year. 192 | /// - month: Month. 193 | /// - day: Day. 194 | /// - hour: Hour. 195 | /// - minute: Minute. 196 | /// - second: Second. 197 | public init?(year: Int, month: Int, day: Int, hour: Int = 0, minute: Int = 0, second: Int = 0) { 198 | var components = DateComponents() 199 | components.year = year 200 | components.month = month 201 | components.day = day 202 | components.hour = hour 203 | components.minute = minute 204 | components.second = second 205 | 206 | let calendar = Calendar.autoupdatingCurrent 207 | guard let date = calendar.date(from: components) else { 208 | return nil 209 | } 210 | self = date 211 | } 212 | 213 | /// 比较自己与另一个日期 214 | /// 215 | /// - Returns: 如果是同一天,则返回true,否则是false 216 | public func isSame(_ anotherDate: Date) -> Bool { 217 | let calendar = Calendar.autoupdatingCurrent 218 | let componentsSelf = calendar.dateComponents([.year, .month, .day], from: self) 219 | let componentsAnotherDate = calendar.dateComponents([.year, .month, .day], from: anotherDate) 220 | 221 | return componentsSelf.year == componentsAnotherDate.year && componentsSelf.month == componentsAnotherDate.month && componentsSelf.day == componentsAnotherDate.day 222 | } 223 | 224 | // 判断是否是今天 225 | public func isToday() -> Bool { 226 | return self.isSame(Date()) 227 | } 228 | 229 | // 判断是否是昨天 230 | public static func isLastDay (dateString : String) -> Bool { 231 | let todayTimestamp = self.getTimestamp(dateString: today()) 232 | let lastdayTimestamp = self.getTimestamp(dateString: dateString) 233 | return lastdayTimestamp == todayTimestamp-(24*60*60) 234 | } 235 | 236 | // 获取今天日期字符串 237 | public static func today() -> String { 238 | let dataFormatter : DateFormatter = DateFormatter() 239 | dataFormatter.dateFormat = "yyyy-MM-dd" 240 | let now : Date = Date() 241 | return dataFormatter.string(from: now) 242 | } 243 | 244 | 245 | // yyyy-MM-dd格式 转 MM月dd日 246 | public static func formattDay (dataString : String) -> String { 247 | if dataString.length <= 0 { 248 | return "errorDate" 249 | } 250 | let dateFormatter : DateFormatter = DateFormatter() 251 | dateFormatter.dateFormat = "yyyy-MM-dd" 252 | let date: Date = dateFormatter.date(from: dataString)! 253 | 254 | 255 | // 转换成xx月xx日格式 256 | let newDateFormatter : DateFormatter = DateFormatter() 257 | newDateFormatter.dateFormat = "MM月dd日" 258 | return newDateFormatter.string(from: date) 259 | } 260 | 261 | public static func formattYYYYMMDDHHMMSS(dateString: String) -> Date { 262 | let dateFormatter = DateFormatter() 263 | dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" 264 | return dateFormatter.date(from: dateString) ?? Date() 265 | } 266 | 267 | 268 | // 根据日期获取时间戳 269 | public static func getTimestamp (dateString : String) -> TimeInterval { 270 | if dateString.length <= 0 { 271 | return 0 272 | } 273 | let newDateStirng = dateString.appending(" 00:00:00") 274 | 275 | let formatter : DateFormatter = DateFormatter() 276 | formatter.dateStyle = DateFormatter.Style.medium 277 | formatter.dateStyle = DateFormatter.Style.short 278 | formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" 279 | formatter.timeZone = TimeZone(identifier: "Asia/Beijing") 280 | 281 | let dateNow = formatter.date(from: newDateStirng) 282 | 283 | return (dateNow?.timeIntervalSince1970)! 284 | } 285 | 286 | //时间戳转化时间 287 | public static func timeStampToString(timeStamp:String) -> String { 288 | 289 | let string = NSString(string: timeStamp) 290 | let timeSta:TimeInterval = string.doubleValue 291 | let formatter = DateFormatter() 292 | formatter.dateFormat="yyyy-MM-dd HH:mm:ss" 293 | formatter.timeZone = TimeZone(identifier: "Asia/Beijing") 294 | let date = NSDate(timeIntervalSince1970: timeSta) 295 | 296 | return formatter.string(from: date as Date) 297 | } 298 | 299 | // 获取星期 300 | public static func weekWithDateString (dateString : String) -> String{ 301 | let timestamp = Date.getTimestamp(dateString: dateString) 302 | let day = Int(timestamp/86400) 303 | let array : Array = ["星期一","星期二","星期三","星期四","星期五","星期六","星期日"]; 304 | return array[(day-3)%7] 305 | // return "星期\((day-3)%7))" 306 | } 307 | 308 | public static func currentDayzero() -> Date { 309 | let calendar = Calendar.current 310 | let unitFlags = Set([.year, .month, .day, .hour, .minute, .second]) 311 | var components = calendar.dateComponents(unitFlags, from: Date()) 312 | components.timeZone = TimeZone.current 313 | components.hour = 0 314 | components.minute = 0 315 | components.second = 0 316 | if let date = calendar.date(from: components) { 317 | return date 318 | } 319 | return Date() 320 | } 321 | 322 | public var YYYYMMDDDateString : String { 323 | let dateFormatter: DateFormatter = DateFormatter(); 324 | dateFormatter.dateFormat = "yyyy-MM-dd" 325 | return dateFormatter.string(from: self) 326 | } 327 | 328 | public var HHMMDateString : String { 329 | let dateFormatter: DateFormatter = DateFormatter() 330 | dateFormatter.dateFormat = "HH:mm" 331 | return dateFormatter.string(from: self) 332 | } 333 | 334 | /// 将给定日期结构转化为格式化字符串。 335 | /// 336 | /// - Parameters: 337 | /// - info: The Date to be formatted. 338 | /// - dateSeparator: The string to be used as date separator. (Currently does not work on Linux). 339 | /// - usFormat: Set if the timestamp is in US format or not. 340 | /// - nanosecond: Set if the timestamp has to have the nanosecond. 341 | /// - Returns: Returns a String in the following format (dateSeparator = "/", usFormat to false and nanosecond to false). D/M/Y H:M:S. Example: 15/10/2013 10:38:43. 342 | public func description(dateSeparator: String = "/", usFormat: Bool = false, nanosecond: Bool = false) -> String { 343 | var description: String 344 | 345 | #if os(Linux) 346 | if usFormat { 347 | description = String(format: "%04li-%02li-%02li %02li:%02li:%02li", self.year, self.month, self.day, self.hour, self.minute, self.second) 348 | } else { 349 | description = String(format: "%02li-%02li-%04li %02li:%02li:%02li", self.month, self.day, self.year, self.hour, self.minute, self.second) 350 | } 351 | #else 352 | if usFormat { 353 | description = String(format: "%04li%@%02li%@%02li %02li:%02li:%02li", self.year, dateSeparator, self.month, dateSeparator, self.day, self.hour, self.minute, self.second) 354 | } else { 355 | description = String(format: "%02li%@%02li%@%04li %02li:%02li:%02li", self.month, dateSeparator, self.day, dateSeparator, self.year, self.hour, self.minute, self.second) 356 | } 357 | #endif 358 | 359 | if nanosecond { 360 | description += String(format: ":%03li", self.nanosecond / 1000000) 361 | } 362 | return description 363 | } 364 | } 365 | -------------------------------------------------------------------------------- /SweetKit/SweetKit/Extensions/Foundation/Dictionary+Extension.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension Dictionary where Key: ExpressibleByStringLiteral { 4 | 5 | /// Merge the keys/values of two dictionaries. 6 | /// 7 | /// let dict : [String : String] = ["key1" : "value1"] 8 | /// let dict2 : [String : String] = ["key2" : "value2"] 9 | /// let result = dict + dict2 10 | /// result["key1"] -> "value1" 11 | /// result["key2"] -> "value2" 12 | /// 13 | /// - Parameters: 14 | /// - lhs: dictionary 15 | /// - rhs: dictionary 16 | /// - Returns: An dictionary with keys and values from both. 17 | public static func +(lhs: [Key: Value], rhs: [Key: Value]) -> [Key: Value] { 18 | var result = lhs 19 | rhs.forEach{ result[$0] = $1 } 20 | return result 21 | } 22 | 23 | // MARK: - Operators 24 | 25 | /// Append the keys and values from the second dictionary into the first one. 26 | /// 27 | /// var dict : [String : String] = ["key1" : "value1"] 28 | /// let dict2 : [String : String] = ["key2" : "value2"] 29 | /// dict += dict2 30 | /// dict["key1"] -> "value1" 31 | /// dict["key2"] -> "value2" 32 | /// 33 | /// - Parameters: 34 | /// - lhs: dictionary 35 | /// - rhs: dictionary 36 | public static func +=(lhs: inout [Key: Value], rhs: [Key: Value]) { 37 | rhs.forEach({ lhs[$0] = $1}) 38 | } 39 | 40 | 41 | /// Remove contained in the array from the dictionary 42 | /// 43 | /// let dict : [String : String] = ["key1" : "value1", "key2" : "value2", "key3" : "value3"] 44 | /// let result = dict-["key1", "key2"] 45 | /// result.keys.contains("key3") -> true 46 | /// result.keys.contains("key1") -> false 47 | /// result.keys.contains("key2") -> false 48 | /// 49 | /// - Parameters: 50 | /// - lhs: dictionary 51 | /// - rhs: array with the keys to be removed. 52 | /// - Returns: a new dictionary with keys removed. 53 | public static func -(lhs: [Key: Value], keys: [Key]) -> [Key: Value]{ 54 | var result = lhs 55 | result.removeAll(keys: keys) 56 | return result 57 | } 58 | 59 | /// Remove contained in the array from the dictionary 60 | /// 61 | /// var dict : [String : String] = ["key1" : "value1", "key2" : "value2", "key3" : "value3"] 62 | /// dict-=["key1", "key2"] 63 | /// dict.keys.contains("key3") -> true 64 | /// dict.keys.contains("key1") -> false 65 | /// dict.keys.contains("key2") -> false 66 | /// 67 | /// - Parameters: 68 | /// - lhs: dictionary 69 | /// - rhs: array with the keys to be removed. 70 | public static func -=(lhs: inout [Key: Value], keys: [Key]) { 71 | lhs.removeAll(keys: keys) 72 | } 73 | 74 | /// Lowercase all keys in dictionary. 75 | /// 76 | /// var dict = ["tEstKeY": "value"] 77 | /// dict.lowercaseAllKeys() 78 | /// print(dict) // prints "["testkey": "value"]" 79 | public mutating func lowercaseAllKeys() { 80 | for key in keys { 81 | if let lowercaseKey = String(describing: key).lowercased() as? Key { 82 | self[lowercaseKey] = removeValue(forKey: key) 83 | } 84 | } 85 | } 86 | 87 | /// Check if key exists in dictionary. 88 | /// 89 | /// let dict: [String : Any] = ["testKey": "testValue", "testArrayKey": [1, 2, 3, 4, 5]] 90 | /// dict.has(key: "testKey") -> true 91 | /// dict.has(key: "anotherKey") -> false 92 | /// 93 | /// - Parameter key: key to search for 94 | /// - Returns: true if key exists in dictionary. 95 | public func has(key: Key) -> Bool { 96 | return index(forKey: key) != nil 97 | } 98 | 99 | 100 | /// Remove all keys of the dictionary. 101 | /// 102 | /// var dict : [String : String] = ["key1" : "value1", "key2" : "value2", "key3" : "value3"] 103 | /// dict.removeAll(keys: ["key1", "key2"]) 104 | /// dict.keys.contains("key3") -> true 105 | /// dict.keys.contains("key1") -> false 106 | /// dict.keys.contains("key2") -> false 107 | /// 108 | /// - Parameter keys: keys to be removed 109 | public mutating func removeAll(keys: [Key]) { 110 | keys.forEach({ removeValue(forKey: $0)}) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /SweetKit/SweetKit/Extensions/Foundation/FileManager+Extension.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension FileManager { 4 | /// 存文件到沙盒 5 | /// 6 | /// - Parameters: 7 | /// - data: 数据源 8 | /// - savePath: 保存位置 9 | /// - Returns: 删除或者保存错误 10 | public class func save(_ data: Data, savePath: String) -> Error? { 11 | if FileManager.default.fileExists(atPath: savePath) { 12 | do { 13 | try FileManager.default.removeItem(atPath: savePath) 14 | } catch let error { 15 | return error 16 | } 17 | } 18 | do { 19 | try data.write(to: URL(fileURLWithPath: savePath)) 20 | } catch let error { 21 | return error 22 | } 23 | return nil 24 | } 25 | 26 | 27 | public class func save(content: String, savePath: String) -> Error? { 28 | if FileManager.default.fileExists(atPath: savePath) { 29 | do { 30 | try FileManager.default.removeItem(atPath: savePath) 31 | } catch let error { 32 | return error 33 | } 34 | } 35 | do { 36 | try content.write(to: URL(fileURLWithPath: savePath), atomically: true, encoding: .utf8) 37 | } catch let error { 38 | return error 39 | } 40 | return nil 41 | } 42 | 43 | 44 | /// 在沙盒创建文件夹 45 | /// 46 | /// - Parameter path: 文件夹地址 47 | /// - Returns: 创建错误 48 | @discardableResult 49 | public class func create(at path: String) -> Error? { 50 | if (!FileManager.default.fileExists(atPath: path)) { 51 | do { 52 | try FileManager.default.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil) 53 | } catch let error { 54 | print("error:\(error)") 55 | return error 56 | } 57 | } 58 | return nil 59 | } 60 | 61 | /// 在沙盒中删除文件 62 | /// 63 | /// - Parameter path: 需要删除的文件地址 64 | /// - Returns: 删除错误 65 | @discardableResult 66 | public class func delete(at path: String) -> Error? { 67 | if (FileManager.default.fileExists(atPath: path)) { 68 | do { 69 | try FileManager.default.removeItem(atPath: path) 70 | } catch let error { 71 | return error 72 | } 73 | return nil 74 | } 75 | return NSError(domain: "File does not exist", code: -1, userInfo: nil) as Error 76 | } 77 | 78 | public class func rename(oldFileName: String, newFileName: String) -> Bool { 79 | do { 80 | try FileManager.default.moveItem(atPath: oldFileName, toPath: newFileName) 81 | return true 82 | } catch { 83 | print("error:\(error)") 84 | return false 85 | } 86 | } 87 | 88 | public class func copy(oldFileName: String, newFileName: String) -> Bool { 89 | do { 90 | try FileManager.default.copyItem(atPath: oldFileName, toPath: newFileName) 91 | return true 92 | } catch { 93 | return false 94 | } 95 | } 96 | 97 | public class var document: String { 98 | get { 99 | return NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] 100 | } 101 | } 102 | 103 | public class var library: String { 104 | get { 105 | return NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true)[0] 106 | } 107 | } 108 | 109 | public class var temp: String { 110 | get { 111 | return NSTemporaryDirectory() 112 | } 113 | } 114 | 115 | public class var caches: String { 116 | get { 117 | return NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)[0] 118 | } 119 | } 120 | 121 | public class var log: String { 122 | get { 123 | return document.appendingPathComponent("Logs") 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /SweetKit/SweetKit/Extensions/Foundation/String+Extension.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public extension String { 4 | 5 | /// 转换成 Int 6 | public func toUInt() -> UInt? { 7 | return UInt(self) 8 | } 9 | 10 | /// 转换成 Int, 如果转换失败,返回默认值 11 | public func toUIntWithDefault(defaultValue: UInt) -> UInt { 12 | return UInt(self) ?? defaultValue 13 | } 14 | 15 | /// 转换成 Float 16 | public var float: Float? { 17 | let numberFormatter = NumberFormatter() 18 | return numberFormatter.number(from: self)?.floatValue 19 | } 20 | 21 | /// 转换成 Float 22 | public var cgfloat: CGFloat? { 23 | return CGFloat(NumberFormatter().number(from: self) ?? 0 ) 24 | } 25 | 26 | /// 转换成 Double 27 | public var double: Double? { 28 | let numberFormatter = NumberFormatter() 29 | return numberFormatter.number(from: self)?.doubleValue 30 | } 31 | 32 | /// 转换成 Data 33 | public var data: Data? { 34 | return self.data(using: .utf8) 35 | } 36 | 37 | public func addToPasteboard() { 38 | UIPasteboard.general.string = self 39 | } 40 | 41 | /// Base64 编码 42 | public var base64encoded: String { 43 | guard let data: Data = self.data(using: .utf8) else { 44 | return "" 45 | } 46 | return data.base64EncodedString() 47 | } 48 | 49 | /// Base64 解码 50 | public var base64decoded: String { 51 | guard let data = Data(base64Encoded: String(self), options: .ignoreUnknownCharacters), 52 | let dataString = String(data: data, encoding: .utf8) else { 53 | return "" 54 | } 55 | return String(describing: dataString) 56 | } 57 | 58 | /// URL 编码 59 | public var urlEncoded: String? { 60 | return self.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlHostAllowed) 61 | } 62 | 63 | /// 获取字符串长度 64 | public var length : Int { 65 | return characters.count 66 | } 67 | 68 | /// 由换行符分隔的字符串数组。 69 | /// 70 | /// "Hello\ntest".lines -> ["Hello", "test"] 71 | /// 72 | public var lines: [String] { 73 | var result = [String]() 74 | enumerateLines { line, _ in 75 | result.append(line) 76 | } 77 | return result 78 | } 79 | 80 | /// 从字符串中获得的Bool值 81 | /// 82 | /// "1".bool -> true 83 | /// "False".bool -> false 84 | /// "Hello".bool = nil 85 | /// 86 | public var bool: Bool? { 87 | let selfLowercased = trimmed.lowercased() 88 | if selfLowercased == "true" || selfLowercased == "1" { 89 | return true 90 | } else if selfLowercased == "false" || selfLowercased == "0" { 91 | return false 92 | } 93 | return nil 94 | } 95 | 96 | public func hash() -> Int { 97 | let sum = self.characters 98 | .map { String($0).unicodeScalars.first?.value } 99 | .flatMap { $0 } 100 | .reduce(0, +) 101 | return Int(sum) 102 | } 103 | 104 | public func localized() -> String { 105 | return NSLocalizedString(self, comment: "") 106 | } 107 | 108 | /// 从 yyyy-MM-dd 格式的字符串对象 109 | /// 110 | /// "2007-06-29".date -> Optional(Date) 111 | /// 112 | public var date: Date? { 113 | let selfLowercased = trimmed.lowercased() 114 | let formatter = DateFormatter() 115 | formatter.timeZone = TimeZone.current 116 | formatter.dateFormat = "yyyy-MM-dd" 117 | return formatter.date(from: selfLowercased) 118 | } 119 | 120 | /// 从 yyyy-MM-dd HH:mm:ss 格式的字符串对象 121 | /// 122 | /// "2007-06-29 14:23:09".dateTime -> Optional(Date) 123 | /// 124 | public var dateTime: Date? { 125 | let selfLowercased = trimmed.lowercased() 126 | let formatter = DateFormatter() 127 | formatter.timeZone = TimeZone.current 128 | formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" 129 | return formatter.date(from: selfLowercased) 130 | } 131 | 132 | /// 返回给定字符的索引 133 | /// 134 | /// - Parameter character: character 135 | /// - Returns: 返回给定字符的索引,如果未找到,则返回1 136 | public func index(of character: Character) -> Int { 137 | if let index = self.characters.index(of: character) { 138 | return self.characters.distance(from: self.startIndex, to: index) 139 | } 140 | return -1 141 | } 142 | 143 | /// 创建一个给定范围的子串 144 | public func substring(with range: CountableClosedRange) -> String { 145 | return self.substring(with: Range(uncheckedBounds: (lower: range.lowerBound, upper: range.upperBound + 1))) 146 | } 147 | 148 | /// 根据给定的索引获取字符 149 | /// 150 | /// - Parameter index: index. 151 | /// - Returns: 在给定的索引中返回字符,从0开始 152 | public func character(at index: Int) -> Character { 153 | return self[self.characters.index(self.startIndex, offsetBy: index)] 154 | } 155 | 156 | /// 返回一个新的字符串,该字符串包含从字符串开始到给定索引结束的字符串。 157 | public func substring(from index: Int) -> String { 158 | return self.substring(from: self.characters.index(self.startIndex, offsetBy: index)) 159 | } 160 | 161 | /// 从给定的字符创建一个子字符串 162 | /// 163 | /// - Parameter character: character. 164 | /// - Returns: 从字符返回子字符串 165 | public func substring(from character: Character) -> String { 166 | let index: Int = self.index(of: character) 167 | guard index > -1 else { 168 | return "" 169 | } 170 | return substring(from: index + 1) 171 | } 172 | 173 | /// 返回一个新字符串,该字符串包含给定索引上的字符串,但不包括给定索引中的字符串。 174 | public func substring(to index: Int) -> String { 175 | guard index <= self.length else { 176 | return "" 177 | } 178 | return self.substring(to: self.characters.index(self.startIndex, offsetBy: index)) 179 | } 180 | 181 | /// 创建一个指定范围的子字符串 182 | public func substring(with range: Range) -> String { 183 | let start = self.characters.index(self.startIndex, offsetBy: range.lowerBound) 184 | let end = self.characters.index(self.startIndex, offsetBy: range.upperBound) 185 | 186 | return self.substring(with: start..) -> String { 193 | return substring(with: range) 194 | } 195 | 196 | /// 剪切空格和换行字符 197 | public mutating func trim() { 198 | self = trimmed 199 | } 200 | 201 | /// 剪切空格和换行字符,返回一个新字符串。 202 | public var trimmed: String { 203 | return trimmingCharacters(in: .whitespacesAndNewlines) 204 | } 205 | 206 | /// 删除两个或多个重复的空格 207 | public func removeExtraSpaces() -> String { 208 | let squashed = replacingOccurrences(of: "[ ]+", with: " ", options: .regularExpression, range: nil) 209 | return squashed.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) 210 | } 211 | 212 | /// 将给定索引中的字符作为字符串返回 213 | public subscript(index: Int) -> String { 214 | return String(self[index]) 215 | } 216 | 217 | /// 返回给定字符的索引,如果未找到,则返回-1。 218 | /// 219 | /// - Parameter character: Returns the index of the given character, -1 if not found. 220 | public subscript(character: Character) -> Int { 221 | return self.index(of: character) 222 | } 223 | 224 | /// 根据给定的索引获取字符 225 | public subscript(index: Int) -> Character { 226 | return self[self.characters.index(self.startIndex, offsetBy: index)] 227 | } 228 | 229 | /// 如果是回文,则返回true,否则是false。 230 | public func isPalindrome() -> Bool { 231 | let selfString = self.lowercased().replacingOccurrences(of: " ", with: "") 232 | let otherString = String(selfString.characters.reversed()) 233 | return selfString == otherString 234 | } 235 | 236 | /// 计算符号的个数 237 | public func countSymbols() -> Int { 238 | var countSymbol = 0 239 | for i in 0 ..< self.length { 240 | guard let character = UnicodeScalar((NSString(string: self)).character(at: i)) else { 241 | return 0 242 | } 243 | let isSymbol = CharacterSet(charactersIn: "`~!?@#$€£¥§%^&*()_+-={}[]:\";.,<>'•\\|/").contains(character) 244 | if isSymbol { 245 | countSymbol += 1 246 | } 247 | } 248 | 249 | return countSymbol 250 | } 251 | 252 | /// 计算符号的个数 253 | public func countNumbers() -> Int { 254 | var countNumber = 0 255 | for i in 0 ..< self.length { 256 | guard let character = UnicodeScalar((NSString(string: self)).character(at: i)) else { 257 | return 0 258 | } 259 | let isNumber = CharacterSet(charactersIn: "0123456789").contains(character) 260 | if isNumber { 261 | countNumber += 1 262 | } 263 | } 264 | 265 | return countNumber 266 | } 267 | 268 | /// 返回第一个大写字母字符的字符串. 269 | public func uppercasedFirst() -> String { 270 | return String(self.characters.prefix(1)).uppercased() + String(self.characters.dropFirst()) 271 | } 272 | 273 | /// 返回第一个小写字母字符的字符串 274 | public func lowercasedFirst() -> String { 275 | return String(self.characters.prefix(1)).lowercased() + String(self.characters.dropFirst()) 276 | } 277 | 278 | /// 返回指定字符串的出现次数 区分大小写或不区分 279 | public func occurrences(of string: String, caseSensitive: Bool = true) -> Int { 280 | var string = string 281 | if !caseSensitive { 282 | string = string.lowercased() 283 | } 284 | return self.lowercased().components(separatedBy: string).count - 1 285 | } 286 | 287 | /// 检查是否具有给定字符串 区分大小写或不区分 288 | public func range(of string: String, caseSensitive: Bool = true) -> Bool { 289 | return caseSensitive ? (self.range(of: string) != nil) : (self.lowercased().range(of: string.lowercased()) != nil) 290 | } 291 | 292 | /// 返回是否有给定的子字符串 区分大小写或不区分 293 | public func has(_ string: String, caseSensitive: Bool = true) -> Bool { 294 | return self.range(of: string, caseSensitive: caseSensitive) 295 | } 296 | 297 | /// 返回颠倒字符串 298 | /// 299 | /// - parameter preserveFormat: If set to true preserve the String format. 300 | /// The default value is false. 301 | /// **Example:** 302 | /// "Let's try this function?" -> 303 | /// "?noitcnuf siht yrt S'tel" 304 | public func reversed(preserveFormat: Bool = false) -> String { 305 | guard !self.characters.isEmpty else { 306 | return "" 307 | } 308 | 309 | var reversed = String(self.removeExtraSpaces().characters.reversed()) 310 | 311 | if !preserveFormat { 312 | return reversed 313 | } 314 | 315 | let words = reversed.components(separatedBy: " ").filter { $0 != "" } 316 | 317 | reversed.removeAll() 318 | for word in words { 319 | if let char = word.unicodeScalars.last { 320 | if CharacterSet.uppercaseLetters.contains(char) { 321 | reversed += word.lowercased().uppercasedFirst() 322 | } else { 323 | reversed += word.lowercased() 324 | } 325 | } else { 326 | reversed += word.lowercased() 327 | } 328 | 329 | if word != words[words.count - 1] { 330 | reversed += " " 331 | } 332 | } 333 | 334 | return reversed 335 | } 336 | 337 | /// Converts self to an UUID APNS valid (No "<>" or "-" or spaces). 338 | /// 339 | /// - Returns: Converts self to an UUID APNS valid (No "<>" or "-" or spaces). 340 | public func readableUUID() -> String { 341 | return self.trimmingCharacters(in: CharacterSet(charactersIn: "<>")).replacingOccurrences(of: " ", with: "").replacingOccurrences(of: "-", with: "") 342 | } 343 | 344 | public static func random(ofLength length: Int) -> String { 345 | guard length > 0 else { return "" } 346 | let base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 347 | return (0.. String { 365 | let string = NSString(string: self) 366 | 367 | return string.appendingPathComponent(path) 368 | } 369 | 370 | /// Appends a path extension to the string. 371 | public func appendingPathExtension(_ ext: String) -> String? { 372 | let nsSt = NSString(string: self) 373 | 374 | return nsSt.appendingPathExtension(ext) 375 | } 376 | 377 | /// Returns an array of path components. 378 | public var pathComponents: [String] { 379 | return NSString(string: self).pathComponents 380 | } 381 | 382 | /// Delete the path extension. 383 | public var deletingPathExtension: String { 384 | return NSString(string: self).deletingPathExtension 385 | } 386 | 387 | /// Returns the last path component. 388 | public var lastPathComponent: String { 389 | return NSString(string: self).lastPathComponent 390 | } 391 | 392 | /// Returns the path extension. 393 | public var pathExtension: String { 394 | return NSString(string: self).pathExtension 395 | } 396 | 397 | /// Delete the last path component. 398 | public var deletingLastPathComponent: String { 399 | return NSString(string: self).deletingLastPathComponent 400 | } 401 | } 402 | 403 | extension String { 404 | /// 字符串大小 405 | public func toSize(size: CGSize, fontSize: CGFloat, maximumNumberOfLines: Int = 0) -> CGSize { 406 | let font = UIFont.systemFont(ofSize: fontSize) 407 | var size = self.boundingRect(with: size, options: .usesLineFragmentOrigin, attributes:[NSFontAttributeName : font], context: nil).size 408 | if maximumNumberOfLines > 0 { 409 | size.height = min(size.height, CGFloat(maximumNumberOfLines) * font.lineHeight) 410 | } 411 | return size 412 | } 413 | 414 | /// 字符串宽度 415 | public func toWidth(fontSize: CGFloat, maximumNumberOfLines: Int = 0) -> CGFloat { 416 | let size = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude) 417 | return toSize(size: size, fontSize: fontSize, maximumNumberOfLines: maximumNumberOfLines).width 418 | } 419 | 420 | /// 字符串高度 421 | public func toHeight(width: CGFloat, fontSize: CGFloat, maximumNumberOfLines: Int = 0) -> CGFloat { 422 | let size = CGSize(width: width, height: CGFloat.greatestFiniteMagnitude) 423 | return toSize(size: size, fontSize: fontSize, maximumNumberOfLines: maximumNumberOfLines).height 424 | } 425 | 426 | /// 计算字符串的高度,并限制宽度 427 | public func heightWithConstrainedWidth(_ width: CGFloat, font: UIFont) -> CGFloat { 428 | let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude) 429 | let boundingBox = self.boundingRect( 430 | with: constraintRect, 431 | options: .usesLineFragmentOrigin, 432 | attributes: [NSFontAttributeName: font], 433 | context: nil) 434 | return boundingBox.height 435 | } 436 | 437 | /// 下划线 438 | public func underline() -> NSAttributedString { 439 | let underlineString = NSAttributedString(string: self, attributes: [NSUnderlineStyleAttributeName: NSUnderlineStyle.styleSingle.rawValue]) 440 | return underlineString 441 | } 442 | 443 | // 斜体 444 | public func italic() -> NSAttributedString { 445 | let italicString = NSMutableAttributedString(string: self, attributes: [NSFontAttributeName: UIFont.italicSystemFont(ofSize: UIFont.systemFontSize)]) 446 | return italicString 447 | } 448 | 449 | /// 设置指定文字颜色 450 | public func makeSubstringColor(_ text: String, color: UIColor) -> NSAttributedString { 451 | let attributedText = NSMutableAttributedString(string: self) 452 | 453 | let range = (self as NSString).range(of: text) 454 | if range.location != NSNotFound { 455 | attributedText.setAttributes([NSForegroundColorAttributeName: color], range: range) 456 | } 457 | 458 | return attributedText 459 | } 460 | } 461 | -------------------------------------------------------------------------------- /SweetKit/SweetKit/Extensions/Foundation/String+Validate.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension String { 4 | 5 | /// 是否是邮箱 6 | public func isEmail() -> Bool { 7 | let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}" 8 | // let emailRegex = "^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((\\.[a-zA-Z0-9_-]{2,3}){1,2})$" 9 | let testPredicate = NSPredicate(format:"SELF MATCHES %@", emailRegex) 10 | return testPredicate.evaluate(with: self) 11 | } 12 | 13 | 14 | /// 是否是1开头的手机号码 15 | public func isPhoneNumber() -> Bool { 16 | let regex = "^1\\d{10}$" 17 | let testPredicate = NSPredicate(format:"SELF MATCHES %@", regex) 18 | return testPredicate.evaluate(with: self) 19 | } 20 | 21 | 22 | /// 正则匹配手机号 23 | public func checkMobile() -> Bool { 24 | /** 25 | * 手机号码: 26 | * 13[0-9], 14[5,7], 15[0, 1, 2, 3, 5, 6, 7, 8, 9], 17[6, 7, 8], 18[0-9], 170[0-9] 27 | * 移动号段: 134,135,136,137,138,139,150,151,152,157,158,159,182,183,184,187,188,147,178,1705 28 | * 联通号段: 130,131,132,155,156,185,186,145,176,1709 29 | * 电信号段: 133,153,180,181,189,177,1700 30 | */ 31 | let MOBIL = "^1((3[0-9]|4[57]|5[0-35-9]|7[0678]|8[0-9])\\d{8}$)" 32 | /** 33 | * 中国移动:China Mobile 34 | * 134,135,136,137,138,139,150,151,152,157,158,159,182,183,184,187,188,147,178,1705 35 | */ 36 | let CM = "(^1(3[4-9]|4[7]|5[0-27-9]|7[8]|8[2-478])\\d{8}$)|(^1705\\d{7}$)" 37 | /** 38 | * 中国联通:China Unicom 39 | * 130,131,132,155,156,185,186,145,176,1709 40 | */ 41 | let CU = "(^1(3[0-2]|4[5]|5[56]|7[6]|8[56])\\d{8}$)|(^1709\\d{7}$)" 42 | /** 43 | * 中国电信:China Telecom 44 | * 133,153,180,181,189,177,1700 45 | */ 46 | let CT = "(^1(33|53|77|8[019])\\d{8}$)|(^1700\\d{7}$)" 47 | let regextestmobile = NSPredicate(format: "SELF MATCHES %@", MOBIL) 48 | let regextestcm = NSPredicate(format: "SELF MATCHES %@", CM) 49 | let regextestcu = NSPredicate(format: "SELF MATCHES %@", CU) 50 | let regextestct = NSPredicate(format: "SELF MATCHES %@", CT) 51 | if regextestmobile.evaluate(with: self) || regextestcm.evaluate(with: self) || regextestcu.evaluate(with: self) || regextestct.evaluate(with: self) { 52 | return true 53 | } 54 | return false 55 | } 56 | 57 | /// 是不是身份证 58 | public func isIdentityCard() -> Bool { 59 | let regex: String = "^(\\d{14}|\\d{17})(\\d|[xX])$" 60 | let testPredicate = NSPredicate(format: "SELF MATCHES %@", regex) 61 | return testPredicate.evaluate(with: self) 62 | } 63 | 64 | /// 正则匹配用户密码6-18位数字和字母组合 65 | public func checkPassword() -> Bool { 66 | let pattern = "^(?![0-9]+$)(?![a-zA-Z]+$)[a-zA-Z0-9]{6,18}" 67 | let pred = NSPredicate(format: "SELF MATCHES %@", pattern) 68 | return pred.evaluate(with: self) 69 | } 70 | 71 | /// 正则匹配URL 72 | public func checkURL() -> Bool { 73 | let pattern = "^[0-9A-Za-z]{1,50}" 74 | let pred = NSPredicate(format: "SELF MATCHES %@", pattern) 75 | return pred.evaluate(with: self) 76 | } 77 | 78 | /// 正则匹配用户姓名,20位的中文或英文 79 | public func checkUserName() -> Bool { 80 | let pattern = "^[a-zA-Z\\u4E00-\\u9FA5]{1,20}" 81 | let pred = NSPredicate(format: "SELF MATCHES %@", pattern) 82 | return pred.evaluate(with: self) 83 | } 84 | 85 | /// 验证是否是数字 86 | public func isNumber() -> Bool { 87 | let cs: CharacterSet = CharacterSet(charactersIn: "0123456789") 88 | 89 | let specialrang: NSRange = (self as NSString).rangeOfCharacter(from: cs) 90 | 91 | return specialrang.location != NSNotFound 92 | } 93 | 94 | /// 验证是否包含 "特殊字符" 95 | public func isSpecialCharacter() -> Bool { 96 | let character = CharacterSet(charactersIn: "@/:;()¥「」!,.?<>£"、[]{}#%-*+=_\\|~<>$€^•'@#$%^&*()_+'\"/" + 97 | "") 98 | 99 | let specialrang: NSRange = (self as NSString).rangeOfCharacter(from: character) 100 | 101 | return specialrang.location != NSNotFound 102 | } 103 | 104 | public func containerWD() -> Bool { 105 | let regex = "^\\w+:\\d+:\\w+;$" 106 | let testPredicate = NSPredicate(format:"SELF MATCHES %@", regex) 107 | return testPredicate.evaluate(with: self) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /SweetKit/SweetKit/Extensions/UIKit/Language+Extension.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public extension UINavigationItem { 4 | @IBInspectable var language: String { 5 | set { 6 | title = newValue.localized() 7 | } 8 | get { 9 | return "" 10 | } 11 | } 12 | } 13 | 14 | public extension UIBarButtonItem { 15 | @IBInspectable var language: String { 16 | set { 17 | title = newValue.localized() 18 | } 19 | get { 20 | return "" 21 | } 22 | } 23 | } 24 | 25 | public extension UIView { 26 | @IBInspectable public var language: String { 27 | set { 28 | if newValue != "" { 29 | setLanguage(language: newValue) } 30 | } 31 | get { 32 | return "" 33 | } 34 | } 35 | 36 | public func setLanguage(language: String) { 37 | if let label = self as? UILabel { 38 | label.text = language.localized() 39 | } 40 | //normal 41 | //heighlighted 42 | //selected 43 | //normal:value 44 | if let button = self as? UIButton { 45 | let languages = language.components(separatedBy: ";") 46 | for values in languages { 47 | let titles = values.components(separatedBy: ":") 48 | if let key = titles.first, let value = titles.last { 49 | if key == "normal" { 50 | button.setTitle(value.localized(), for: UIControlState.normal) 51 | } 52 | if key == "heighlighted" { 53 | button.setTitle(value.localized(), for: UIControlState.highlighted) 54 | } 55 | if key == "selected" { 56 | button.setTitle(value.localized(), for: UIControlState.selected) 57 | } 58 | } 59 | } 60 | if language.length == 0 { 61 | button.setTitle(language.localized(), for: UIControlState.normal) 62 | } 63 | 64 | } 65 | } 66 | 67 | @IBInspectable public var placeholaerLanguage: String { 68 | set { 69 | if newValue != "" { 70 | setPlaceholaerLanguage(language: newValue) 71 | } 72 | } 73 | get { 74 | return "" 75 | } 76 | } 77 | 78 | public func setPlaceholaerLanguage(language: String) { 79 | if let textField = self as? UITextField { 80 | textField.placeholder = language.localized() 81 | } 82 | } 83 | 84 | @IBInspectable public var buttonDefaultLanguage: String { 85 | set { 86 | if newValue != "" { 87 | setButtonDefaultLanguage(language: newValue) 88 | } 89 | } 90 | get { 91 | return "" 92 | } 93 | } 94 | 95 | public func setButtonDefaultLanguage(language: String) { 96 | if let button = self as? UIButton { 97 | button.setTitle(language.localized(), for: UIControlState.normal) 98 | } 99 | } 100 | 101 | @IBInspectable public var buttonHighlightedLanguage: String { 102 | set { 103 | if newValue != "" { 104 | setButtonHighlightedLanguage(language: newValue) 105 | } 106 | } 107 | get { 108 | return "" 109 | } 110 | } 111 | 112 | public func setButtonHighlightedLanguage(language: String) { 113 | if let button = self as? UIButton { 114 | button.setTitle(language.localized(), for: UIControlState.highlighted) 115 | } 116 | } 117 | 118 | @IBInspectable public var buttonSelectedLanguage: String { 119 | set { 120 | if newValue != "" { 121 | setButtonSelectedLanguage(language: newValue) 122 | } 123 | } 124 | get { 125 | return "" 126 | } 127 | } 128 | 129 | public func setButtonSelectedLanguage(language: String) { 130 | if let button = self as? UIButton { 131 | button.setTitle(language.localized(), for: UIControlState.selected) 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /SweetKit/SweetKit/Extensions/UIKit/UIButton+Utils.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public extension UIButton { 4 | 5 | public convenience init(title: String, titleColor: UIColor = .white) { 6 | self.init() 7 | 8 | setTitle(title, for: .normal) 9 | setTitleColor(titleColor, for: .normal) 10 | } 11 | 12 | public convenience init(frame: CGRect, title: String, backgroundImage: UIImage? = nil, highlightedBackgroundImage: UIImage? = nil) { 13 | self.init(frame: frame) 14 | self.frame = frame 15 | self.setTitle(title, for: .normal) 16 | self.setBackgroundImage(backgroundImage, for: .normal) 17 | self.setBackgroundImage(highlightedBackgroundImage, for: .highlighted) 18 | } 19 | 20 | public convenience init(frame: CGRect, image: UIImage, highlightedImage: UIImage? = nil) { 21 | self.init(frame: frame) 22 | self.frame = frame 23 | self.setImage(image, for: .normal) 24 | self.setImage(highlightedImage, for: .highlighted) 25 | } 26 | 27 | public func setTitleColor(_ color: UIColor, highlightedColor: UIColor) { 28 | self.setTitleColor(color, for: .normal) 29 | self.setTitleColor(highlightedColor, for: .highlighted) 30 | } 31 | 32 | public var normalTitle: String { 33 | get { 34 | return self.title(for: .normal) ?? "" 35 | } 36 | set { 37 | self.setTitle(newValue, for: .normal) 38 | } 39 | } 40 | 41 | public var selectedTitle: String { 42 | get { 43 | return self.title(for: .selected) ?? "" 44 | } 45 | set { 46 | self.setTitle(newValue, for: .selected) 47 | } 48 | } 49 | 50 | public var normalTitleColor: UIColor { 51 | get { 52 | return self.titleColor(for: .normal) ?? .white 53 | } 54 | set { 55 | self.setTitleColor(newValue, for: .normal) 56 | } 57 | } 58 | 59 | public var selectedTitleColor: UIColor { 60 | get { 61 | return self.titleColor(for: .selected) ?? .white 62 | } 63 | set { 64 | self.setTitleColor(newValue, for: .selected) 65 | } 66 | } 67 | 68 | public var normalImage: UIImage? { 69 | get { 70 | return self.image(for: .normal) 71 | } 72 | set { 73 | self.setImage(newValue, for: .normal) 74 | } 75 | } 76 | 77 | public var selectedImage: UIImage? { 78 | get { 79 | return self.image(for: .selected) 80 | } 81 | set { 82 | self.setImage(newValue, for: .selected) 83 | } 84 | } 85 | public var fontSize: CGFloat { 86 | get { 87 | return self.titleLabel?.font.pointSize ?? 17 88 | } 89 | set { 90 | self.titleLabel?.font = UIFont.systemFont(ofSize: newValue) 91 | } 92 | } 93 | 94 | @IBInspectable public var underline:Bool { 95 | set { 96 | if newValue { 97 | let attrs = [ 98 | NSUnderlineStyleAttributeName : NSUnderlineStyle.styleSingle.rawValue 99 | ] 100 | self.titleLabel?.attributedText = NSMutableAttributedString(string: self.titleLabel!.text!, attributes: attrs) 101 | } 102 | } 103 | get { 104 | let range = NSMakeRange(0, self.titleLabel!.text!.length) 105 | var underlined:Bool = false 106 | 107 | self.titleLabel?.attributedText?.enumerateAttributes(in: range, options: NSAttributedString.EnumerationOptions.longestEffectiveRangeNotRequired) { (attributes, range, stop) in 108 | if attributes[NSUnderlineStyleAttributeName] != nil { 109 | underlined = Bool(attributes[NSUnderlineStyleAttributeName] as! NSNumber) 110 | } 111 | } 112 | return underlined 113 | } 114 | } 115 | 116 | public func setBackgroundColor(_ color: UIColor, for state: UIControlState) { 117 | let rectangle = CGRect(size: CGSize(1)) 118 | UIGraphicsBeginImageContext(rectangle.size) 119 | 120 | let context = UIGraphicsGetCurrentContext() 121 | context?.setFillColor(color.cgColor) 122 | context?.fill(rectangle) 123 | 124 | let image = UIGraphicsGetImageFromCurrentImageContext() 125 | UIGraphicsEndImageContext() 126 | 127 | setBackgroundImage(image!, for: state) 128 | } 129 | 130 | /// 按钮图片在上文字在下 垂直对齐 131 | /// 132 | /// - Parameter spacing: 图片和文字之间的间距 133 | public func alignVertical(spacing: CGFloat = 6.0) { 134 | guard let imageSize = imageView?.image?.size, 135 | let text = currentTitle, 136 | let font = titleLabel?.font 137 | else { return } 138 | titleLabel?.textAlignment = .center 139 | titleEdgeInsets = UIEdgeInsets(top: 0.0, left: -imageSize.width, bottom: -(imageSize.height + spacing), right: 0.0) 140 | let titleSize = NSString(string: text).size(attributes: [NSFontAttributeName: font]) 141 | imageEdgeInsets = UIEdgeInsets(top: -(titleSize.height + spacing), left: 0.0, bottom: 0.0, right: -titleSize.width) 142 | let edgeOffset = abs(titleSize.height - imageSize.height) / 2.0; 143 | contentEdgeInsets = UIEdgeInsets(top: edgeOffset, left: 0.0, bottom: edgeOffset, right: 0.0) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /SweetKit/SweetKit/Extensions/UIKit/UICollectionView+Extension.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public extension UICollectionView { 4 | 5 | public func sectionWidth(at section: Int) -> CGFloat { 6 | var width = bounds.width 7 | width -= contentInset.left 8 | width -= contentInset.right 9 | 10 | if let delegate = self.delegate as? UICollectionViewDelegateFlowLayout, 11 | let inset = delegate.collectionView?(self, layout: self.collectionViewLayout, insetForSectionAt: section) { 12 | width -= inset.left 13 | width -= inset.right 14 | } else if let layout = self.collectionViewLayout as? UICollectionViewFlowLayout { 15 | width -= layout.sectionInset.left 16 | width -= layout.sectionInset.right 17 | } 18 | 19 | return width 20 | } 21 | 22 | public var indexPaths: [IndexPath] { 23 | var indexPaths = [IndexPath]() 24 | 25 | let sections = self.numberOfSections 26 | for section in 0 ..< sections { 27 | let rows = self.numberOfItems(inSection: section) 28 | for row in 0 ..< rows { 29 | indexPaths.append(IndexPath(row: row, section: section)) 30 | } 31 | } 32 | 33 | return indexPaths 34 | } 35 | 36 | public func nextIndexPath(to indexPath: IndexPath, offset: Int = 0) -> IndexPath? { 37 | return UICollectionView.nextIndexPath(to: indexPath, offset: offset, source: self.indexPaths) 38 | } 39 | 40 | public func previousIndexPath(to indexPath: IndexPath, offset: Int = 0) -> IndexPath? { 41 | return UICollectionView.nextIndexPath(to: indexPath, offset: offset, source: self.indexPaths.reversed()) 42 | } 43 | 44 | private class func nextIndexPath(to indexPath: IndexPath, offset: Int = 0, source: [IndexPath]) -> IndexPath? { 45 | var found = false 46 | var skippedResults = offset 47 | 48 | for currentIndexPath in source { 49 | if found == true { 50 | if skippedResults <= 0 { 51 | return currentIndexPath 52 | } 53 | 54 | skippedResults -= 1 55 | } 56 | 57 | if currentIndexPath == indexPath { 58 | found = true 59 | } 60 | } 61 | 62 | return nil 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /SweetKit/SweetKit/Extensions/UIKit/UIColor+Extension.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public extension UIColor { 4 | 5 | /// 快速构建rgb颜色 6 | /// 7 | /// - Parameters: 8 | /// - r: r 9 | /// - g: g 10 | /// - b: b 11 | /// - alpha: alpha 12 | /// - Returns: 返回rgb颜色对象,alpha默认1 13 | public class func colorWith(r: CGFloat, g: CGFloat, b: CGFloat, alpha: CGFloat = 1) -> UIColor{ 14 | return UIColor(red: r/255.0, green: g/255.0, blue: b/255.0, alpha: alpha) 15 | } 16 | 17 | 18 | /// 生成随机色 19 | /// 20 | /// - Returns: 返回随机色 21 | public class func randomColor() -> UIColor { 22 | let r = CGFloat(arc4random_uniform(256)) 23 | let g = CGFloat(arc4random_uniform(256)) 24 | let b = CGFloat(arc4random_uniform(256)) 25 | return UIColor.colorWith(r: r, g: g, b: b) 26 | } 27 | 28 | /// 16进制转UIColor 29 | /// 30 | /// - Parameters: 31 | /// - hex: 16进制 32 | /// - alpha: 不透明度 33 | /// - Returns: UIColor 34 | public class func colorWith(hexString:String, alpha: CGFloat = 1) -> UIColor { 35 | var cString:String = hexString.trimmingCharacters(in: NSCharacterSet.whitespacesAndNewlines).uppercased() 36 | 37 | if (cString.hasPrefix("#")) { 38 | cString = (cString as NSString).substring(from: 1) 39 | } 40 | 41 | if (cString.characters.count != 6) { 42 | return UIColor.gray 43 | } 44 | 45 | let rString = (cString as NSString).substring(to: 2) 46 | let gString = ((cString as NSString).substring(from: 2) as NSString).substring(to: 2) 47 | let bString = ((cString as NSString).substring(from: 4) as NSString).substring(to: 2) 48 | 49 | var r:CUnsignedInt = 0, g:CUnsignedInt = 0, b:CUnsignedInt = 0; 50 | Scanner(string: rString).scanHexInt32(&r) 51 | Scanner(string: gString).scanHexInt32(&g) 52 | Scanner(string: bString).scanHexInt32(&b) 53 | 54 | return UIColor(red: CGFloat(r) / 255.0, green: CGFloat(g) / 255.0, blue: CGFloat(b) / 255.0, alpha: alpha) 55 | } 56 | 57 | 58 | @nonobjc public static func hexa(_ value: UInt32) -> UIColor { 59 | let a = CGFloat((value & 0xFF000000) >> 24) / 255.0 60 | let r = CGFloat((value & 0xFF0000) >> 16) / 255.0 61 | let g = CGFloat((value & 0xFF00) >> 8) / 255.0 62 | let b = CGFloat((value & 0xFF)) / 255.0 63 | 64 | return UIColor(red: r, green: g, blue: b, alpha: a) 65 | } 66 | 67 | @nonobjc public static func hex(_ value: UInt32, alpha: CGFloat = 1.0) -> UIColor { 68 | let r = CGFloat((value & 0xFF0000) >> 16) / 255.0 69 | let g = CGFloat((value & 0xFF00) >> 8) / 255.0 70 | let b = CGFloat((value & 0xFF)) / 255.0 71 | 72 | return UIColor(red: r, green: g, blue: b, alpha: alpha) 73 | } 74 | 75 | public var hexString: String { 76 | guard let components = self.cgColor.components else { return "000000" } 77 | let r = components[0] 78 | let g = components[1] 79 | let b = components[2] 80 | return String(format: "%02X%02X%02X", Int(r * 255), Int(g * 255), Int(b * 255)) 81 | } 82 | 83 | static public func colorBetweenColors(startColor: UIColor, endColor: UIColor, percentage: CGFloat) -> UIColor { 84 | if percentage <= 0 { 85 | return startColor 86 | } else if percentage >= 1 { 87 | return endColor 88 | } 89 | 90 | var startRed: CGFloat = 0 91 | var startGreen: CGFloat = 0 92 | var startBlue: CGFloat = 0 93 | var startAlpha: CGFloat = 0 94 | startColor.getRed(&startRed, green: &startGreen, blue: &startBlue, alpha: &startAlpha) 95 | 96 | var endRed: CGFloat = 0 97 | var endGreen: CGFloat = 0 98 | var endBlue: CGFloat = 0 99 | var endAlpha: CGFloat = 0 100 | endColor.getRed(&endRed, green: &endGreen, blue: &endBlue, alpha: &endAlpha) 101 | 102 | let middleRed = Float(startRed + (percentage * (endRed - startRed))) 103 | let middleGreen = Float(startGreen + (percentage * (endGreen - startGreen))) 104 | let middleBlue = Float(startBlue + (percentage * (endBlue - startBlue))) 105 | let middleAlpha = Float(startAlpha + (percentage * (endAlpha - startAlpha))) 106 | 107 | return UIColor(colorLiteralRed: middleRed, green: middleGreen, blue: middleBlue, alpha: middleAlpha) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /SweetKit/SweetKit/Extensions/UIKit/UIDevice+Extension.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import SystemConfiguration.CaptiveNetwork 3 | 4 | // MARK: - UIDevice 5 | 6 | public extension UIDevice { 7 | 8 | /// MARK: - 获取设备型号 9 | public static func phoneModel() -> String { 10 | var systemInfo = utsname() 11 | uname(&systemInfo) 12 | let machineMirror = Mirror(reflecting: systemInfo.machine) 13 | let identifier = machineMirror.children.reduce("") { identifier, element in 14 | guard let value = element.value as? Int8 , value != 0 else { return identifier } 15 | return identifier + String(UnicodeScalar(UInt8(value))) 16 | } 17 | 18 | switch identifier { 19 | case "iPod5,1": return "iPod Touch 5" 20 | case "iPod7,1": return "iPod Touch 6" 21 | case "iPhone3,1", "iPhone3,2", "iPhone3,3": return "iPhone 4" 22 | case "iPhone4,1": return "iPhone 4s" 23 | case "iPhone5,1", "iPhone5,2": return "iPhone 5" 24 | case "iPhone5,3", "iPhone5,4": return "iPhone 5c" 25 | case "iPhone6,1", "iPhone6,2": return "iPhone 5s" 26 | case "iPhone7,2": return "iPhone 6" 27 | case "iPhone7,1": return "iPhone 6 Plus" 28 | case "iPhone8,1": return "iPhone 6s" 29 | case "iPhone8,2": return "iPhone 6s Plus" 30 | case "iPhone8,4": return "iPhone 5SE" 31 | case "iPhone9,1": return "iPhone 7" 32 | case "iPhone9,2": return "iPhone 7 Plus" 33 | case "iPad2,1", "iPad2,2", "iPad2,3", "iPad2,4":return "iPad 2" 34 | case "iPad3,1", "iPad3,2", "iPad3,3": return "iPad 3" 35 | case "iPad3,4", "iPad3,5", "iPad3,6": return "iPad 4" 36 | case "iPad4,1", "iPad4,2", "iPad4,3": return "iPad Air" 37 | case "iPad5,3", "iPad5,4": return "iPad Air 2" 38 | case "iPad2,5", "iPad2,6", "iPad2,7": return "iPad Mini" 39 | case "iPad4,4", "iPad4,5", "iPad4,6": return "iPad Mini 2" 40 | case "iPad4,7", "iPad4,8", "iPad4,9": return "iPad Mini 3" 41 | case "iPad5,1", "iPad5,2": return "iPad Mini 4" 42 | case "iPad6,7", "iPad6,8": return "iPad Pro" 43 | case "AppleTV5,3": return "Apple TV" 44 | case "i386", "x86_64": return "Simulator" 45 | default: return identifier 46 | } 47 | } 48 | 49 | /// 判断是不是模拟器 50 | public static var isSimulator: Bool { 51 | return UIDevice.phoneModel() == "Simulator" 52 | } 53 | 54 | public static var isPad: Bool { 55 | return UIDevice.current.userInterfaceIdiom == .pad 56 | } 57 | 58 | /// 获取mac地址 59 | public static func getMacAddress() -> String! { 60 | 61 | if let cfa: NSArray = CNCopySupportedInterfaces() { 62 | for x in cfa { 63 | if let dic = CFBridgingRetain(CNCopyCurrentNetworkInfo(x as! CFString)) { 64 | let mac = dic["BSSID"] 65 | return mac as! String! 66 | } 67 | } 68 | } 69 | return nil 70 | } 71 | 72 | /// 系统信息 73 | /// 74 | /// - Parameter typeSpecifier: Type of system info. 75 | /// - Returns: Return sysyem info. 76 | fileprivate static func getSysInfo(_ typeSpecifier: Int32) -> Int { 77 | var name: [Int32] = [CTL_HW, typeSpecifier] 78 | var size: Int = 2 79 | sysctl(&name, 2, nil, &size, &name, 0) 80 | var results: Int = 0 81 | sysctl(&name, 2, &results, &size, &name, 0) 82 | 83 | return results 84 | } 85 | 86 | 87 | /// 返回当前设备的CPU频率。 88 | public static var cpuFrequency: Int { 89 | return self.getSysInfo(HW_CPU_FREQ) 90 | } 91 | 92 | /// 返回当前设备总线的频率。 93 | public static var busFrequency: Int { 94 | return self.getSysInfo(HW_TB_FREQ) 95 | } 96 | 97 | /// 返回设备内存大小。 98 | public static var ramSize: Int { 99 | return self.getSysInfo(HW_MEMSIZE) 100 | } 101 | 102 | /// Returns device CPUs number. 103 | public static var cpusNumber: Int { 104 | return self.getSysInfo(HW_NCPU) 105 | } 106 | 107 | /// 返回设备总内存。 108 | public static var totalMemory: Int { 109 | return self.getSysInfo(HW_PHYSMEM) 110 | } 111 | 112 | /// Returns current device non-kernel memory. 113 | public static var userMemory: Int { 114 | return self.getSysInfo(HW_USERMEM) 115 | } 116 | 117 | /// 返回当前设备总磁盘空间 118 | public static func totalDiskSpace() -> NSNumber { 119 | do { 120 | let attributes = try FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory()) 121 | return attributes[.systemSize] as? NSNumber ?? NSNumber(value: 0.0) 122 | } catch { 123 | return NSNumber(value: 0.0) 124 | } 125 | } 126 | 127 | /// 返回当前设备空闲磁盘空间 128 | public static func freeDiskSpace() -> NSNumber { 129 | do { 130 | let attributes = try FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory()) 131 | return attributes[.systemFreeSize] as? NSNumber ?? NSNumber(value: 0.0) 132 | } catch { 133 | return NSNumber(value: 0.0) 134 | } 135 | } 136 | 137 | /// 返回当前屏幕的一个像素的点大小 138 | public class var onePixel: CGFloat { 139 | return CGFloat(1.0) / UIScreen.main.scale 140 | } 141 | 142 | 143 | /// 将浮动值返回到当前屏幕的最近像素 144 | static public func roundFloatToPixel(_ value: CGFloat) -> CGFloat { 145 | return round(value * UIScreen.main.scale) / UIScreen.main.scale 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /SweetKit/SweetKit/Extensions/UIKit/UIImage+Extension.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import CoreGraphics 3 | import CoreImage 4 | import Accelerate 5 | 6 | public extension UIImage { 7 | 8 | public func roundWithCornerRadius(_ cornerRadius: CGFloat) -> UIImage { 9 | let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: self.size) 10 | UIGraphicsBeginImageContextWithOptions(self.size, false, 1) 11 | UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).addClip() 12 | draw(in: rect) 13 | return UIGraphicsGetImageFromCurrentImageContext()! 14 | } 15 | 16 | /// 缩放 17 | // public func resize(_ size: CGSize) -> UIImage { 18 | // UIGraphicsBeginImageContextWithOptions(size, false, 0) 19 | // draw(in: CGRect(origin: CGPoint.zero, size: size)) 20 | // let image = UIGraphicsGetImageFromCurrentImageContext() 21 | // UIGraphicsEndImageContext() 22 | // return image! 23 | // } 24 | 25 | /// Resizes the image by a given rate for a given interpolation quality. 26 | /// 27 | /// - Parameters: 28 | /// - rate: The resize rate. Positive to enlarge, negative to shrink. Defaults to medium. 29 | /// - quality: The interpolation quality. 30 | /// - Returns: The resized image. 31 | public func resized(by rate: CGFloat, quality: CGInterpolationQuality = .medium) -> UIImage { 32 | let width = self.size.width * rate 33 | let height = self.size.height * rate 34 | let size = CGSize(width: width, height: height) 35 | 36 | UIGraphicsBeginImageContext(size) 37 | let context = UIGraphicsGetCurrentContext() 38 | context?.interpolationQuality = quality 39 | self.draw(in: CGRect(origin: .zero, size: size)) 40 | let resized = UIGraphicsGetImageFromCurrentImageContext()! 41 | UIGraphicsEndImageContext() 42 | 43 | return resized 44 | } 45 | 46 | // More information here: http://nshipster.com/image-resizing/ 47 | public func resizeImage(newWidth: CGFloat) -> UIImage { 48 | 49 | let scale = newWidth / self.size.width 50 | let newHeight = self.size.height * scale 51 | UIGraphicsBeginImageContextWithOptions(CGSize(width: newWidth, height: newHeight), false, 0) 52 | self.draw(in: CGRect(x: 0, y: 0, width: newWidth, height: newHeight)) 53 | let newImage = UIGraphicsGetImageFromCurrentImageContext() 54 | UIGraphicsEndImageContext() 55 | 56 | return newImage! 57 | } 58 | 59 | /// 切图 60 | public func crop(_ rect: CGRect) -> UIImage { 61 | UIGraphicsBeginImageContextWithOptions(rect.size, false, 0) 62 | draw(at: CGPoint(x: -rect.origin.x, y: -rect.origin.y)) 63 | let image = UIGraphicsGetImageFromCurrentImageContext() 64 | UIGraphicsEndImageContext() 65 | return image! 66 | } 67 | 68 | /// 根据颜色生成一张图片 69 | public static func withColor(_ color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) -> UIImage { 70 | let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height) 71 | UIGraphicsBeginImageContextWithOptions(size, false, 0) 72 | color.setFill() 73 | UIRectFill(rect) 74 | let image = UIGraphicsGetImageFromCurrentImageContext() 75 | UIGraphicsEndImageContext() 76 | return image! 77 | } 78 | 79 | /// 获取指定View图片 80 | public class func imageWithView(_ view: UIView) -> UIImage { 81 | UIGraphicsBeginImageContext(view.bounds.size) 82 | view.layer.render(in: UIGraphicsGetCurrentContext()!) 83 | let image = UIGraphicsGetImageFromCurrentImageContext() 84 | UIGraphicsEndImageContext() 85 | return image! 86 | } 87 | 88 | public func imageClipOvalImage() -> UIImage { 89 | UIGraphicsBeginImageContextWithOptions(size, false, 0.0) 90 | let ctx = UIGraphicsGetCurrentContext() 91 | let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height) 92 | ctx?.addEllipse(in: rect) 93 | ctx?.clip() 94 | draw(in: rect) 95 | let image = UIGraphicsGetImageFromCurrentImageContext() 96 | UIGraphicsEndImageContext() 97 | return image! 98 | } 99 | 100 | /// Creates a QR code from a string. 101 | /// Resizing rate defaults to 15.0 here because the CIFilter result is 31x31 pixels in size. 102 | /// 103 | /// - Parameter string: Text to be the QR Code content 104 | /// - Parameter resizeRate: The resizing rate. Positive for enlarging and negative for shrinking. Defaults to 15.0. 105 | /// - Returns: image QR Code image 106 | public static func imageQRCode(for string: String, resizeRate: CGFloat = 15.0) -> UIImage { 107 | let data = string.data(using: .isoLatin1, allowLossyConversion: false) 108 | 109 | let filter = CIFilter(name: "CIQRCodeGenerator")! 110 | filter.setDefaults() 111 | filter.setValue(data, forKey: "inputMessage") 112 | filter.setValue("H", forKey: "inputCorrectionLevel") 113 | 114 | let cImage = filter.outputImage! 115 | 116 | let qrCode = UIImage(ciImage: cImage) 117 | let qrCodeResized = qrCode.resized(by: resizeRate, quality: .none) 118 | 119 | return qrCodeResized 120 | } 121 | 122 | /// 图片高宽比 123 | public var aspectRatio: CGFloat { 124 | return size.width / size.height 125 | } 126 | 127 | public var base64: String { 128 | return UIImageJPEGRepresentation(self, 1.0)!.base64EncodedString() 129 | } 130 | 131 | 132 | /// 从base64字符串创建一个图像。 133 | public convenience init?(base64: String) { 134 | guard let data = Data(base64Encoded: base64, options: .ignoreUnknownCharacters) else { 135 | return nil 136 | } 137 | self.init(data: data) 138 | } 139 | 140 | /** 141 | Fix the image's orientation 142 | 143 | https://github.com/cosnovae/fixUIImageOrientation/blob/master/fixImageOrientation.swift 144 | 145 | - parameter src: the source image 146 | 147 | - returns: new image 148 | */ 149 | public func fixImageOrientation() -> UIImage { 150 | if self.imageOrientation == UIImageOrientation.up { 151 | return self 152 | } 153 | 154 | var transform: CGAffineTransform = CGAffineTransform.identity 155 | 156 | switch self.imageOrientation { 157 | case UIImageOrientation.down, UIImageOrientation.downMirrored: 158 | transform = transform.translatedBy(x: self.size.width, y: self.size.height) 159 | transform = transform.rotated(by: .pi) 160 | break 161 | case UIImageOrientation.left, UIImageOrientation.leftMirrored: 162 | transform = transform.translatedBy(x: self.size.width, y: 0) 163 | transform = transform.rotated(by: .pi / 2) 164 | break 165 | case UIImageOrientation.right, UIImageOrientation.rightMirrored: 166 | transform = transform.translatedBy(x: 0, y: self.size.height) 167 | transform = transform.rotated(by: -.pi / 2) 168 | break 169 | case UIImageOrientation.up, UIImageOrientation.upMirrored: 170 | break 171 | } 172 | 173 | switch self.imageOrientation { 174 | case UIImageOrientation.upMirrored, UIImageOrientation.downMirrored: 175 | transform.translatedBy(x: self.size.width, y: 0) 176 | transform.scaledBy(x: -1, y: 1) 177 | break 178 | case UIImageOrientation.leftMirrored, UIImageOrientation.rightMirrored: 179 | transform.translatedBy(x: self.size.height, y: 0) 180 | transform.scaledBy(x: -1, y: 1) 181 | case UIImageOrientation.up, UIImageOrientation.down, UIImageOrientation.left, UIImageOrientation.right: 182 | break 183 | } 184 | 185 | let ctx:CGContext = CGContext(data: nil, width: Int(self.size.width), height: Int(self.size.height), bitsPerComponent: self.cgImage!.bitsPerComponent, bytesPerRow: 0, space: self.cgImage!.colorSpace!, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)! 186 | 187 | ctx.concatenate(transform) 188 | 189 | switch self.imageOrientation { 190 | case UIImageOrientation.left, UIImageOrientation.leftMirrored, UIImageOrientation.right, UIImageOrientation.rightMirrored: 191 | ctx.draw(self.cgImage!, in: CGRect(x: 0, y: 0, width: self.size.height, height: self.size.width)) 192 | break 193 | default: 194 | ctx.draw(self.cgImage!, in: CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height)) 195 | break 196 | } 197 | 198 | let cgimage:CGImage = ctx.makeImage()! 199 | let image:UIImage = UIImage(cgImage: cgimage) 200 | 201 | return image 202 | } 203 | 204 | //https://github.com/melvitax/AFImageHelper/blob/master/AFImageHelper%2FAFImageExtension.swift 205 | public enum UIImageContentMode { 206 | case scaleToFill, scaleAspectFit, scaleAspectFill 207 | } 208 | 209 | /** 210 | Creates a resized copy of an image. 211 | 212 | - Parameter size: The new size of the image. 213 | - Parameter contentMode: The way to handle the content in the new size. 214 | - Parameter quality: The image quality 215 | 216 | - Returns A new image 217 | */ 218 | public func resize(_ size:CGSize, contentMode: UIImageContentMode = .scaleToFill, quality: CGInterpolationQuality = .medium) -> UIImage? { 219 | let horizontalRatio = size.width / self.size.width; 220 | let verticalRatio = size.height / self.size.height; 221 | var ratio: CGFloat! 222 | 223 | switch contentMode { 224 | case .scaleToFill: 225 | ratio = 1 226 | case .scaleAspectFill: 227 | ratio = max(horizontalRatio, verticalRatio) 228 | case .scaleAspectFit: 229 | ratio = min(horizontalRatio, verticalRatio) 230 | } 231 | 232 | let rect = CGRect(x: 0, y: 0, width: size.width * ratio, height: size.height * ratio) 233 | 234 | // Fix for a colorspace / transparency issue that affects some types of 235 | // images. See here: http://vocaro.com/trevor/blog/2009/10/12/resize-a-uiimage-the-right-way/comment-page-2/#comment-39951 236 | 237 | let colorSpace = CGColorSpaceCreateDeviceRGB() 238 | let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue) 239 | let context = CGContext(data: nil, width: Int(rect.size.width), height: Int(rect.size.height), bitsPerComponent: 8, bytesPerRow: 0, space: colorSpace, bitmapInfo: bitmapInfo.rawValue) 240 | 241 | let transform = CGAffineTransform.identity 242 | 243 | // Rotate and/or flip the image if required by its orientation 244 | context?.concatenate(transform); 245 | 246 | // Set the quality level to use when rescaling 247 | context!.interpolationQuality = quality 248 | 249 | 250 | //CGContextSetInterpolationQuality(context, CGInterpolationQuality(kCGInterpolationHigh.value)) 251 | 252 | // Draw into the context; this scales the image 253 | context?.draw(self.cgImage!, in: rect) 254 | 255 | // Get the resized image from the context and a UIImage 256 | let newImage = UIImage(cgImage: (context?.makeImage()!)!, scale: self.scale, orientation: self.imageOrientation) 257 | return newImage; 258 | } 259 | 260 | public func j_crop(_ bounds: CGRect) -> UIImage? { 261 | return UIImage(cgImage: (self.cgImage?.cropping(to: bounds)!)!, scale: 0.0, orientation: self.imageOrientation) 262 | } 263 | 264 | public func cropToSquare() -> UIImage? { 265 | let size = CGSize(width: self.size.width * self.scale, height: self.size.height * self.scale) 266 | let shortest = min(size.width, size.height) 267 | let left: CGFloat = size.width > shortest ? (size.width-shortest)/2 : 0 268 | let top: CGFloat = size.height > shortest ? (size.height-shortest)/2 : 0 269 | let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height) 270 | let insetRect = rect.insetBy(dx: left, dy: top) 271 | return crop(insetRect) 272 | } 273 | 274 | /// 将模糊效果应用于图像 275 | /// 276 | /// - Parameters: 277 | /// - blurRadius: Blur radius. 278 | /// - saturation: Saturation delta factor, leave it default (1.8) if you don't what is. 279 | /// - tintColor: Blur tint color, default is nil. 280 | /// - maskImage: Apply a mask image, leave it default (nil) if you don't want to mask. 281 | /// - Returns: Return the transformed image. 282 | public func blur(radius blurRadius: CGFloat, saturation: CGFloat = 1.8, tintColor: UIColor? = nil, maskImage: UIImage? = nil) -> UIImage { 283 | guard size.width > 1 && size.height > 1, let selfCGImage = cgImage else { 284 | return self 285 | } 286 | 287 | let imageRect = CGRect(origin: CGPoint(x: 0, y: 0), size: size) 288 | var effectImage = self 289 | 290 | let hasBlur = Float(blurRadius) > Float.ulpOfOne 291 | let hasSaturationChange = Float(abs(saturation - 1)) > Float.ulpOfOne 292 | 293 | if hasBlur || hasSaturationChange { 294 | UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale) 295 | guard let effectInContext = UIGraphicsGetCurrentContext() else { 296 | UIGraphicsEndImageContext() 297 | return self 298 | } 299 | effectInContext.scaleBy(x: 1, y: -1) 300 | effectInContext.translateBy(x: 0, y: -size.height) 301 | effectInContext.draw(selfCGImage, in: imageRect) 302 | var effectInBuffer = vImage_Buffer(data: effectInContext.data, height: UInt(effectInContext.height), width: UInt(effectInContext.width), rowBytes: effectInContext.bytesPerRow) 303 | 304 | UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale) 305 | guard let effectOutContext = UIGraphicsGetCurrentContext() else { 306 | UIGraphicsEndImageContext() 307 | return self 308 | } 309 | var effectOutBuffer = vImage_Buffer(data: effectOutContext.data, height: UInt(effectOutContext.height), width: UInt(effectOutContext.width), rowBytes: effectOutContext.bytesPerRow) 310 | 311 | if hasBlur { 312 | let inputRadius = blurRadius * UIScreen.main.scale 313 | var radius = UInt32(floor(inputRadius * 3.0 * CGFloat(sqrt(2 * Double.pi)) / 4 + 0.5)) 314 | if radius % 2 != 1 { 315 | radius += 1 316 | } 317 | 318 | let imageEdgeExtendFlags = vImage_Flags(kvImageEdgeExtend) 319 | vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, nil, 0, 0, radius, radius, nil, imageEdgeExtendFlags) 320 | vImageBoxConvolve_ARGB8888(&effectOutBuffer, &effectInBuffer, nil, 0, 0, radius, radius, nil, imageEdgeExtendFlags) 321 | vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, nil, 0, 0, radius, radius, nil, imageEdgeExtendFlags) 322 | } 323 | 324 | if hasSaturationChange { 325 | let s = saturation 326 | let floatingPointSaturationMatrix = [ 327 | 0.0722 + 0.9278 * s, 0.0722 - 0.0722 * s, 0.0722 - 0.0722 * s, 0, 328 | 0.7152 - 0.7152 * s, 0.7152 + 0.2848 * s, 0.7152 - 0.7152 * s, 0, 329 | 0.2126 - 0.2126 * s, 0.2126 - 0.2126 * s, 0.2126 + 0.7873 * s, 0, 330 | 0, 0, 0, 1 331 | ] 332 | 333 | let divisor: CGFloat = 256 334 | let saturationMatrix = floatingPointSaturationMatrix.map { 335 | return Int16(round($0 * divisor)) 336 | } 337 | 338 | if hasBlur { 339 | vImageMatrixMultiply_ARGB8888(&effectOutBuffer, &effectInBuffer, saturationMatrix, Int32(divisor), nil, nil, vImage_Flags(kvImageNoFlags)) 340 | } else { 341 | vImageMatrixMultiply_ARGB8888(&effectInBuffer, &effectOutBuffer, saturationMatrix, Int32(divisor), nil, nil, vImage_Flags(kvImageNoFlags)) 342 | } 343 | } 344 | 345 | effectImage = UIGraphicsGetImageFromCurrentImageContext()! 346 | UIGraphicsEndImageContext() 347 | } 348 | 349 | UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale) 350 | guard let outputContext = UIGraphicsGetCurrentContext() else { 351 | UIGraphicsEndImageContext() 352 | return self 353 | } 354 | outputContext.scaleBy(x: 1, y: -1) 355 | outputContext.translateBy(x: 0, y: -size.height) 356 | 357 | outputContext.draw(selfCGImage, in: imageRect) 358 | 359 | if hasBlur { 360 | outputContext.saveGState() 361 | if let maskImage = maskImage { 362 | outputContext.clip(to: imageRect, mask: maskImage.cgImage!) 363 | } 364 | outputContext.draw(effectImage.cgImage!, in: imageRect) 365 | outputContext.restoreGState() 366 | } 367 | 368 | if let tintColor = tintColor { 369 | outputContext.saveGState() 370 | outputContext.setFillColor(tintColor.cgColor) 371 | outputContext.fill(imageRect) 372 | outputContext.restoreGState() 373 | } 374 | 375 | guard let outputImage = UIGraphicsGetImageFromCurrentImageContext() else { 376 | UIGraphicsEndImageContext() 377 | return self 378 | } 379 | UIGraphicsEndImageContext() 380 | 381 | return outputImage 382 | } 383 | 384 | } 385 | 386 | -------------------------------------------------------------------------------- /SweetKit/SweetKit/Extensions/UIKit/UIKit+Extension.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import CoreGraphics 3 | 4 | extension URL { 5 | /// 将 query 转换为字典 6 | public var queryParameters: [String: String]? { 7 | guard let components = URLComponents(url: self, resolvingAgainstBaseURL: true), 8 | let queryItems = components.queryItems else { 9 | return nil 10 | } 11 | 12 | var parameters = [String: String]() 13 | for item in queryItems { 14 | parameters[item.name] = item.value 15 | } 16 | 17 | return parameters 18 | } 19 | } 20 | 21 | 22 | 23 | // MARK: - CGRect 24 | public extension CGRect { 25 | 26 | public init(x: CGFloat = 0, y: CGFloat = 0, width: CGFloat = 0, height: CGFloat = 0) { 27 | self.init(origin: CGPoint(x: x, y: y), size: CGSize(width: width, height: height)) 28 | } 29 | 30 | public init(x: CGFloat = 0, y: CGFloat = 0, size: CGSize) { 31 | self.init(origin: CGPoint(x: x, y: y), size: size) 32 | } 33 | 34 | public init(origin: CGPoint, width: CGFloat = 0, height: CGFloat = 0) { 35 | self.init(origin: origin, size: CGSize(width: width, height: height)) 36 | } 37 | } 38 | 39 | public extension UIEdgeInsets { 40 | 41 | public init(_ top: CGFloat = 0, left: CGFloat = 0, bottom: CGFloat = 0, right: CGFloat = 0) { 42 | self.init(top: top, left: left, bottom: bottom, right: right) 43 | } 44 | } 45 | 46 | 47 | // MARK: - CGSize 48 | public extension CGSize { 49 | 50 | public init(_ both: CGFloat) { 51 | self.init(width: both, height: both) 52 | } 53 | 54 | public init(width: CGFloat) { 55 | self.init(width: width, height: 0) 56 | } 57 | 58 | public init(height: CGFloat) { 59 | self.init(width: 0, height: height) 60 | } 61 | 62 | /** 63 | Aspect fit size 64 | 65 | - parameter boundingSize: boundingSize 66 | 67 | - returns: CGSize 68 | */ 69 | public func aspectFit(_ boundingSize: CGSize) -> CGSize { 70 | let minRatio = min(boundingSize.width / width, boundingSize.height / height) 71 | return CGSize(width: width * minRatio, height: height*minRatio) 72 | } 73 | 74 | /** 75 | Pixel size 76 | 77 | - returns: CGSize 78 | */ 79 | public func toPixel() -> CGSize { 80 | let scale = UIScreen.main.scale 81 | return CGSize(width: self.width * scale, height: self.height * scale) 82 | } 83 | } 84 | 85 | // MARK: Float、Interger 86 | 87 | public extension IntegerLiteralType { 88 | public var f: CGFloat { 89 | return CGFloat(self) 90 | } 91 | } 92 | 93 | public extension FloatLiteralType { 94 | public var f: CGFloat { 95 | return CGFloat(self) 96 | } 97 | } 98 | 99 | 100 | public extension CGFloat { 101 | 102 | public var half: CGFloat { 103 | return self * 0.5 104 | } 105 | 106 | public var double: CGFloat { 107 | return self * 2 108 | } 109 | 110 | public static var max = CGFloat.greatestFiniteMagnitude 111 | 112 | public static var min = CGFloat.leastNormalMagnitude 113 | 114 | } 115 | 116 | // MARK: - Int 117 | public extension Int { 118 | public var boolValue: Bool { 119 | return self > 0 120 | } 121 | } 122 | 123 | public extension Bool { 124 | public var reverse: Bool { 125 | return !self 126 | } 127 | 128 | public var intValue: Int { 129 | return self ? 1 : 0 130 | } 131 | } 132 | 133 | // MARK: - UserDefaults 134 | public extension UserDefaults { 135 | public static func save(at value: Any?, forKey key: String) { 136 | UserDefaults.standard.set(value, forKey: key) 137 | UserDefaults.standard.synchronize() 138 | } 139 | 140 | public static func get(forKey key: String) -> Any? { 141 | return UserDefaults.standard.object(forKey: key) 142 | } 143 | 144 | public static func remove(forKey key: String) { 145 | UserDefaults.standard.removeObject(forKey: key) 146 | UserDefaults.standard.synchronize() 147 | } 148 | } 149 | 150 | 151 | 152 | // MARK: - UIApplication 153 | public extension UIApplication { 154 | 155 | /// App版本 156 | public class func appVersion() -> String { 157 | return Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String 158 | } 159 | 160 | /// App构建版本 161 | public class func appBuild() -> String { 162 | return Bundle.main.object(forInfoDictionaryKey: kCFBundleVersionKey as String) as! String 163 | } 164 | 165 | public class var iconFilePath: String { 166 | let iconFilename = Bundle.main.object(forInfoDictionaryKey: "CFBundleIconFile") 167 | let iconBasename = (iconFilename as! NSString).deletingPathExtension 168 | let iconExtension = (iconFilename as! NSString).pathExtension 169 | return Bundle.main.path(forResource: iconBasename, ofType: iconExtension)! 170 | } 171 | 172 | public class func iconImage() -> UIImage? { 173 | guard let image = UIImage(contentsOfFile:self.iconFilePath) else { 174 | return nil 175 | } 176 | return image 177 | } 178 | 179 | public class func versionDescription() -> String { 180 | let version = appVersion() 181 | #if DEBUG 182 | return "Debug - \(version)" 183 | #else 184 | return "Release - \(version)" 185 | #endif 186 | } 187 | 188 | public class func appBundleName() -> String{ 189 | return Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as! String 190 | } 191 | 192 | public func runInBackground(_ closure: @escaping () -> Void, expirationHandler: (() -> Void)? = nil) { 193 | DispatchQueue.main.async { 194 | let taskID: UIBackgroundTaskIdentifier 195 | if let expirationHandler = expirationHandler { 196 | taskID = self.beginBackgroundTask(expirationHandler: expirationHandler) 197 | } else { 198 | taskID = self.beginBackgroundTask(expirationHandler: { }) 199 | } 200 | closure() 201 | self.endBackgroundTask(taskID) 202 | } 203 | } 204 | } 205 | 206 | 207 | 208 | 209 | 210 | 211 | // MARK: - UUID 212 | public extension UUID { 213 | public static var string: String { 214 | get { 215 | return Foundation.UUID().uuidString.replacingOccurrences(of: "-", with: "") 216 | } 217 | } 218 | } 219 | 220 | public extension UIBezierPath { 221 | public static func midpoint(p0: CGPoint, p1: CGPoint) -> CGPoint { 222 | return CGPoint(x: (p0.x + p1.x) / 2.0, y: (p0.y + p1.y) / 2.0) 223 | } 224 | } 225 | 226 | 227 | 228 | // MARK: - UITapGestureRecognizer 229 | public extension UITapGestureRecognizer { 230 | 231 | 232 | /// UILabel 添加链接点击响应 233 | /// 234 | /// - Parameters: 235 | /// - label: 需要响应事件的label 236 | /// - targetRange: 需要响应点击文字的Range 237 | /// step 1. policyPromptLabel.addGestureRecognizer(pan) 238 | /// step 2 let range1 = (text as NSString).range(of: subStr1) 239 | /// let range2 = (text as NSString).range(of: subStr2) 240 | /// step 3 gesture.didTapAttributedTextInLabel(policyPromptLabel, targetRange: range1) 241 | /// gesture.didTapAttributedTextInLabel(policyPromptLabel, targetRange: range2) 242 | public func didTapAttributedTextInLabel(_ label: UILabel, targetRange: NSRange) -> Bool { 243 | //Create instances of NSLayoutManager, NSTextContainer and NSTextStorage 244 | let layoutManager = NSLayoutManager() 245 | let textContainer = NSTextContainer(size: CGSize.zero) 246 | let textStorage = NSTextStorage(attributedString: label.attributedText!) 247 | 248 | //Configure layoutManager and textStorage 249 | layoutManager.addTextContainer(textContainer) 250 | textStorage.addLayoutManager(layoutManager) 251 | 252 | //Configure textContainer 253 | textContainer.lineFragmentPadding = 0.0 254 | textContainer.lineBreakMode = label.lineBreakMode 255 | textContainer.maximumNumberOfLines = label.numberOfLines 256 | let labelSize = label.bounds.size 257 | textContainer.size = labelSize 258 | 259 | //Find the tapped character location and compare it to the specified range 260 | let locationOfTouchInLabel = self.location(in: label) 261 | let textBoundingBox = layoutManager.usedRect(for: textContainer) 262 | let textContainerOffset = CGPoint(x: (labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x, y: (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y) 263 | let locationOfTouchInTextContainer = CGPoint(x: locationOfTouchInLabel.x - textContainerOffset.x, y: locationOfTouchInLabel.y - textContainerOffset.y) 264 | 265 | let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil) 266 | 267 | return NSLocationInRange(indexOfCharacter, targetRange) 268 | } 269 | 270 | } 271 | -------------------------------------------------------------------------------- /SweetKit/SweetKit/Extensions/UIKit/UILable+Extension.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public extension UILabel { 4 | 5 | /// Create an UILabel with the given parameters. 6 | public convenience init(frame: CGRect = .zero, 7 | text: String = "", 8 | fontSize: CGFloat = 17, 9 | color: UIColor, lines: Int = 1, 10 | shadowColor: UIColor = UIColor.clear) { 11 | self.init(frame: frame) 12 | self.text = text 13 | self.textColor = color 14 | self.numberOfLines = lines 15 | self.shadowColor = shadowColor 16 | } 17 | 18 | @IBInspectable 19 | public var underline: Bool { 20 | get { 21 | return self.underline 22 | } 23 | set { 24 | guard let text: String = self.text else { 25 | return 26 | } 27 | let textAttributes = NSMutableAttributedString(string: text) 28 | if newValue { 29 | textAttributes.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.styleSingle.rawValue, range: NSMakeRange(0, text.characters.count)) 30 | } else { 31 | textAttributes.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.styleNone.rawValue, range: NSMakeRange(0, text.characters.count)) 32 | } 33 | self.attributedText = textAttributes 34 | } 35 | } 36 | 37 | /// 设置指定文字[]大小 38 | public func makeSubstringsBold(text: [String], size: CGFloat) { 39 | text.forEach { self.makeSubstringBold($0, size: size) } 40 | } 41 | 42 | /// 设置指定文字大小 43 | public func makeSubstringBold(_ boldText: String, size: CGFloat) { 44 | let attributedText = self.attributedText!.mutableCopy() as! NSMutableAttributedString 45 | 46 | let range = ((self.text ?? "") as NSString).range(of: boldText) 47 | if range.location != NSNotFound { 48 | attributedText.setAttributes([NSFontAttributeName: UIFont.systemFont(ofSize: size)], range: range) 49 | } 50 | 51 | self.attributedText = attributedText 52 | } 53 | 54 | public func makeSubstringWeight(_ text: String) { 55 | let attributedText = self.attributedText!.mutableCopy() as! NSMutableAttributedString 56 | let range = ((self.text ?? "") as NSString).range(of: text) 57 | if range.location != NSNotFound { 58 | attributedText.setAttributes([NSFontAttributeName: UIFont.systemFont(ofSize: font.pointSize)], range: range) 59 | } 60 | self.attributedText = attributedText 61 | } 62 | 63 | /// 设置指定文字颜色 64 | public func makeSubstringColor(_ text: String, color: UIColor) { 65 | let attributedText = self.attributedText!.mutableCopy() as! NSMutableAttributedString 66 | 67 | let range = ((self.text ?? "") as NSString).range(of: text) 68 | if range.location != NSNotFound { 69 | attributedText.setAttributes([NSForegroundColorAttributeName: color], range: range) 70 | } 71 | 72 | self.attributedText = attributedText 73 | } 74 | 75 | /// 使指定文字添加删除线 76 | public func strikethrough(text: String) { 77 | self.attributedText = NSAttributedString(string: text, attributes: [NSStrikethroughStyleAttributeName: NSUnderlineStyle.styleSingle.rawValue]) 78 | } 79 | 80 | /// 设置行高 81 | public func setLineHeight(_ lineHeight: Int) { 82 | let displayText = text ?? "" 83 | let attributedString = self.attributedText!.mutableCopy() as! NSMutableAttributedString 84 | let paragraphStyle = NSMutableParagraphStyle() 85 | paragraphStyle.lineSpacing = CGFloat(lineHeight) 86 | paragraphStyle.alignment = textAlignment 87 | attributedString.addAttribute(NSParagraphStyleAttributeName, value: paragraphStyle, range: NSMakeRange(0, displayText.characters.count)) 88 | 89 | attributedText = attributedString 90 | } 91 | 92 | public func makeTransparent() { 93 | isOpaque = false 94 | backgroundColor = .clear 95 | } 96 | 97 | /** 98 | The content size of UILabel 99 | 100 | - returns: CGSize 101 | */ 102 | public func contentSize() -> CGSize { 103 | let paragraphStyle = NSMutableParagraphStyle() 104 | paragraphStyle.lineBreakMode = self.lineBreakMode 105 | paragraphStyle.alignment = self.textAlignment 106 | let attributes: [String : AnyObject] = [NSFontAttributeName: self.font, NSParagraphStyleAttributeName: paragraphStyle] 107 | let contentSize: CGSize = self.text!.boundingRect( 108 | with: self.frame.size, 109 | options: ([.usesLineFragmentOrigin, .usesFontLeading]), 110 | attributes: attributes, 111 | context: nil 112 | ).size 113 | return contentSize 114 | } 115 | 116 | /** 117 | Set UILabel's frame with the string, and limit the width. 118 | 119 | - parameter string: text 120 | - parameter width: your limit width 121 | */ 122 | public func setFrameWithString(_ string: String, width: CGFloat) { 123 | self.numberOfLines = 0 124 | let attributes: [String : AnyObject] = [ 125 | NSFontAttributeName: self.font, 126 | ] 127 | let resultSize: CGSize = string.boundingRect( 128 | with: CGSize(width: width, height: CGFloat.greatestFiniteMagnitude), 129 | options: NSStringDrawingOptions.usesLineFragmentOrigin, 130 | attributes: attributes, 131 | context: nil 132 | ).size 133 | let resultHeight: CGFloat = resultSize.height 134 | let resultWidth: CGFloat = resultSize.width 135 | var frame: CGRect = self.frame 136 | frame.size.height = resultHeight 137 | frame.size.width = resultWidth 138 | self.frame = frame 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /SweetKit/SweetKit/Extensions/UIKit/UINavigationController+Navigation.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public extension UINavigationController { 4 | 5 | public override func push(controller: UIViewController, animated: Bool = true) { 6 | pushViewController(controller, animated: animated) 7 | } 8 | 9 | @discardableResult 10 | public override func pop(animated: Bool = true) -> UIViewController? { 11 | return popViewController(animated: animated) 12 | } 13 | 14 | @discardableResult 15 | public func replace(with controller: UIViewController, animated: Bool = true) -> UIViewController? { 16 | var controllers = viewControllers 17 | let current = controllers.popLast() 18 | controllers.append(controller) 19 | 20 | setViewControllers(controllers, animated: animated) 21 | 22 | return current 23 | } 24 | 25 | @discardableResult 26 | public func popAllAndReplace(with controller: UIViewController) -> [UIViewController] { 27 | let transition = CATransition() 28 | transition.duration = 0.5 29 | transition.type = kCATransitionMoveIn 30 | transition.subtype = kCATransitionFromLeft 31 | transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) 32 | view.layer.add(transition, forKey: nil) 33 | 34 | return replaceAll(with: controller, animated: false) 35 | } 36 | 37 | @discardableResult 38 | public func replaceAll(with controller: UIViewController, animated: Bool = true) -> [UIViewController] { 39 | let currentControllers = viewControllers 40 | 41 | setViewControllers([controller], animated: animated) 42 | 43 | return currentControllers 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /SweetKit/SweetKit/Extensions/UIKit/UIScrollView+Extension.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | 4 | public extension UIScrollView { 5 | fileprivate struct AssociatedKeys { 6 | static var kKeyScrollViewVerticalIndicator = "_verticalScrollIndicator" 7 | static var kKeyScrollViewHorizontalIndicator = "_horizontalScrollIndicator" 8 | } 9 | 10 | /// YES if the scrollView's offset is at the very top. 11 | public var isAtTop: Bool { 12 | get { return self.contentOffset.y == 0.0 ? true : false } 13 | } 14 | 15 | /// YES if the scrollView's offset is at the very bottom. 16 | public var isAtBottom: Bool { 17 | get { 18 | let bottomOffset = self.contentSize.height - self.bounds.size.height 19 | return self.contentOffset.y == bottomOffset ? true : false 20 | } 21 | } 22 | 23 | /// YES if the scrollView can scroll from it's current offset position to the bottom. 24 | public var canScrollToBottom: Bool { 25 | get { return self.contentSize.height > self.bounds.size.height ? true : false } 26 | } 27 | 28 | /// The vertical scroll indicator view. 29 | public var verticalScroller: UIView { 30 | get { 31 | if (objc_getAssociatedObject(self, #function) == nil) { 32 | objc_setAssociatedObject(self, #function, self.safeValueForKey(AssociatedKeys.kKeyScrollViewVerticalIndicator), objc_AssociationPolicy.OBJC_ASSOCIATION_ASSIGN); 33 | } 34 | return objc_getAssociatedObject(self, #function) as! UIView 35 | } 36 | } 37 | 38 | /// The horizontal scroll indicator view. 39 | public var horizontalScroller: UIView { 40 | get { 41 | if (objc_getAssociatedObject(self, #function) == nil) { 42 | objc_setAssociatedObject(self, #function, self.safeValueForKey(AssociatedKeys.kKeyScrollViewHorizontalIndicator), objc_AssociationPolicy.OBJC_ASSOCIATION_ASSIGN); 43 | } 44 | return objc_getAssociatedObject(self, #function) as! UIView 45 | } 46 | } 47 | 48 | fileprivate func safeValueForKey(_ key: String) -> AnyObject{ 49 | let instanceVariable: Ivar = class_getInstanceVariable(type(of: self), key.cString(using: String.Encoding.utf8)!) 50 | return object_getIvar(self, instanceVariable) as AnyObject; 51 | } 52 | 53 | 54 | /** 55 | Sets the content offset to the top. 56 | 57 | - parameter animated: animated YES to animate the transition at a constant velocity to the new offset, NO to make the transition immediate. 58 | */ 59 | public func scrollToTopAnimated(_ animated: Bool) { 60 | if !self.isAtTop { 61 | let bottomOffset = CGPoint.zero; 62 | self.setContentOffset(bottomOffset, animated: animated) 63 | } 64 | } 65 | 66 | /** 67 | Sets the content offset to the bottom. 68 | 69 | - parameter animated: animated YES to animate the transition at a constant velocity to the new offset, NO to make the transition immediate. 70 | */ 71 | public func scrollToBottomAnimated(_ animated: Bool) { 72 | if self.canScrollToBottom && !self.isAtBottom { 73 | let bottomOffset = CGPoint(x: 0.0, y: self.contentSize.height - self.bounds.size.height) 74 | self.setContentOffset(bottomOffset, animated: animated) 75 | } 76 | } 77 | 78 | /** 79 | Stops scrolling, if it was scrolling. 80 | */ 81 | public func stopScrolling() { 82 | guard self.isDragging else { 83 | return 84 | } 85 | var offset = self.contentOffset 86 | offset.y -= 1.0 87 | self.setContentOffset(offset, animated: false) 88 | 89 | offset.y += 1.0 90 | self.setContentOffset(offset, animated: false) 91 | } 92 | } 93 | 94 | 95 | 96 | 97 | public extension UIScrollView { 98 | 99 | public convenience init(frame: CGRect, contentSize: CGSize, clipsToBounds: Bool, pagingEnabled: Bool, showScrollIndicators: Bool, delegate: UIScrollViewDelegate?) { 100 | self.init(frame: frame) 101 | self.delegate = delegate 102 | self.isPagingEnabled = pagingEnabled 103 | self.clipsToBounds = clipsToBounds 104 | self.showsVerticalScrollIndicator = showScrollIndicators 105 | self.showsHorizontalScrollIndicator = showScrollIndicators 106 | self.contentSize = contentSize 107 | } 108 | 109 | public var isOverflowVertical: Bool { 110 | return self.contentSize.height > bounds.height && bounds.height > 0 111 | } 112 | 113 | public func isReachedBottom(withTolerance tolerance: CGFloat = 0) -> Bool { 114 | guard self.isOverflowVertical else { return false } 115 | let contentOffsetBottom = self.contentOffset.y + bounds.height 116 | return contentOffsetBottom >= self.contentSize.height - tolerance 117 | } 118 | 119 | public func scrollToBottom(animated: Bool) { 120 | guard self.isOverflowVertical else { return } 121 | let targetY = self.contentSize.height + self.contentInset.bottom - bounds.height 122 | let targetOffset = CGPoint(x: 0, y: targetY) 123 | self.setContentOffset(targetOffset, animated: true) 124 | } 125 | 126 | public func scrollToTop(animated: Bool = true) { 127 | let inset = contentInset 128 | setContentOffset(CGPoint(x: -inset.left, y: -inset.top), animated: animated) 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /SweetKit/SweetKit/Extensions/UIKit/UITableView+Extension.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public extension UITableView { 4 | 5 | 6 | public func estimatedRowHeight(_ height: CGFloat) { 7 | self.rowHeight = UITableViewAutomaticDimension 8 | self.estimatedRowHeight = height 9 | } 10 | 11 | /// 隐藏 section style模式下顶部的空隙 12 | public func hideHeaderViewSpace(_ margin: CGFloat = 0.1) { 13 | self.tableHeaderView = UIView(frame: CGRect(height: margin)) 14 | } 15 | 16 | /// 隐藏空的Cell 17 | public func hideEmptyCells() { 18 | self.tableFooterView = UIView(frame: .zero) 19 | } 20 | 21 | /// Dequeue reusable UITableViewCell using class name 22 | /// 23 | /// - Parameter name: UITableViewCell type 24 | /// - Returns: UITableViewCell object with associated class name (optional value) 25 | public func dequeueReusableCell(withClass name: T.Type) -> T? { 26 | return dequeueReusableCell(withIdentifier: String(describing: name)) as? T 27 | } 28 | 29 | /// Dequeue reusable UITableViewCell using class name for indexPath 30 | /// 31 | /// - Parameters: 32 | /// - name: UITableViewCell type. 33 | /// - indexPath: location of cell in tableView. 34 | /// - Returns: UITableViewCell object with associated class name. 35 | public func dequeueReusableCell(withClass name: T.Type, for indexPath: IndexPath) -> T { 36 | return dequeueReusableCell(withIdentifier: String(describing: name), for: indexPath) as! T 37 | } 38 | 39 | /// Dequeue reusable UITableViewHeaderFooterView using class name 40 | /// 41 | /// - Parameter name: UITableViewHeaderFooterView type 42 | /// - Returns: UITableViewHeaderFooterView object with associated class name (optional value) 43 | public func dequeueReusableHeaderFooterView(withClass name: T.Type) -> T? { 44 | return dequeueReusableHeaderFooterView(withIdentifier: String(describing: name)) as? T 45 | } 46 | 47 | /// Register UITableViewHeaderFooterView using class name 48 | /// 49 | /// - Parameters: 50 | /// - nib: Nib file used to create the header or footer view. 51 | /// - name: UITableViewHeaderFooterView type. 52 | public func register(nib: UINib?, withHeaderFooterViewClass name: T.Type) { 53 | register(nib, forHeaderFooterViewReuseIdentifier: String(describing: name)) 54 | } 55 | 56 | /// Register UITableViewHeaderFooterView using class name 57 | /// 58 | /// - Parameter name: UITableViewHeaderFooterView type 59 | public func register(headerFooterViewClassWith name: T.Type) { 60 | register(T.self, forHeaderFooterViewReuseIdentifier: String(describing: name)) 61 | } 62 | 63 | /// Register UITableViewCell using class name 64 | /// 65 | /// - Parameter name: UITableViewCell type 66 | public func register(cellWithClass name: T.Type) { 67 | register(T.self, forCellReuseIdentifier: String(describing: name)) 68 | } 69 | 70 | /// Register UITableViewCell using class name 71 | /// 72 | /// - Parameters: 73 | /// - nib: Nib file used to create the tableView cell. 74 | /// - name: UITableViewCell type. 75 | public func register(nib: UINib?, withCellClass name: T.Type) { 76 | register(nib, forCellReuseIdentifier: String(describing: name)) 77 | } 78 | 79 | /// Retrive all the IndexPaths for the section. 80 | /// 81 | /// - Parameter section: The section. 82 | /// - Returns: Return an array with all the IndexPaths. 83 | public func indexPaths(section: Int) -> [IndexPath] { 84 | var indexPaths: [IndexPath] = [] 85 | let rows: Int = self.numberOfRows(inSection: section) 86 | for i in 0 ..< rows { 87 | let indexPath: IndexPath = IndexPath(row: i, section: section) 88 | indexPaths.append(indexPath) 89 | } 90 | 91 | return indexPaths 92 | } 93 | 94 | /// Retrive the next index path for the given row at section. 95 | /// 96 | /// - Parameters: 97 | /// - row: Row of the index path. 98 | /// - section: Section of the index path 99 | /// - Returns: Returns the next index path. 100 | public func nextIndexPath(row: Int, forSection section: Int) -> IndexPath? { 101 | let indexPath: [IndexPath] = self.indexPaths(section: section) 102 | guard indexPath != [] else { 103 | return nil 104 | } 105 | 106 | return indexPath[row + 1] 107 | } 108 | 109 | /// Retrive the previous index path for the given row at section 110 | /// 111 | /// - Parameters: 112 | /// - row: Row of the index path. 113 | /// - section: Section of the index path. 114 | /// - Returns: Returns the previous index path. 115 | public func previousIndexPath(row: Int, forSection section: Int) -> IndexPath? { 116 | let indexPath: [IndexPath] = self.indexPaths(section: section) 117 | guard indexPath != [] else { 118 | return nil 119 | } 120 | 121 | return indexPath[row - 1] 122 | } 123 | } 124 | 125 | 126 | public extension UITableView { 127 | public func reloadData(_ completion: @escaping ()->()) { 128 | UIView.animate(withDuration: 0, animations: { 129 | self.reloadData() 130 | }, completion:{ _ in 131 | completion() 132 | }) 133 | } 134 | 135 | 136 | public func insertRowsAtBottom(_ rows: [IndexPath]) { 137 | //保证 insert row 不闪屏 138 | UIView.setAnimationsEnabled(false) 139 | CATransaction.begin() 140 | CATransaction.setDisableActions(true) 141 | self.beginUpdates() 142 | self.insertRows(at: rows, with: .none) 143 | self.endUpdates() 144 | self.scrollToRow(at: rows[0], at: .bottom, animated: false) 145 | CATransaction.commit() 146 | UIView.setAnimationsEnabled(true) 147 | } 148 | 149 | public func totalRows() -> Int { 150 | var i = 0 151 | var rowCount = 0 152 | while i < self.numberOfSections { 153 | rowCount += self.numberOfRows(inSection: i) 154 | i += 1 155 | } 156 | return rowCount 157 | } 158 | 159 | public var lastIndexPath: IndexPath? { 160 | if (self.totalRows()-1) > 0{ 161 | return IndexPath(row: self.totalRows()-1, section: 0) 162 | } else { 163 | return nil 164 | } 165 | } 166 | 167 | //插入数据后调用 168 | public func scrollBottomWithoutFlashing() { 169 | guard let indexPath = self.lastIndexPath else { 170 | return 171 | } 172 | UIView.setAnimationsEnabled(false) 173 | CATransaction.begin() 174 | CATransaction.setDisableActions(true) 175 | self.scrollToRow(at: indexPath, at: .bottom, animated: false) 176 | CATransaction.commit() 177 | UIView.setAnimationsEnabled(true) 178 | } 179 | 180 | //键盘动画结束后调用 181 | public func scrollBottomToLastRow() { 182 | guard let indexPath = self.lastIndexPath else { 183 | return 184 | } 185 | self.scrollToRow(at: indexPath, at: .bottom, animated: false) 186 | } 187 | 188 | // func scrollToBottom(animated: Bool) { 189 | // let bottomOffset = CGPoint(x: 0, y:self.contentSize.height - self.bounds.size.height) 190 | // self.setContentOffset(bottomOffset, animated: animated) 191 | // } 192 | 193 | public var isContentInsetBottomZero: Bool { 194 | get { return self.contentInset.bottom == 0 } 195 | } 196 | 197 | public func resetContentInsetAndScrollIndicatorInsets() { 198 | self.contentInset = UIEdgeInsets.zero 199 | self.scrollIndicatorInsets = UIEdgeInsets.zero 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /SweetKit/SweetKit/Extensions/UIKit/UITextField+Extension.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public extension UITextField { 4 | 5 | /// UITextField text type. 6 | /// 7 | /// - emailAddress: UITextField is used to enter email addresses. 8 | /// - password: UITextField is used to enter passwords. 9 | /// - generic: UITextField is used to enter generic text. 10 | public enum TextType { 11 | case emailAddress 12 | case password 13 | case generic 14 | } 15 | 16 | /// Set textField for common text types. 17 | public var textType: TextType { 18 | get { 19 | if keyboardType == .emailAddress { 20 | return .emailAddress 21 | } else if isSecureTextEntry { 22 | return .password 23 | } 24 | return .generic 25 | } 26 | set { 27 | switch newValue { 28 | case .emailAddress: 29 | keyboardType = .emailAddress 30 | autocorrectionType = .no 31 | autocapitalizationType = .none 32 | isSecureTextEntry = false 33 | 34 | case .password: 35 | keyboardType = .asciiCapable 36 | autocorrectionType = .no 37 | autocapitalizationType = .none 38 | isSecureTextEntry = true 39 | 40 | case .generic: 41 | isSecureTextEntry = false 42 | 43 | } 44 | } 45 | } 46 | 47 | public var fontSize: CGFloat { 48 | get { 49 | return self.font?.pointSize ?? 17 50 | } 51 | set { 52 | self.font = UIFont.systemFont(ofSize: newValue) 53 | } 54 | } 55 | 56 | /// EZSE: Add left padding to the text in textfield 57 | public func addLeftTextPadding(_ blankSize: CGFloat) { 58 | let leftView = UIView() 59 | leftView.frame = CGRect(x: 0, y: 0, width: blankSize, height: frame.height) 60 | self.leftView = leftView 61 | self.leftViewMode = UITextFieldViewMode.always 62 | } 63 | 64 | /// EZSE: Add a image icon on the left side of the textfield 65 | public func addLeftIcon(_ image: UIImage?, frame: CGRect, imageSize: CGSize) { 66 | let leftView = UIView(frame: frame) 67 | let imgView = UIImageView() 68 | imgView.frame = CGRect(x: frame.width - 8 - imageSize.width, y: (frame.height - imageSize.height) / 2, width: imageSize.width, height: imageSize.height) 69 | imgView.image = image 70 | leftView.addSubview(imgView) 71 | self.leftView = leftView 72 | self.leftViewMode = UITextFieldViewMode.always 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /SweetKit/SweetKit/Extensions/UIKit/UIView+Border.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public class Border { 4 | enum Position: Int { 5 | case Top = 1130001 6 | case Bottom = 1130002 7 | case Left = 1130003 8 | case Right = 1130004 9 | } 10 | 11 | let size: CGFloat 12 | let color: UIColor 13 | let offset: UIEdgeInsets 14 | public init(size: CGFloat = 0.5, color: UIColor = UIColor.hex(0xE5E5E5), offset: UIEdgeInsets = .zero) { 15 | self.size = size 16 | self.color = color 17 | self.offset = offset 18 | } 19 | 20 | fileprivate func horizontal(position: Position) -> String { 21 | switch position { 22 | case .Top, .Bottom: 23 | return "H:|-(\(offset.left))-[v]-(\(offset.right))-|" 24 | case .Left: 25 | return "H:|-(\(offset.left))-[v(\(size))]" 26 | case .Right: 27 | return "H:[v(\(size))]-(\(offset.right))-|" 28 | } 29 | } 30 | fileprivate func vertical(position: Position) -> String { 31 | switch position { 32 | case .Top: 33 | return "V:|-(\(offset.top))-[v(\(size))]" 34 | case .Bottom: 35 | return "V:[v(\(size))]-(\(offset.bottom))-|" 36 | case .Left, .Right: 37 | return "V:|-(\(offset.top))-[v]-(\(offset.bottom))-|" 38 | } 39 | } 40 | } 41 | 42 | private var borderTopAssociationKey: UInt8 = 0 43 | private var borderBottomAssociationKey: UInt8 = 0 44 | private var borderLeftAssociationKey: UInt8 = 0 45 | private var borderRightAssociationKey: UInt8 = 0 46 | 47 | public extension UIView { 48 | public var borderTop: Border? { 49 | set { 50 | objc_setAssociatedObject(self, &borderTopAssociationKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 51 | setBorderUtility(newValue, position: .Top) 52 | } 53 | get { 54 | return objc_getAssociatedObject(self, &borderTopAssociationKey) as? Border 55 | } 56 | } 57 | public var borderBottom: Border? { 58 | set { 59 | objc_setAssociatedObject(self, &borderBottomAssociationKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 60 | setBorderUtility(newValue, position: .Bottom) 61 | } 62 | get { 63 | return objc_getAssociatedObject(self, &borderBottomAssociationKey) as? Border 64 | } 65 | } 66 | public var borderLeft: Border? { 67 | set { 68 | objc_setAssociatedObject(self, &borderLeftAssociationKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 69 | setBorderUtility(newValue, position: .Left) 70 | } 71 | get { 72 | return objc_getAssociatedObject(self, &borderLeftAssociationKey) as? Border 73 | } 74 | } 75 | public var borderRight: Border? { 76 | set { 77 | objc_setAssociatedObject(self, &borderRightAssociationKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 78 | setBorderUtility(newValue, position: .Right) 79 | } 80 | get { 81 | return objc_getAssociatedObject(self, &borderRightAssociationKey) as? Border 82 | } 83 | } 84 | private func setBorderUtility(_ newValue: Border?, position: Border.Position) { 85 | let BorderTag = position.rawValue 86 | 87 | let v = self.viewWithTag(BorderTag) 88 | if let border = newValue { 89 | if v != nil { 90 | v?.removeFromSuperview() 91 | } 92 | let v = UIView() 93 | v.tag = BorderTag 94 | v.backgroundColor = border.color 95 | addSubview(v) 96 | v.translatesAutoresizingMaskIntoConstraints = false 97 | self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: border.horizontal(position: position), options: .directionLeadingToTrailing, metrics: nil, views: ["v": v])) 98 | self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: border.vertical(position: position), options: .directionLeadingToTrailing, metrics: nil, views: ["v": v])) 99 | } else { 100 | if v != nil { 101 | v?.removeFromSuperview() 102 | } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /SweetKit/SweetKit/Extensions/UIKit/UIView+Extension.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public extension UIView { 4 | 5 | public convenience init(backgroundColor: UIColor) { 6 | self.init() 7 | self.backgroundColor = backgroundColor 8 | } 9 | 10 | public var rootView: UIView { 11 | if let superview = superview { 12 | return superview.rootView 13 | } else { 14 | return self 15 | } 16 | } 17 | 18 | /// 给View加上圆角 19 | @IBInspectable public var setCornerRadius: CGFloat { 20 | get { 21 | return self.layer.cornerRadius 22 | } 23 | set { 24 | self.layer.cornerRadius = newValue 25 | self.layer.masksToBounds = newValue > 0 26 | } 27 | } 28 | 29 | 30 | /// 根据类查找视图 31 | /// 32 | /// - Parameter superViewClass: 类 33 | /// - Returns: View 34 | public func findSuperView(cls superViewClass : T.Type) -> T? { 35 | 36 | var xsuperView: UIView! = self.superview! 37 | var foundSuperView: UIView! 38 | 39 | while (xsuperView != nil && foundSuperView == nil) { 40 | 41 | if xsuperView.self is T { 42 | foundSuperView = xsuperView 43 | } else { 44 | xsuperView = xsuperView.superview 45 | } 46 | } 47 | return foundSuperView as? T 48 | } 49 | 50 | /** 51 | 添加点击事件 52 | 53 | - parameter target: 对象 54 | - parameter action: 动作 55 | */ 56 | public func addTapGesture(target : AnyObject,action : Selector) { 57 | 58 | let tap = UITapGestureRecognizer(target: target, action: action) 59 | self.isUserInteractionEnabled = true 60 | self.addGestureRecognizer(tap) 61 | } 62 | 63 | /** 64 | 添加点击事件 65 | 66 | - parameter target: 对象 67 | - parameter action: 动作 68 | */ 69 | public func addLongPressGesture(target : AnyObject,action : Selector) { 70 | let longPress = UILongPressGestureRecognizer(target: target, action: action) 71 | self.isUserInteractionEnabled = true 72 | self.addGestureRecognizer(longPress) 73 | } 74 | 75 | // @discardableResult 76 | // public func children(_ children: UIView...) -> UIView { 77 | // return self.children(children) 78 | // } 79 | // 80 | // @discardableResult 81 | // public func children(_ children: [UIView]) -> UIView { 82 | // children.forEach(addSubview) 83 | // return self 84 | // } 85 | 86 | @discardableResult 87 | public func addSubviews(_ subviews: UIView...) -> UIView{ 88 | subviews.forEach(addSubview) 89 | return self 90 | } 91 | 92 | @discardableResult 93 | public func addSubviews(_ subviews: [UIView]) -> UIView{ 94 | subviews.forEach (addSubview) 95 | return self 96 | } 97 | 98 | 99 | /// 给View加上圆角 100 | public func roundCorners(corners: UIRectCorner, radius: CGFloat) { 101 | let maskPath = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) 102 | let maskLayer = CAShapeLayer() 103 | maskLayer.frame = bounds 104 | maskLayer.path = maskPath.cgPath 105 | layer.mask = maskLayer 106 | } 107 | 108 | /// 删除所有View 109 | public func removeAllSubviews() { 110 | while subviews.count != 0 { 111 | subviews.last?.removeFromSuperview() 112 | } 113 | } 114 | 115 | public func responderViewController() -> UIViewController { 116 | var responder: UIResponder! 117 | var nextResponder = superview?.next 118 | repeat { 119 | responder = nextResponder 120 | nextResponder = nextResponder?.next 121 | 122 | } while !(responder.isKind(of: UIViewController.self)) 123 | return responder as! UIViewController 124 | } 125 | 126 | /** 127 | Shakes the view. Useful for displaying failures to users. 128 | */ 129 | public func shake() { 130 | self.transform = CGAffineTransform(translationX: 10, y: 0) 131 | 132 | UIView.animate(withDuration: 1, delay: 0, usingSpringWithDamping: 0.3, initialSpringVelocity: 50, options: [.curveEaseOut, .beginFromCurrentState, .allowUserInteraction], animations: { 133 | self.transform = .identity 134 | }, completion: nil) 135 | } 136 | 137 | /// Create a shake effect. 138 | /// 139 | /// - Parameters: 140 | /// - count: Shakes count. Default is 2. 141 | /// - duration: Shake duration. Default is 0.15. 142 | /// - translation: Shake translation. Default is 5. 143 | public func shake(count: Float = 2, duration: TimeInterval = 0.15, translation: Float = 5) { 144 | let animation: CABasicAnimation = CABasicAnimation(keyPath: "transform.translation.x") 145 | animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) 146 | animation.repeatCount = count 147 | animation.duration = (duration) / TimeInterval(animation.repeatCount) 148 | animation.autoreverses = true 149 | animation.byValue = translation 150 | 151 | self.layer.add(animation, forKey: "shake") 152 | } 153 | 154 | /** 155 | 使用视图的alpha创建一个淡出动画 156 | - parameter duration: 157 | - parameter delay: 158 | - parameter completion: 159 | */ 160 | public func fadeOut(_ duration: TimeInterval = 0.4, delay: TimeInterval = 0.0, completion: ((Bool) -> Void)? = nil) { 161 | UIView.animate(withDuration: duration, delay: delay, options: UIViewAnimationOptions.curveEaseIn, animations: { 162 | self.alpha = 0.0 163 | }, completion: completion) 164 | } 165 | 166 | /** 167 | 使用视图的alpha创建一个淡入动画 168 | - parameter duration: 169 | - parameter delay: 170 | - parameter completion: 171 | */ 172 | public func fadeIn(_ duration: TimeInterval = 0.4, delay: TimeInterval = 0.0, completion: ((Bool) -> Void)? = nil) 173 | { 174 | UIView.animate(withDuration: duration, delay: delay, options: UIViewAnimationOptions.curveEaseIn, animations: { 175 | self.alpha = 1.0 176 | }, completion: completion) 177 | } 178 | 179 | /** 180 | Disturbs the view. Useful for getting the user's attention when something changed. 181 | */ 182 | public func disturb() { 183 | self.transform = CGAffineTransform(scaleX: 0.98, y: 0.98) 184 | 185 | UIView.animate(withDuration: 1, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 150, options: [.curveEaseOut, .beginFromCurrentState, .allowUserInteraction], animations: { 186 | self.transform = .identity 187 | }, completion: nil) 188 | } 189 | 190 | public func addShadow(with color: UIColor) { 191 | layer.shadowColor = color.cgColor 192 | layer.shadowRadius = 8 193 | layer.shadowOpacity = 0.7 194 | layer.shadowOffset = CGSize(width: 0, height: 5) 195 | } 196 | 197 | public func removeShadow() { 198 | layer.shadowOpacity = 0 199 | } 200 | 201 | /// Removes specified set of constraints from the views in the receiver's subtree and from the receiver itself. 202 | /// 203 | /// - parameter constraints: A set of constraints that need to be removed. 204 | public func removeConstraintsFromSubtree(_ constraints: Set) { 205 | var constraintsToRemove = [NSLayoutConstraint]() 206 | 207 | for constraint in self.constraints { 208 | if constraints.contains(constraint) { 209 | constraintsToRemove.append(constraint) 210 | } 211 | } 212 | 213 | self.removeConstraints(constraintsToRemove) 214 | 215 | for view in self.subviews { 216 | view.removeConstraintsFromSubtree(constraints) 217 | } 218 | } 219 | 220 | /// Helper method to instantiate a nib file. 221 | /// 222 | /// - Parameter bundle: The bundle where the nib is located, by default we'll use the main bundle. 223 | /// - Returns: Returns an instance of the nib as a UIView. 224 | public class func instanceFromNib(bundle: Bundle = .main) -> T { 225 | return UINib(nibName: String(describing: T.self), bundle: bundle).instantiate(withOwner: nil, options: nil)[0] as! T 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /SweetKit/SweetKit/Extensions/UIKit/UIView+Positioning.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public extension UIView { 4 | // MARK: - Basic Properties 5 | 6 | /// X Axis value of UIView. 7 | var x: CGFloat { 8 | set { self.frame = CGRect(x: _pixelIntegral(newValue), 9 | y: self.y, 10 | width: self.width, 11 | height: self.height) 12 | } 13 | get { return self.frame.origin.x } 14 | } 15 | 16 | /// Y Axis value of UIView. 17 | var y: CGFloat { 18 | set { self.frame = CGRect(x: self.x, 19 | y: _pixelIntegral(newValue), 20 | width: self.width, 21 | height: self.height) 22 | } 23 | get { return self.frame.origin.y } 24 | } 25 | 26 | /// Width of view. 27 | var width: CGFloat { 28 | set { self.frame = CGRect(x: self.x, 29 | y: self.y, 30 | width: _pixelIntegral(newValue), 31 | height: self.height) 32 | } 33 | get { return self.frame.size.width } 34 | } 35 | 36 | /// Height of view. 37 | var height: CGFloat { 38 | set { self.frame = CGRect(x: self.x, 39 | y: self.y, 40 | width: self.width, 41 | height: _pixelIntegral(newValue)) 42 | } 43 | get { return self.frame.size.height } 44 | } 45 | 46 | // MARK: - Origin and Size 47 | 48 | /// View's Origin point. 49 | var origin: CGPoint { 50 | set { self.frame = CGRect(x: _pixelIntegral(newValue.x), 51 | y: _pixelIntegral(newValue.y), 52 | width: self.width, 53 | height: self.height) 54 | } 55 | get { return self.frame.origin } 56 | } 57 | 58 | /// View's size. 59 | var size: CGSize { 60 | set { self.frame = CGRect(x: self.x, 61 | y: self.y, 62 | width: _pixelIntegral(newValue.width), 63 | height: _pixelIntegral(newValue.height)) 64 | } 65 | get { return self.frame.size } 66 | } 67 | 68 | // MARK: - Extra Properties 69 | 70 | /// View's right side (x + width). 71 | var right: CGFloat { 72 | set { self.x = newValue - self.width } 73 | get { return self.x + self.width } 74 | } 75 | 76 | /// View's bottom (y + height). 77 | var bottom: CGFloat { 78 | set { self.y = newValue - self.height } 79 | get { return self.y + self.height } 80 | } 81 | 82 | /// View's top (y). 83 | var top: CGFloat { 84 | set { self.y = newValue } 85 | get { return self.y } 86 | } 87 | 88 | /// View's left side (x). 89 | var left: CGFloat { 90 | set { self.x = newValue } 91 | get { return self.x } 92 | } 93 | 94 | /// View's center X value (center.x). 95 | var centerX: CGFloat { 96 | set { self.center = CGPoint(x: newValue, y: self.centerY) } 97 | get { return self.center.x } 98 | } 99 | 100 | /// View's center Y value (center.y). 101 | var centerY: CGFloat { 102 | set { self.center = CGPoint(x: self.centerX, y: newValue) } 103 | get { return self.center.y } 104 | } 105 | 106 | /// Last subview on X Axis. 107 | var lastSubviewOnX: UIView? { 108 | return self.subviews.reduce(UIView(frame: .zero)) { 109 | return $1.x > $0.x ? $1 : $0 110 | } 111 | } 112 | 113 | /// Last subview on Y Axis. 114 | var lastSubviewOnY: UIView? { 115 | return self.subviews.reduce(UIView(frame: .zero)) { 116 | return $1.y > $0.y ? $1 : $0 117 | } 118 | } 119 | 120 | // MARK: - Bounds Methods 121 | 122 | /// X value of bounds (bounds.origin.x). 123 | var boundsX: CGFloat { 124 | set { self.bounds = CGRect(x: _pixelIntegral(newValue), 125 | y: self.boundsY, 126 | width: self.boundsWidth, 127 | height: self.boundsHeight) 128 | } 129 | get { return self.bounds.origin.x } 130 | } 131 | 132 | /// Y value of bounds (bounds.origin.y). 133 | var boundsY: CGFloat { 134 | set { self.frame = CGRect(x: self.boundsX, 135 | y: _pixelIntegral(newValue), 136 | width: self.boundsWidth, 137 | height: self.boundsHeight) 138 | } 139 | get { return self.bounds.origin.y } 140 | } 141 | 142 | /// Width of bounds (bounds.size.width). 143 | var boundsWidth: CGFloat { 144 | set { self.frame = CGRect(x: self.boundsX, 145 | y: self.boundsY, 146 | width: _pixelIntegral(newValue), 147 | height: self.boundsHeight) 148 | } 149 | get { return self.bounds.size.width } 150 | } 151 | 152 | /// Height of bounds (bounds.size.height). 153 | var boundsHeight: CGFloat { 154 | set { self.frame = CGRect(x: self.boundsX, 155 | y: self.boundsY, 156 | width: self.boundsWidth, 157 | height: _pixelIntegral(newValue)) 158 | } 159 | get { return self.bounds.size.height } 160 | } 161 | 162 | // MARK: - Useful Methods 163 | 164 | /// Center view to it's parent view. 165 | func centerToParent() { 166 | guard let superview = self.superview else { return } 167 | 168 | switch UIApplication.shared.statusBarOrientation { 169 | case .landscapeLeft, .landscapeRight: 170 | self.origin = CGPoint(x: (superview.height / 2) - (self.width / 2), 171 | y: (superview.width / 2) - (self.height / 2)) 172 | case .portrait, .portraitUpsideDown: 173 | self.origin = CGPoint(x: (superview.width / 2) - (self.width / 2), 174 | y: (superview.height / 2) - (self.height / 2)) 175 | case .unknown: 176 | return 177 | } 178 | } 179 | 180 | // MARK: - Private Methods 181 | fileprivate func _pixelIntegral(_ pointValue: CGFloat) -> CGFloat { 182 | let scale = UIScreen.main.scale 183 | return (round(pointValue * scale) / scale) 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /SweetKit/SweetKit/Extensions/UIKit/UIViewController+Extension.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public extension UIViewController { 4 | 5 | public var tabBarHeight: CGFloat { 6 | if let me = self as? UINavigationController, let visibleViewController = me.visibleViewController { 7 | return visibleViewController.tabBarHeight 8 | } 9 | if let tab = self.tabBarController { 10 | return tab.tabBar.frame.size.height 11 | } 12 | return 0 13 | } 14 | 15 | public var navigationBarHeight: CGFloat { 16 | if let me = self as? UINavigationController, let visibleViewController = me.visibleViewController { 17 | return visibleViewController.navigationBarHeight 18 | } 19 | if let nav = self.navigationController { 20 | return nav.navigationBar.height 21 | } 22 | return 0 23 | } 24 | 25 | public var isModal: Bool { 26 | if self.presentingViewController != nil { 27 | return true 28 | } else if self.navigationController?.presentingViewController?.presentedViewController == self.navigationController { 29 | return true 30 | } else if self.tabBarController?.presentingViewController is UITabBarController { 31 | return true 32 | } 33 | return false 34 | } 35 | 36 | /// 查找指定类型的子控制器 37 | public func findChildViewControllerOfType(_ klass: AnyClass) -> UIViewController? { 38 | for child in childViewControllers { 39 | if child.isKind(of: klass) { 40 | return child 41 | } 42 | } 43 | return nil 44 | } 45 | 46 | // Touch View Hidden Keyboard 47 | public func hideKeyboardWhenTappedAround() { 48 | let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard)) 49 | view.addGestureRecognizer(tap) 50 | } 51 | 52 | public func dismissKeyboard() { 53 | view.endEditing(true) 54 | } 55 | 56 | /// SO: http://stackoverflow.com/questions/24825123/get-the-current-view-controller-from-the-app-delegate 57 | public func currentViewController() -> UIViewController { 58 | func findBestViewController(_ controller: UIViewController?) -> UIViewController? { 59 | if let presented = controller?.presentedViewController { // Presented界面 60 | return findBestViewController(presented) 61 | } else { 62 | switch controller { 63 | case is UISplitViewController: // Return right hand side 64 | let split = controller as? UISplitViewController 65 | guard split?.viewControllers.isEmpty ?? true else { 66 | return findBestViewController(split?.viewControllers.last) 67 | } 68 | case is UINavigationController: // Return top view 69 | let navigation = controller as? UINavigationController 70 | guard navigation?.viewControllers.isEmpty ?? true else { 71 | return findBestViewController(navigation?.topViewController) 72 | } 73 | case is UITabBarController: // Return visible view 74 | let tab = controller as? UITabBarController 75 | guard tab?.viewControllers?.isEmpty ?? true else { 76 | return findBestViewController(tab?.selectedViewController) 77 | } 78 | default: break 79 | } 80 | } 81 | return controller 82 | } 83 | return findBestViewController(UIApplication.shared.keyWindow?.rootViewController)! // 假定永远有 84 | } 85 | 86 | public func push(controller: UIViewController, animated: Bool = true) { 87 | navigationController?.push(controller: controller, animated: animated) 88 | } 89 | 90 | @discardableResult 91 | public func pop(animated: Bool = true) -> UIViewController? { 92 | return navigationController?.popViewController(animated: animated) 93 | } 94 | 95 | public func popRoot(animated: Bool = true) { 96 | navigationController?.popToRootViewController(animated: animated) 97 | } 98 | 99 | 100 | } 101 | -------------------------------------------------------------------------------- /SweetKit/SweetKit/Helper/GCDHelper.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct GCDHelper { 4 | 5 | public static let mainQueue: DispatchQueue = { 6 | return DispatchQueue.main 7 | }() 8 | 9 | public static let backgroundQueue: DispatchQueue = { 10 | return DispatchQueue.global(qos: DispatchQoS.QoSClass.background) 11 | }() 12 | 13 | public static func delay(_ delay: Double, block: @escaping () -> Void) { 14 | GCDHelper.mainQueue.asyncAfter( 15 | deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: block) 16 | } 17 | 18 | public static func runOnMainThread(_ block: @escaping () -> Void) { 19 | GCDHelper.mainQueue.async(execute: block) 20 | } 21 | 22 | public static func runOnBackgroundThread(_ block: @escaping () -> Void) { 23 | GCDHelper.backgroundQueue.async(execute: block) 24 | } 25 | 26 | public static func synced(_ lock: AnyObject, closure: () -> Void) { 27 | objc_sync_enter(lock) 28 | closure() 29 | objc_sync_exit(lock) 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /SweetKit/SweetKit/Helper/LocalNotificationHelper.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import AVKit 3 | 4 | class LocalNotificationHelper: NSObject { 5 | 6 | let LOCAL_NOTIFICATION_CATEGORY : String = "LocalNotificationCategory" 7 | 8 | // MARK: - Shared Instance 9 | 10 | class var shared: LocalNotificationHelper { 11 | struct Singleton { 12 | static var sharedInstance = LocalNotificationHelper() 13 | } 14 | return Singleton.sharedInstance 15 | } 16 | 17 | // MARK: - Schedule Notification 18 | 19 | func scheduleNotificationWithKey(key: String, title: String, message: String, seconds: Double, userInfo: [String: Any]?) { 20 | let date = Date(timeIntervalSinceNow: TimeInterval(seconds)) 21 | let notification = notificationWithTitle(key: key, title: title, message: message, date: date, userInfo: userInfo, soundName: nil, hasAction: true) 22 | notification.category = LOCAL_NOTIFICATION_CATEGORY 23 | UIApplication.shared.scheduleLocalNotification(notification) 24 | } 25 | 26 | func scheduleNotificationWithKey(key: String, title: String, message: String, date: Date, userInfo: [String: Any]?){ 27 | let notification = notificationWithTitle(key: key, title: title, message: message, date: date, userInfo: ["key": key], soundName: nil, hasAction: true) 28 | notification.category = LOCAL_NOTIFICATION_CATEGORY 29 | UIApplication.shared.scheduleLocalNotification(notification) 30 | } 31 | 32 | func scheduleNotificationWithKey(key: String, title: String, message: String, seconds: Double, soundName: String, userInfo: [NSObject: AnyObject]?){ 33 | let date = Date(timeIntervalSinceNow: TimeInterval(seconds)) 34 | let notification = notificationWithTitle(key: key, title: title, message: message, date: date, userInfo: ["key": key], soundName: soundName, hasAction: true) 35 | UIApplication.shared.scheduleLocalNotification(notification) 36 | } 37 | 38 | func scheduleNotificationWithKey(key: String, title: String, message: String, date: Date, soundName: String, userInfo: [String: Any]?){ 39 | let notification = notificationWithTitle(key: key, title: title, message: message, date: date, userInfo: ["key": key], soundName: soundName, hasAction: true) 40 | UIApplication.shared.scheduleLocalNotification(notification) 41 | } 42 | 43 | // MARK: - Present Notification 44 | 45 | func presentNotificationWithKey(key: String, title: String, message: String, soundName: String, userInfo: [String: Any]?) { 46 | let notification = notificationWithTitle(key: key, title: title, message: message, date: nil, userInfo: ["key": key], soundName: nil, hasAction: true) 47 | UIApplication.shared.presentLocalNotificationNow(notification) 48 | } 49 | 50 | /// 添加一个本地通知 51 | /// 52 | /// - Parameters: 53 | /// - key: key 54 | /// - title: 通知上显示的主题内容 55 | /// - message: 待机界面的滑动动作提示 56 | /// - date: 通知的触发时间,例如即刻起15分钟后 57 | /// - userInfo: 通知上绑定的其他信息,为键值对 58 | /// - soundName: 通知的声音 59 | /// - hasAction: 启动滑块 60 | /// - Returns: UILocalNotification 61 | func notificationWithTitle(key : String, title: String, message: String, date: Date?, userInfo: [String: Any]?, soundName: String?, hasAction: Bool) -> UILocalNotification { 62 | 63 | var dct = userInfo 64 | dct?["key"] = String(stringLiteral: key) as Any? 65 | 66 | let notification = UILocalNotification() 67 | notification.alertAction = title 68 | notification.alertBody = message 69 | notification.userInfo = dct 70 | notification.soundName = soundName ?? UILocalNotificationDefaultSoundName 71 | notification.fireDate = date as Date? 72 | notification.hasAction = hasAction 73 | return notification 74 | } 75 | 76 | func getNotificationWithKey(key : String) -> UILocalNotification { 77 | 78 | var notif : UILocalNotification? 79 | 80 | for notification in UIApplication.shared.scheduledLocalNotifications! where notification.userInfo!["key"] as! String == key{ 81 | notif = notification 82 | break 83 | } 84 | 85 | return notif! 86 | } 87 | 88 | func cancelNotification(key : String){ 89 | 90 | for notification in UIApplication.shared.scheduledLocalNotifications! where notification.userInfo!["key"] as! String == key{ 91 | UIApplication.shared.cancelLocalNotification(notification) 92 | break 93 | } 94 | } 95 | 96 | func getAllNotifications() -> [UILocalNotification]? { 97 | return UIApplication.shared.scheduledLocalNotifications 98 | } 99 | 100 | func cancelAllNotifications() { 101 | UIApplication.shared.cancelAllLocalNotifications() 102 | } 103 | 104 | func registerUserNotificationWithActionButtons(actions : [UIUserNotificationAction]){ 105 | 106 | let category = UIMutableUserNotificationCategory() 107 | category.identifier = LOCAL_NOTIFICATION_CATEGORY 108 | 109 | category.setActions(actions, for: UIUserNotificationActionContext.default) 110 | 111 | let settings = UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: NSSet(object: category) as? Set) 112 | UIApplication.shared.registerUserNotificationSettings(settings) 113 | } 114 | 115 | func registerUserNotification(){ 116 | 117 | let settings = UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil) 118 | UIApplication.shared.registerUserNotificationSettings(settings) 119 | } 120 | 121 | func createUserNotificationActionButton(identifier : String, title : String) -> UIUserNotificationAction{ 122 | 123 | let actionButton = UIMutableUserNotificationAction() 124 | actionButton.identifier = identifier 125 | actionButton.title = title 126 | actionButton.activationMode = UIUserNotificationActivationMode.background 127 | actionButton.isAuthenticationRequired = true 128 | actionButton.isDestructive = false 129 | 130 | return actionButton 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /SweetKit/SweetKit/Helper/SKLog.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // MARK: - Global functions 4 | 5 | public struct SKLog { 6 | // MARK: - Variables 7 | 8 | /// Activate or not SKLog. 9 | public static var active: Bool = isDebug 10 | 11 | /// The log string. 12 | public static var logged: String = "" 13 | /// The detailed log string. 14 | public static var detailedLog: String = "" 15 | 16 | private enum LogType { 17 | case warning, error, debug, info 18 | 19 | var level: String { 20 | switch self { 21 | case .error: return "❌ ERROR" 22 | case .warning: return "⚠️ WARNING" 23 | case .info: return "💙 INFO" 24 | case .debug: return "💚 DEBUG" 25 | } 26 | } 27 | } 28 | 29 | // MARK: - Functions 30 | 31 | private static func log(_ items: [Any], filename: String = #file, function: StaticString = #function, line: Int = #line, type: LogType) { 32 | if self.active { 33 | var _message = type.level + " " + message(from: items) 34 | if _message.hasSuffix("\n") == false { 35 | _message += "\n" 36 | } 37 | 38 | self.logged += _message 39 | 40 | let filenameWithoutExtension = filename.lastPathComponent.deletingPathExtension 41 | let timestamp = Date().description(dateSeparator: "-", usFormat: true, nanosecond: true) 42 | let logMessage = "\(timestamp) \(filenameWithoutExtension):\(line) \(function): \(_message)" 43 | print(logMessage, terminator: "") 44 | 45 | self.detailedLog += logMessage 46 | } 47 | } 48 | 49 | public static func warning(_ items: Any..., filename: String = #file, function: StaticString = #function, line: Int = #line) { 50 | self.log(items, filename: filename, function: function, line: line, type: .warning) 51 | } 52 | 53 | public static func error(_ items: Any..., filename: String = #file, function: StaticString = #function, line: Int = #line) { 54 | self.log(items, filename: filename, function: function, line: line, type: .error) 55 | } 56 | 57 | public static func debug(_ items: Any..., filename: String = #file, function: StaticString = #function, line: Int = #line) { 58 | self.log(items, filename: filename, function: function, line: line, type: .debug) 59 | } 60 | 61 | public static func info(_ items: Any..., filename: String = #file, function: StaticString = #function, line: Int = #line) { 62 | self.log(items, filename: filename, function: function, line: line, type: .info) 63 | } 64 | 65 | private static func message(from items: [Any]) -> String { 66 | return items 67 | .map { String(describing: $0) } 68 | .joined(separator: " ") 69 | } 70 | 71 | /// Clear the log string. 72 | public static func clear() { 73 | logged = "" 74 | detailedLog = "" 75 | } 76 | 77 | /// Save the Log in a file. 78 | /// 79 | /// - Parameters: 80 | /// - path: Save path. 81 | /// - filename: Log filename. 82 | public static func saveLog(in path: String = FileManager.log, 83 | filename: String = Date().YYYYMMDDDateString.appendingPathExtension("log")!) { 84 | if detailedLog.isEmpty { return } 85 | let fullPath = path.appendingPathComponent(filename) 86 | var logs = detailedLog 87 | if FileManager.default.fileExists(atPath: fullPath) { 88 | logs = try! String(contentsOfFile: fullPath, encoding: .utf8) 89 | logs = logs + detailedLog 90 | _ = FileManager.save(content: logs, savePath: path.appendingPathComponent(filename)) 91 | return 92 | } 93 | FileManager.create(at: fullPath) 94 | _ = FileManager.save(content: logs, savePath: fullPath) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /SweetKit/SweetKit/Helper/SKSystemSound.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import AudioToolbox 3 | 4 | /// SweetKitError errors enum. 5 | /// 6 | /// - jsonSerialization: JSONSerialization error. 7 | /// - errorLoadingSound: Could not load sound error. 8 | /// - pathNotExist: Path not exist error. 9 | /// - pathNotAllowed: Path not allowed error. 10 | public enum SKError: Error { 11 | case jsonSerialization 12 | case errorLoadingSound 13 | case pathNotExist 14 | case pathNotAllowed 15 | } 16 | 17 | // MARK: - SweetSystemSound struct 18 | 19 | /// This struct adds some useful functions to play system sounds. 20 | public class SKSystemSound { 21 | // MARK: - Variables 22 | 23 | /// Audio IDs enum. 24 | /// 25 | /// More info [here](http://iphonedevwiki.net/index.php/AudioServices). 26 | /// 27 | /// - NewMail: New Mail. 28 | /// - MailSent: Mail Sent. 29 | /// - VoiceMail: Voice Mail. 30 | /// - RecivedMessage: Recived Message. 31 | /// - SentMessage: Sent Message. 32 | /// - Alarm: Alarm. 33 | /// - LowPower: Low Power. 34 | /// - SMSReceived1: SMS Received 1. 35 | /// - SMSReceived2: SMS Received 2. 36 | /// - SMSReceived3: SMS Received 3. 37 | /// - SMSReceived4: SMS Received 4. 38 | /// - SMSReceived5: SMS Received 5. 39 | /// - SMSReceived6: SMS Received 6. 40 | /// - TweetSent: Tweet Sent. 41 | /// - Anticipate: Anticipate. 42 | /// - Bloom: Bloom. 43 | /// - Calypso: Calypso. 44 | /// - ChooChoo: Choo Choo. 45 | /// - Descent: Descent. 46 | /// - Fanfare: Fanfare. 47 | /// - Ladder: Ladder. 48 | /// - Minuet: Minuet. 49 | /// - NewsFlash: News Flash. 50 | /// - Noir: Noir. 51 | /// - SherwoodForest: Sherwood Forest. 52 | /// - Spell: Spell. 53 | /// - Suspence: Suspence. 54 | /// - Telegraph: Telegraph. 55 | /// - Tiptoes: Tiptoes. 56 | /// - Typewriters: Typewriters. 57 | /// - Update: Update. 58 | /// - USSDAlert: USSD Alert. 59 | /// - SIMToolkitCallDropped: SIM Toolkit Call Dropped. 60 | /// - SIMToolkitGeneralBeep: SIM Toolkit General Beep. 61 | /// - SIMToolkitNegativeACK: SIM Toolkit Negative ACK. 62 | /// - SIMToolkitPositiveACK: SIM Toolkit Positive ACK. 63 | /// - SIMToolkitSMS: SIM Toolkit SMS. 64 | /// - Tink: Tink. 65 | /// - CTBusy: CT Busy. 66 | /// - CTCongestion: CT Congestion. 67 | /// - CTPathACK: CT Path ACK. 68 | /// - CTError: CT Error. 69 | /// - CTCallWaiting: CT Call Waiting. 70 | /// - CTKeytone: CT Keytone. 71 | /// - Lock: Lock. 72 | /// - Unlock: Unlock. 73 | /// - FailedUnlock: Failed Unlock. 74 | /// - KeypressedTink: Keypressed Tink. 75 | /// - KeypressedTock: Keypressed Tock. 76 | /// - Tock: Tock. 77 | /// - BeepBeep: Beep Beep. 78 | /// - RingerCharged: Ringer Charged. 79 | /// - PhotoShutter: Photo Shutter. 80 | /// - Shake: Shake. 81 | /// - JBLBegin: JBL Begin. 82 | /// - JBLConfirm: JBL Confirm. 83 | /// - JBLCancel: JBL Cancel. 84 | /// - BeginRecording: Begin Recording. 85 | /// - EndRecording: End Recording. 86 | /// - JBLAmbiguous: JBL Ambiguous. 87 | /// - JBLNoMatch: JBL No Match. 88 | /// - BeginVideoRecord: Begin Video Record. 89 | /// - EndVideoRecord: End Video Record. 90 | /// - VCInvitationAccepted: VC Invitation Accepted. 91 | /// - VCRinging: VC Ringing. 92 | /// - VCEnded: VC Ended. 93 | /// - VCCallWaiting: VC Call Waiting. 94 | /// - VCCallUpgrade: VC Call Upgrade. 95 | /// - TouchTone1: Touch Tone 1. 96 | /// - TouchTone2: Touch Tone 2. 97 | /// - TouchTone3: Touch Tone 3. 98 | /// - TouchTone4: Touch Tone 4. 99 | /// - TouchTone5: Touch Tone 5. 100 | /// - TouchTone6: Touch Tone 6. 101 | /// - TouchTone7: Touch Tone 7. 102 | /// - TouchTone8: Touch Tone 8. 103 | /// - TouchTone9: Touch Tone 9. 104 | /// - TouchTone10: Touch Tone 10. 105 | /// - TouchToneStar: Touch Tone Star. 106 | /// - TouchTonePound: Touch Tone Pound. 107 | /// - HeadsetStartCall: Headset Start Call. 108 | /// - HeadsetRedial: Headset Redial. 109 | /// - HeadsetAnswerCall: Headset Answer Call. 110 | /// - HeadsetEndCall: Headset End Call. 111 | /// - HeadsetCallWaitingActions: Headset Call Waiting Actions. 112 | /// - HeadsetTransitionEnd: Headset Transition End. 113 | /// - Voicemail: Voicemail. 114 | /// - ReceivedMessage: Received Message. 115 | /// - NewMail2: New Mail 2. 116 | /// - MailSent2: Mail Sent 2. 117 | /// - Alarm2: Alarm 2. 118 | /// - Lock2: Lock 2. 119 | /// - Tock2: Tock 2. 120 | /// - SMSReceived1_2: SMS Received 1_2. 121 | /// - SMSReceived2_2: SMS Received 2_2. 122 | /// - SMSReceived3_2: SMS Received 3_2. 123 | /// - SMSReceived4_2: SMS Received 4_2. 124 | /// - SMSReceivedVibrate: SMS Received Vibrate. 125 | /// - SMSReceived1_3: SMS Received 1_3. 126 | /// - SMSReceived5_3: SMS Received 5_3. 127 | /// - SMSReceived6_3: SMS Received 6_3. 128 | /// - Voicemail2: Voicemail 2. 129 | /// - Anticipate2: Anticipate 2. 130 | /// - Bloom2: Bloom 2. 131 | /// - Calypso2: Calypso 2. 132 | /// - ChooChoo2: Choo Choo 2. 133 | /// - Descent2: Descent 2. 134 | /// - Fanfare2: Fanfare 2. 135 | /// - Ladder2: Ladder 2. 136 | /// - Minuet2: Minuet 2. 137 | /// - NewsFlash2: News Flash 2. 138 | /// - Noir2: Noir 2. 139 | /// - SherwoodForest2: Sherwood Forest 2. 140 | /// - Spell2: Spell 2. 141 | /// - Suspence2: Suspence 2. 142 | /// - Telegraph2: Telegraph 2. 143 | /// - Tiptoes2: Tiptoes 2. 144 | /// - Typewriters2: Typewriters 2. 145 | /// - Update2: Update 2. 146 | /// - RingerVibeChanged: Ringer Vibe Changed. 147 | /// - SilentVibeChanged: Silent Vibe Changed. 148 | /// - Vibrate: Vibrate. 149 | public enum AudioID: UInt32 { 150 | case newMail = 1000 151 | case mailSent = 1001 152 | case voiceMail = 1002 153 | case recivedMessage = 1003 154 | case sentMessage = 1004 155 | case alarm = 1005 156 | case lowPower = 1006 157 | case smsReceived1 = 1007 158 | case smsReceived2 = 1008 159 | case smsReceived3 = 1009 160 | case smsReceived4 = 1010 161 | case smsReceived5 = 1013 162 | case smsReceived6 = 1014 163 | case tweetSent = 1016 164 | case anticipate = 1020 165 | case bloom = 1021 166 | case calypso = 1022 167 | case chooChoo = 1023 168 | case descent = 1024 169 | case fanfare = 1025 170 | case ladder = 1026 171 | case minuet = 1027 172 | case newsFlash = 1028 173 | case noir = 1029 174 | case sherwoodForest = 1030 175 | case spell = 1031 176 | case suspence = 1032 177 | case telegraph = 1033 178 | case tiptoes = 1034 179 | case typewriters = 1035 180 | case update = 1036 181 | case ussdAlert = 1050 182 | case simToolkitCallDropped = 1051 183 | case simToolkitGeneralBeep = 1052 184 | case simToolkitNegativeACK = 1053 185 | case simToolkitPositiveACK = 1054 186 | case simToolkitSMS = 1055 187 | case tink = 1057 188 | case ctBusy = 1070 189 | case ctCongestion = 1071 190 | case ctPathACK = 1072 191 | case ctError = 1073 192 | case ctCallWaiting = 1074 193 | case ctKeytone = 1075 194 | case lock = 1100 195 | case unlock = 1101 196 | case failedUnlock = 1102 197 | case keypressedTink = 1103 198 | case keypressedTock = 1104 199 | case tock = 1105 200 | case beepBeep = 1106 201 | case ringerCharged = 1107 202 | case photoShutter = 1108 203 | case shake = 1109 204 | case jblBegin = 1110 205 | case jblConfirm = 1111 206 | case jblCancel = 1112 207 | case beginRecording = 1113 208 | case endRecording = 1114 209 | case jblAmbiguous = 1115 210 | case jblNoMatch = 1116 211 | case beginVideoRecord = 1117 212 | case endVideoRecord = 1118 213 | case vcInvitationAccepted = 1150 214 | case vcRinging = 1151 215 | case vcEnded = 1152 216 | case vcCallWaiting = 1153 217 | case vcCallUpgrade = 1154 218 | case touchTone1 = 1200 219 | case touchTone2 = 1201 220 | case touchTone3 = 1202 221 | case touchTone4 = 1203 222 | case touchTone5 = 1204 223 | case touchTone6 = 1205 224 | case touchTone7 = 1206 225 | case touchTone8 = 1207 226 | case touchTone9 = 1208 227 | case touchTone10 = 1209 228 | case touchToneStar = 1210 229 | case touchTonePound = 1211 230 | case headsetStartCall = 1254 231 | case headsetRedial = 1255 232 | case headsetAnswerCall = 1256 233 | case headsetEndCall = 1257 234 | case headsetCallWaitingActions = 1258 235 | case headsetTransitionEnd = 1259 236 | case voicemail = 1300 237 | case receivedMessage = 1301 238 | case newMail2 = 1302 239 | case mailSent2 = 1303 240 | case alarm2 = 1304 241 | case lock2 = 1305 242 | case tock2 = 1306 243 | case smsReceived12 = 1307 244 | case smsReceived22 = 1308 245 | case smsReceived32 = 1309 246 | case smsReceived42 = 1310 247 | case smsReceivedVibrate = 1311 248 | case smsReceived13 = 1312 249 | case smsReceived53 = 1313 250 | case smsReceived63 = 1314 251 | case voicemail2 = 1315 252 | case anticipate2 = 1320 253 | case bloom2 = 1321 254 | case calypso2 = 1322 255 | case chooChoo2 = 1323 256 | case descent2 = 1324 257 | case fanfare2 = 1325 258 | case ladder2 = 1326 259 | case minuet2 = 1327 260 | case newsFlash2 = 1328 261 | case noir2 = 1329 262 | case sherwoodForest2 = 1330 263 | case spell2 = 1331 264 | case suspence2 = 1332 265 | case telegraph2 = 1333 266 | case tiptoes2 = 1334 267 | case typewriters2 = 1335 268 | case update2 = 1336 269 | case ringerVibeChanged = 1350 270 | case silentVibeChanged = 1351 271 | case vibrate = 4095 272 | } 273 | 274 | // MARK: - Functions 275 | 276 | /// Play a system sound from the ID. 277 | /// 278 | /// - Parameter audioID: ID of system audio from the AudioID enum. 279 | public static func playSystemSound(audioID: AudioID) { 280 | AudioServicesPlaySystemSound(SystemSoundID(audioID.rawValue)) 281 | } 282 | 283 | /// Play system sound vibrate. 284 | public static func vibrate() { 285 | AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate)) 286 | } 287 | 288 | /// Play custom sound with url. 289 | /// 290 | /// - Parameter soundURL: Sound URL. 291 | /// - Returns: Returns the SystemSoundID. 292 | /// - Throws: Throws BFKitError.errorLoadingSound error. 293 | public static func playSound(soundURL: URL) throws -> SystemSoundID { 294 | var soundID: SystemSoundID = 0 295 | 296 | let error: OSStatus = AudioServicesCreateSystemSoundID(soundURL as CFURL, &soundID) 297 | if error != Int32(kAudioServicesNoError) { 298 | throw SKError.errorLoadingSound 299 | } 300 | return soundID 301 | } 302 | 303 | /// Dispose custom sound. 304 | /// 305 | /// - Parameter soundID: SystemSoundID. 306 | /// - Returns: Returns true if has been disposed, otherwise false. 307 | public static func disposeSound(soundID: SystemSoundID) throws { 308 | let error: OSStatus = AudioServicesDisposeSystemSoundID(soundID) 309 | if error != Int32(kAudioServicesNoError) { 310 | throw SKError.errorLoadingSound 311 | } 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /SweetKit/SweetKit/Helper/SKTouchID.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import LocalAuthentication 3 | 4 | // MARK: - SKTouchID struct 5 | 6 | /// This struct adds some useful functions to use TouchID. 7 | public struct SKTouchID { 8 | // MARK: - Variables 9 | 10 | /// Touch result enum: 11 | /// 12 | /// - success: Success. 13 | /// - error: Error. 14 | /// - authenticationFailed: Authentication Failed. 15 | /// - userCancel: User Cancel. 16 | /// - userFallback: User Fallback. 17 | /// - systemCancel: System Cancel. 18 | /// - passcodeNotSet: Passcode Not Set. 19 | /// - notAvailable: Touch IDNot Available. 20 | /// - notEnrolled: Touch ID Not Enrolled. 21 | /// - lockout: Touch ID Lockout. 22 | /// - appCancel: App Cancel. 23 | /// - invalidContext: Invalid Context. 24 | public enum TouchIDResult: Int { 25 | case success 26 | case authenticationFailed 27 | case userCancel 28 | case userFallback 29 | case systemCancel 30 | case passcodeNotSet 31 | case notAvailable 32 | case notEnrolled 33 | case lockout 34 | case appCancel 35 | case invalidContext 36 | case error 37 | } 38 | 39 | // MARK: - Functions 40 | 41 | /// Shows the TouchID authentication. 42 | /// 43 | /// - Parameters: 44 | /// - reason: Text to show in the alert. 45 | /// - fallbackTitle: Default title "Enter Password" is used when this property is left nil. If set to empty string, the button will be hidden. 46 | /// - completion: Completion handler. 47 | /// - result: Returns the TouchID result, from the TouchIDResult enum. 48 | public static func showTouchID(reason: String, fallbackTitle: String? = nil, completion: @escaping (_ result: TouchIDResult) -> Void) { 49 | let context: LAContext = LAContext() 50 | 51 | context.localizedFallbackTitle = fallbackTitle 52 | 53 | var error: NSError? 54 | if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) { 55 | context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason, reply: { (success: Bool, error: Error?) -> Void in 56 | if success { 57 | completion(.success) 58 | } else { 59 | if #available(iOS 9.0, *) { 60 | switch error! { 61 | case LAError.authenticationFailed: 62 | completion(.authenticationFailed) 63 | case LAError.userCancel: 64 | completion(.userCancel) 65 | case LAError.userFallback: 66 | completion(.userFallback) 67 | case LAError.systemCancel: 68 | completion(.systemCancel) 69 | case LAError.touchIDLockout: 70 | completion(.lockout) 71 | case LAError.appCancel: 72 | completion(.appCancel) 73 | case LAError.invalidContext: 74 | completion(.invalidContext) 75 | default: 76 | completion(.error) 77 | } 78 | } else { 79 | switch error! { 80 | case LAError.authenticationFailed: 81 | completion(.authenticationFailed) 82 | case LAError.userCancel: 83 | completion(.userCancel) 84 | case LAError.userFallback: 85 | completion(.userFallback) 86 | case LAError.systemCancel: 87 | completion(.systemCancel) 88 | default: 89 | completion(.error) 90 | } 91 | } 92 | } 93 | }) 94 | } else { 95 | if #available(iOS 9.0, *) { 96 | switch error! { 97 | case LAError.passcodeNotSet: 98 | completion(.passcodeNotSet) 99 | case LAError.touchIDNotAvailable: 100 | completion(.notAvailable) 101 | case LAError.touchIDNotEnrolled: 102 | completion(.notEnrolled) 103 | case LAError.touchIDLockout: 104 | completion(.lockout) 105 | case LAError.appCancel: 106 | completion(.appCancel) 107 | case LAError.invalidContext: 108 | completion(.invalidContext) 109 | default: 110 | completion(.error) 111 | } 112 | } else { 113 | switch error! { 114 | case LAError.passcodeNotSet: 115 | completion(.passcodeNotSet) 116 | case LAError.touchIDNotAvailable: 117 | completion(.notAvailable) 118 | case LAError.touchIDNotEnrolled: 119 | completion(.notEnrolled) 120 | default: 121 | completion(.error) 122 | } 123 | } 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /SweetKit/SweetKit/Helper/SweetFunc.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // MARK: - Global functions 4 | 5 | /// 角度到弧度转换 6 | public func degreesToRadians(_ degrees: Float) -> Float { 7 | return Float(Double(degrees) * Double.pi / 180) 8 | } 9 | 10 | /// 弧度到角度转换 11 | public func radiansToDegrees(_ radians: Float) -> Float { 12 | return Float(Double(radians) * 180 / Double.pi) 13 | } 14 | 15 | /// 仅在调试模式下执行一个块 16 | /// 17 | /// (http://stackoverflow.com/questions/26890537/disabling-nslog-for-production-in-swift-project/26891797#26891797). 18 | 19 | public func debug(_ block: () -> Void) { 20 | #if DEBUG 21 | block() 22 | #endif 23 | } 24 | 25 | public func release(_ block: () -> Void) { 26 | #if !DEBUG 27 | block() 28 | #endif 29 | } 30 | 31 | public var isDebug: Bool { 32 | #if DEBUG 33 | return true 34 | #else 35 | return false 36 | #endif 37 | } 38 | -------------------------------------------------------------------------------- /SweetKit/SweetKit/Helper/SwiftTimer.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public class SwiftTimer { 4 | 5 | private let internalTimer: DispatchSourceTimer 6 | 7 | private var isRunning = false 8 | 9 | public let repeats: Bool 10 | 11 | public typealias SwiftTimerHandler = (SwiftTimer) -> Void 12 | 13 | private var handler: SwiftTimerHandler 14 | 15 | public init(interval: DispatchTimeInterval, repeats: Bool = false, queue: DispatchQueue = .main , handler: @escaping SwiftTimerHandler) { 16 | 17 | self.handler = handler 18 | self.repeats = repeats 19 | internalTimer = DispatchSource.makeTimerSource(queue: queue) 20 | internalTimer.setEventHandler { [weak self] in 21 | if let strongSelf = self { 22 | handler(strongSelf) 23 | } 24 | } 25 | 26 | if repeats { 27 | internalTimer.scheduleRepeating(deadline: .now() + interval, interval: interval) 28 | } else { 29 | internalTimer.scheduleOneshot(deadline: .now() + interval) 30 | } 31 | } 32 | 33 | public static func repeaticTimer(interval: DispatchTimeInterval, queue: DispatchQueue = .main , handler: @escaping SwiftTimerHandler ) -> SwiftTimer { 34 | return SwiftTimer(interval: interval, repeats: true, queue: queue, handler: handler) 35 | } 36 | 37 | deinit { 38 | if !self.isRunning { 39 | internalTimer.resume() 40 | } 41 | } 42 | 43 | //You can use this method to fire a repeating timer without interrupting its regular firing schedule. If the timer is non-repeating, it is automatically invalidated after firing, even if its scheduled fire date has not arrived. 44 | public func fire() { 45 | if repeats { 46 | handler(self) 47 | } else { 48 | handler(self) 49 | internalTimer.cancel() 50 | } 51 | } 52 | 53 | public func start() { 54 | if !isRunning { 55 | internalTimer.resume() 56 | isRunning = true 57 | } 58 | } 59 | 60 | public func suspend() { 61 | if isRunning { 62 | internalTimer.suspend() 63 | isRunning = false 64 | } 65 | } 66 | 67 | public func rescheduleRepeating(interval: DispatchTimeInterval) { 68 | if repeats { 69 | internalTimer.scheduleRepeating(deadline: .now() + interval, interval: interval) 70 | } 71 | } 72 | 73 | public func rescheduleHandler(handler: @escaping SwiftTimerHandler) { 74 | self.handler = handler 75 | internalTimer.setEventHandler { [weak self] in 76 | if let strongSelf = self { 77 | handler(strongSelf) 78 | } 79 | } 80 | 81 | } 82 | } 83 | 84 | //MARK: Throttle 85 | public extension SwiftTimer { 86 | 87 | private static var timers = [String:DispatchSourceTimer]() 88 | 89 | public static func throttle(interval: DispatchTimeInterval, identifier: String, queue: DispatchQueue = .main , handler: @escaping () -> Void ) { 90 | 91 | if let previousTimer = timers[identifier] { 92 | previousTimer.cancel() 93 | timers.removeValue(forKey: identifier) 94 | } 95 | 96 | let timer = DispatchSource.makeTimerSource(queue: queue) 97 | timers[identifier] = timer 98 | timer.scheduleOneshot(deadline: .now() + interval) 99 | timer.setEventHandler { 100 | handler() 101 | timer.cancel() 102 | timers.removeValue(forKey: identifier) 103 | } 104 | timer.resume() 105 | } 106 | 107 | public static func cancelThrottlingTimer(identifier: String) { 108 | if let previousTimer = timers[identifier] { 109 | previousTimer.cancel() 110 | timers.removeValue(forKey: identifier) 111 | } 112 | } 113 | 114 | 115 | 116 | } 117 | 118 | //MARK: Count Down 119 | public class SwiftCountDownTimer { 120 | 121 | private let internalTimer: SwiftTimer 122 | 123 | private var leftTimes: Int 124 | 125 | private let originalTimes: Int 126 | 127 | private let handler: (SwiftCountDownTimer, _ leftTimes: Int) -> Void 128 | 129 | public init(interval: DispatchTimeInterval, times: Int,queue: DispatchQueue = .main , handler: @escaping (SwiftCountDownTimer, _ leftTimes: Int) -> Void ) { 130 | 131 | self.leftTimes = times 132 | self.originalTimes = times 133 | self.handler = handler 134 | self.internalTimer = SwiftTimer.repeaticTimer(interval: interval, queue: queue, handler: { _ in 135 | }) 136 | self.internalTimer.rescheduleHandler { [weak self] swiftTimer in 137 | if let strongSelf = self { 138 | if strongSelf.leftTimes > 0 { 139 | strongSelf.leftTimes = strongSelf.leftTimes - 1 140 | strongSelf.handler(strongSelf, strongSelf.leftTimes) 141 | } else { 142 | strongSelf.internalTimer.suspend() 143 | } 144 | } 145 | } 146 | } 147 | 148 | public func start() { 149 | self.internalTimer.start() 150 | } 151 | 152 | public func suspend() { 153 | self.internalTimer.suspend() 154 | } 155 | 156 | public func reCountDown() { 157 | self.leftTimes = self.originalTimes 158 | } 159 | 160 | } 161 | 162 | public extension DispatchTimeInterval { 163 | 164 | public static func fromSeconds(_ seconds: Double) -> DispatchTimeInterval { 165 | return .milliseconds(Int(seconds * 1000)) 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /SweetKit/SweetKit/Views/UIInsetLabel.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | @IBDesignable public class UIInsetLabel: UILabel { 4 | 5 | public var contentInsets = UIEdgeInsets.zero 6 | 7 | @IBInspectable 8 | public var contentInsetsTop: CGFloat { 9 | set { 10 | contentInsets.top = newValue 11 | } 12 | get { 13 | return contentInsets.top 14 | } 15 | } 16 | 17 | @IBInspectable 18 | public var contentInsetsLeft: CGFloat { 19 | set { 20 | contentInsets.left = newValue 21 | } 22 | get { 23 | return contentInsets.left 24 | } 25 | } 26 | 27 | @IBInspectable 28 | public var contentInsetsBottom: CGFloat { 29 | set { 30 | contentInsets.bottom = newValue 31 | } 32 | get { 33 | return contentInsets.bottom 34 | } 35 | } 36 | 37 | @IBInspectable 38 | public var contentInsetsRight: CGFloat { 39 | set { 40 | contentInsets.right = newValue 41 | } 42 | get { 43 | return contentInsets.right 44 | } 45 | } 46 | 47 | convenience public init(insets: UIEdgeInsets) { 48 | self.init() 49 | self.contentInsets = insets; 50 | } 51 | 52 | convenience public init(frame: CGRect, insets: UIEdgeInsets) { 53 | self.init(frame:frame) 54 | self.contentInsets = insets 55 | } 56 | 57 | override public func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect { 58 | return super.textRect(forBounds: UIEdgeInsetsInsetRect(bounds, contentInsets), limitedToNumberOfLines: numberOfLines) 59 | } 60 | 61 | 62 | override open var intrinsicContentSize : CGSize { 63 | let size = super.intrinsicContentSize 64 | let width = size.width + contentInsets.left + contentInsets.right 65 | let height = size.height + contentInsets.top + contentInsets.bottom 66 | return CGSize(width: width, height: height) 67 | } 68 | 69 | override public func drawText(in rect: CGRect) { 70 | super.drawText(in: UIEdgeInsetsInsetRect(rect, contentInsets)) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /SweetKit/SweetKit/Views/UIPlaceholderTextView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class UIPlaceholderTextView: UITextView { 4 | 5 | // MARK: - Public Properties 6 | 7 | /// Determines whether or not the placeholder text view contains text. 8 | open var isEmpty: Bool { return text.isEmpty } 9 | 10 | /// The string that is displayed when there is no other text in the placeholder text view. This value is `nil` by default. 11 | @IBInspectable open var placeholder: String? { didSet { setNeedsDisplay() } } 12 | 13 | /// The color of the placeholder. This property applies to the entire placeholder string. The default placeholder color is `UIColor(red: 0.6, green: 0.6, blue: 0.6, alpha: 1.0)`. 14 | @IBInspectable open var placeholderColor: UIColor = UIColor(red: 0.6, green: 0.6, blue: 0.6, alpha: 1.0) { didSet { setNeedsDisplay() } } 15 | 16 | /// Max number of characters allowed by TextField. 17 | @IBInspectable public var maxNumberOfCharacters: Int = 0 18 | 19 | // MARK: - Superclass Properties 20 | 21 | override open var attributedText: NSAttributedString! { didSet { setNeedsDisplay() } } 22 | 23 | override open var bounds: CGRect { didSet { setNeedsDisplay() } } 24 | 25 | override open var contentInset: UIEdgeInsets { didSet { setNeedsDisplay() } } 26 | 27 | override open var font: UIFont? { didSet { setNeedsDisplay() } } 28 | 29 | override open var textAlignment: NSTextAlignment { didSet { setNeedsDisplay() } } 30 | 31 | override open var textContainerInset: UIEdgeInsets { didSet { setNeedsDisplay() } } 32 | 33 | override open var typingAttributes: [String : Any] { 34 | didSet { 35 | guard isEmpty else { 36 | return 37 | } 38 | setNeedsDisplay() 39 | } 40 | } 41 | 42 | // MARK: - Object Lifecycle 43 | 44 | deinit { 45 | NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UITextViewTextDidChange, object: self) 46 | } 47 | 48 | required public init?(coder aDecoder: NSCoder) { 49 | super.init(coder: aDecoder) 50 | commonInitializer() 51 | } 52 | 53 | override public init(frame: CGRect, textContainer: NSTextContainer?) { 54 | super.init(frame: frame, textContainer: textContainer) 55 | commonInitializer() 56 | } 57 | 58 | // MARK: - Drawing 59 | 60 | override open func draw(_ rect: CGRect) { 61 | super.draw(rect) 62 | 63 | guard isEmpty else { 64 | return 65 | } 66 | guard let placeholder = self.placeholder else { 67 | return 68 | } 69 | 70 | var placeholderAttributes = typingAttributes 71 | if placeholderAttributes[NSFontAttributeName] == nil { 72 | placeholderAttributes[NSFontAttributeName] = typingAttributes[NSFontAttributeName] ?? font ?? UIFont.systemFont(ofSize: UIFont.systemFontSize) 73 | } 74 | if placeholderAttributes[NSParagraphStyleAttributeName] == nil { 75 | let typingParagraphStyle = typingAttributes[NSParagraphStyleAttributeName] 76 | if typingParagraphStyle == nil { 77 | let paragraphStyle = NSMutableParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle 78 | paragraphStyle.alignment = textAlignment 79 | paragraphStyle.lineBreakMode = textContainer.lineBreakMode 80 | placeholderAttributes[NSParagraphStyleAttributeName] = paragraphStyle 81 | } else { 82 | placeholderAttributes[NSParagraphStyleAttributeName] = typingParagraphStyle 83 | } 84 | } 85 | placeholderAttributes[NSForegroundColorAttributeName] = placeholderColor 86 | 87 | let paraph = NSMutableParagraphStyle() 88 | 89 | paraph.lineSpacing = 3 90 | 91 | placeholderAttributes[NSParagraphStyleAttributeName] = paraph 92 | 93 | let placeholderInsets = UIEdgeInsets(top: contentInset.top + textContainerInset.top, 94 | left: contentInset.left + textContainerInset.left + textContainer.lineFragmentPadding, 95 | bottom: contentInset.bottom + textContainerInset.bottom, 96 | right: contentInset.right + textContainerInset.right + textContainer.lineFragmentPadding) 97 | 98 | let placeholderRect = UIEdgeInsetsInsetRect(rect, placeholderInsets) 99 | placeholder.draw(in: placeholderRect, withAttributes: placeholderAttributes) 100 | } 101 | 102 | // MARK: - Helper Methods 103 | 104 | fileprivate func commonInitializer() { 105 | contentMode = .topLeft 106 | maxNumberOfCharacters = 0 107 | NotificationCenter.default.addObserver(self, selector: #selector(UIPlaceholderTextView.handleTextViewTextDidChangeNotification(_:)), name: NSNotification.Name.UITextViewTextDidChange, object: self) 108 | } 109 | 110 | internal func handleTextViewTextDidChangeNotification(_ notification: Notification) { 111 | guard let object = notification.object as? UIPlaceholderTextView, object === self else { 112 | return 113 | } 114 | if self.maxNumberOfCharacters != 0, text.length >= self.maxNumberOfCharacters { 115 | self.text = text.substring(to: self.maxNumberOfCharacters) 116 | } 117 | setNeedsDisplay() 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /SweetKit/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SweetKit 4 | // 5 | // Created by danxiao on 2017/8/28. 6 | // Copyright © 2017年 DanXiao. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | 16 | } 17 | 18 | override func didReceiveMemoryWarning() { 19 | super.didReceiveMemoryWarning() 20 | 21 | } 22 | 23 | 24 | 25 | } 26 | --------------------------------------------------------------------------------