├── .swift-version ├── Demo ├── .gitignore ├── Podfile ├── Podfile.lock ├── TimedSilverExample.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata ├── TimedSilverExample.xcworkspace │ └── contents.xcworkspacedata ├── TimedSilverExample │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── ViewController.swift └── TimedSilverExampleTests │ ├── Info.plist │ └── TimedSilverExampleTests.swift ├── LICENSE ├── Package.swift ├── README.md ├── Sources ├── Foundation │ ├── Bundle+TSExtension.swift │ ├── Data+TSExtension.swift │ ├── Date+TSExtension.swift │ ├── DispatchQueue+TSExtension.swift │ ├── FileManager+TSExtension.swift │ ├── IndexPath+TSOffset.swift │ ├── IndexSet+TSExtension.swift │ ├── NSDictionary+TSExtension.swift │ ├── NSObject+TSExtension.swift │ ├── NSRange+TSExtension.swift │ ├── NSString+TSExtension.swift │ ├── NotificationCenter+TSBlock.swift │ ├── Timer+TSBlock.swift │ ├── URLRequest+TScURLCommand.swift │ ├── UserDefaults+TSArchiveData.swift │ └── UserDefaults+TSExtension.swift ├── Struct │ ├── Array+TSExtension.swift │ ├── CGSize+TSExtension.swift │ ├── Dictionary+TSExtension.swift │ ├── Double+TSExtension.swift │ ├── String+TSCrypto.swift │ └── String+TSExtension.swift ├── TimedSilverHeader.h └── UIKit │ ├── UIAlertController+TSExtension.swift │ ├── UIApplication+TSExtension.swift │ ├── UIButton+TSExtension.swift │ ├── UIButton+TSTouchArea.swift │ ├── UICollectionView+TSExtension.swift │ ├── UICollectionView+TSGeneric.swift │ ├── UIColor+TSExtension.swift │ ├── UIControl+TSExtension.swift │ ├── UIControl+TSSound.swift │ ├── UIDevice+TSExtension.swift │ ├── UIDevice+TSType.swift │ ├── UIImage+TSExtension.swift │ ├── UIImage+TSLaunchImage.swift │ ├── UIImage+TSOrientation.swift │ ├── UIImage+TSResize.swift │ ├── UIImage+TSRoundedCorner.swift │ ├── UILabel+TSExtension.swift │ ├── UINavigationController+TSExtension.swift │ ├── UINavigationItem+TSExtension.swift │ ├── UIScreen+TSExtension.swift │ ├── UIScrollView+TSExtension.swift │ ├── UIScrollView+TSPage.swift │ ├── UISearchBar+TSExtension.swift │ ├── UITableView+TSExtension.swift │ ├── UITableView+TSGeneric.swift │ ├── UITableView+TSiOS7SettingStyle.swift │ ├── UIView+TSExtension.swift │ ├── UIView+TSFrame.swift │ ├── UIView+TSGestureBlock.swift │ ├── UIViewController+TSExtension.swift │ ├── UIViewController+TSVisible.swift │ └── UIWindow+TSHierarchy.swift └── TimedSilver.podspec /.swift-version: -------------------------------------------------------------------------------- 1 | 5.0 2 | -------------------------------------------------------------------------------- /Demo/.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.orig 19 | npm-debug.log 20 | xcshareddata 21 | 22 | # CocoaPods 23 | # 24 | # We recommend against adding the Pods directory to your .gitignore. However 25 | # you should judge for yourself, the pros and cons are mentioned at: 26 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control? 27 | # 28 | Pods/ 29 | 30 | 31 | ### AppCode ### 32 | ## Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 33 | # 34 | ### Directory-based project format 35 | .idea/ 36 | ## if you remove the above rule, at least ignore user-specific stuff: 37 | # .idea/workspace.xml 38 | ## .idea/tasks.xml 39 | ## and these sensitive or high-churn files: 40 | ## .idea/dataSources.ids 41 | ## .idea/dataSources.xml 42 | ## .idea/sqlDataSources.xml 43 | ## .idea/dynamic.xml 44 | # 45 | ### File-based project format 46 | *.ipr 47 | *.iml 48 | *.iws 49 | # 50 | ### Additional for IntelliJ 51 | out/ 52 | # 53 | ## generated by mpeltonen/sbt-idea plugin 54 | .idea_modules/ 55 | # 56 | ## generated by JIRA plugin 57 | atlassian-ide-plugin.xml 58 | # 59 | ## generated by Crashlytics plugin (for Android Studio and Intellij) 60 | com_crashlytics_export_strings.xml 61 | # 62 | # 63 | ReactComponent/ 64 | # 65 | ## React Native JS file 66 | # 67 | # 68 | cleanUUID.log 69 | 70 | -------------------------------------------------------------------------------- /Demo/Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | use_frameworks! 3 | 4 | target 'TimedSilverExample' do 5 | pod 'TimedSilver', :path => '../' 6 | end 7 | -------------------------------------------------------------------------------- /Demo/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - TimedSilver (1.1.0) 3 | 4 | DEPENDENCIES: 5 | - TimedSilver (from `../`) 6 | 7 | EXTERNAL SOURCES: 8 | TimedSilver: 9 | :path: "../" 10 | 11 | SPEC CHECKSUMS: 12 | TimedSilver: 7b9773d84a2c479893786b4123d6d41ae8be3d4c 13 | 14 | PODFILE CHECKSUM: 416a9304df26078601f30ee2451b2f3dbe8abf38 15 | 16 | COCOAPODS: 1.7.2 17 | -------------------------------------------------------------------------------- /Demo/TimedSilverExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 6640AF2AC6CA686EB97CB29B /* Pods_TimedSilverExample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C667354C03077DB65A58E241 /* Pods_TimedSilverExample.framework */; }; 11 | AEBAE9401D8E8AF200CFDBD5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEBAE93F1D8E8AF200CFDBD5 /* AppDelegate.swift */; }; 12 | AEBAE9421D8E8AF200CFDBD5 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEBAE9411D8E8AF200CFDBD5 /* ViewController.swift */; }; 13 | AEBAE9451D8E8AF200CFDBD5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AEBAE9431D8E8AF200CFDBD5 /* Main.storyboard */; }; 14 | AEBAE9471D8E8AF200CFDBD5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AEBAE9461D8E8AF200CFDBD5 /* Assets.xcassets */; }; 15 | AEBAE94A1D8E8AF200CFDBD5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AEBAE9481D8E8AF200CFDBD5 /* LaunchScreen.storyboard */; }; 16 | AEBAE9551D8E8AF200CFDBD5 /* TimedSilverExampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEBAE9541D8E8AF200CFDBD5 /* TimedSilverExampleTests.swift */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXContainerItemProxy section */ 20 | AEBAE9511D8E8AF200CFDBD5 /* PBXContainerItemProxy */ = { 21 | isa = PBXContainerItemProxy; 22 | containerPortal = AEBAE9341D8E8AF200CFDBD5 /* Project object */; 23 | proxyType = 1; 24 | remoteGlobalIDString = AEBAE93B1D8E8AF200CFDBD5; 25 | remoteInfo = TimedSilverExample; 26 | }; 27 | /* End PBXContainerItemProxy section */ 28 | 29 | /* Begin PBXFileReference section */ 30 | 74904B260662CC4D85CA4568 /* Pods-TimedSilverExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TimedSilverExample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-TimedSilverExample/Pods-TimedSilverExample.debug.xcconfig"; sourceTree = ""; }; 31 | 96DEBC55B60AFCE4512BCF98 /* Pods-TimedSilverExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TimedSilverExample.release.xcconfig"; path = "Pods/Target Support Files/Pods-TimedSilverExample/Pods-TimedSilverExample.release.xcconfig"; sourceTree = ""; }; 32 | AEBAE93C1D8E8AF200CFDBD5 /* TimedSilverExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TimedSilverExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33 | AEBAE93F1D8E8AF200CFDBD5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 34 | AEBAE9411D8E8AF200CFDBD5 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 35 | AEBAE9441D8E8AF200CFDBD5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 36 | AEBAE9461D8E8AF200CFDBD5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 37 | AEBAE9491D8E8AF200CFDBD5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 38 | AEBAE94B1D8E8AF200CFDBD5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 39 | AEBAE9501D8E8AF200CFDBD5 /* TimedSilverExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TimedSilverExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 40 | AEBAE9541D8E8AF200CFDBD5 /* TimedSilverExampleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimedSilverExampleTests.swift; sourceTree = ""; }; 41 | AEBAE9561D8E8AF200CFDBD5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 42 | C667354C03077DB65A58E241 /* Pods_TimedSilverExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TimedSilverExample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 43 | /* End PBXFileReference section */ 44 | 45 | /* Begin PBXFrameworksBuildPhase section */ 46 | AEBAE9391D8E8AF200CFDBD5 /* Frameworks */ = { 47 | isa = PBXFrameworksBuildPhase; 48 | buildActionMask = 2147483647; 49 | files = ( 50 | 6640AF2AC6CA686EB97CB29B /* Pods_TimedSilverExample.framework in Frameworks */, 51 | ); 52 | runOnlyForDeploymentPostprocessing = 0; 53 | }; 54 | AEBAE94D1D8E8AF200CFDBD5 /* Frameworks */ = { 55 | isa = PBXFrameworksBuildPhase; 56 | buildActionMask = 2147483647; 57 | files = ( 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | /* End PBXFrameworksBuildPhase section */ 62 | 63 | /* Begin PBXGroup section */ 64 | 9F8718EE01F821E5C854F8F6 /* Frameworks */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | C667354C03077DB65A58E241 /* Pods_TimedSilverExample.framework */, 68 | ); 69 | name = Frameworks; 70 | sourceTree = ""; 71 | }; 72 | AEBAE9331D8E8AF200CFDBD5 = { 73 | isa = PBXGroup; 74 | children = ( 75 | AEBAE93E1D8E8AF200CFDBD5 /* TimedSilverExample */, 76 | AEBAE9531D8E8AF200CFDBD5 /* TimedSilverExampleTests */, 77 | AEBAE93D1D8E8AF200CFDBD5 /* Products */, 78 | D47055B4FC0570C764FE0D24 /* Pods */, 79 | 9F8718EE01F821E5C854F8F6 /* Frameworks */, 80 | ); 81 | sourceTree = ""; 82 | }; 83 | AEBAE93D1D8E8AF200CFDBD5 /* Products */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | AEBAE93C1D8E8AF200CFDBD5 /* TimedSilverExample.app */, 87 | AEBAE9501D8E8AF200CFDBD5 /* TimedSilverExampleTests.xctest */, 88 | ); 89 | name = Products; 90 | sourceTree = ""; 91 | }; 92 | AEBAE93E1D8E8AF200CFDBD5 /* TimedSilverExample */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | AEBAE93F1D8E8AF200CFDBD5 /* AppDelegate.swift */, 96 | AEBAE9411D8E8AF200CFDBD5 /* ViewController.swift */, 97 | AEBAE9431D8E8AF200CFDBD5 /* Main.storyboard */, 98 | AEBAE9461D8E8AF200CFDBD5 /* Assets.xcassets */, 99 | AEBAE9481D8E8AF200CFDBD5 /* LaunchScreen.storyboard */, 100 | AEBAE94B1D8E8AF200CFDBD5 /* Info.plist */, 101 | ); 102 | path = TimedSilverExample; 103 | sourceTree = ""; 104 | }; 105 | AEBAE9531D8E8AF200CFDBD5 /* TimedSilverExampleTests */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | AEBAE9541D8E8AF200CFDBD5 /* TimedSilverExampleTests.swift */, 109 | AEBAE9561D8E8AF200CFDBD5 /* Info.plist */, 110 | ); 111 | path = TimedSilverExampleTests; 112 | sourceTree = ""; 113 | }; 114 | D47055B4FC0570C764FE0D24 /* Pods */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | 74904B260662CC4D85CA4568 /* Pods-TimedSilverExample.debug.xcconfig */, 118 | 96DEBC55B60AFCE4512BCF98 /* Pods-TimedSilverExample.release.xcconfig */, 119 | ); 120 | name = Pods; 121 | sourceTree = ""; 122 | }; 123 | /* End PBXGroup section */ 124 | 125 | /* Begin PBXNativeTarget section */ 126 | AEBAE93B1D8E8AF200CFDBD5 /* TimedSilverExample */ = { 127 | isa = PBXNativeTarget; 128 | buildConfigurationList = AEBAE9591D8E8AF200CFDBD5 /* Build configuration list for PBXNativeTarget "TimedSilverExample" */; 129 | buildPhases = ( 130 | 0249506483674692E9C1309A /* [CP] Check Pods Manifest.lock */, 131 | AEBAE9381D8E8AF200CFDBD5 /* Sources */, 132 | AEBAE9391D8E8AF200CFDBD5 /* Frameworks */, 133 | AEBAE93A1D8E8AF200CFDBD5 /* Resources */, 134 | ED52BC296B9EF83249163349 /* [CP] Embed Pods Frameworks */, 135 | ); 136 | buildRules = ( 137 | ); 138 | dependencies = ( 139 | ); 140 | name = TimedSilverExample; 141 | productName = TimedSilverExample; 142 | productReference = AEBAE93C1D8E8AF200CFDBD5 /* TimedSilverExample.app */; 143 | productType = "com.apple.product-type.application"; 144 | }; 145 | AEBAE94F1D8E8AF200CFDBD5 /* TimedSilverExampleTests */ = { 146 | isa = PBXNativeTarget; 147 | buildConfigurationList = AEBAE95C1D8E8AF200CFDBD5 /* Build configuration list for PBXNativeTarget "TimedSilverExampleTests" */; 148 | buildPhases = ( 149 | AEBAE94C1D8E8AF200CFDBD5 /* Sources */, 150 | AEBAE94D1D8E8AF200CFDBD5 /* Frameworks */, 151 | AEBAE94E1D8E8AF200CFDBD5 /* Resources */, 152 | ); 153 | buildRules = ( 154 | ); 155 | dependencies = ( 156 | AEBAE9521D8E8AF200CFDBD5 /* PBXTargetDependency */, 157 | ); 158 | name = TimedSilverExampleTests; 159 | productName = TimedSilverExampleTests; 160 | productReference = AEBAE9501D8E8AF200CFDBD5 /* TimedSilverExampleTests.xctest */; 161 | productType = "com.apple.product-type.bundle.unit-test"; 162 | }; 163 | /* End PBXNativeTarget section */ 164 | 165 | /* Begin PBXProject section */ 166 | AEBAE9341D8E8AF200CFDBD5 /* Project object */ = { 167 | isa = PBXProject; 168 | attributes = { 169 | LastSwiftUpdateCheck = 0800; 170 | LastUpgradeCheck = 1030; 171 | ORGANIZATIONNAME = Hilen; 172 | TargetAttributes = { 173 | AEBAE93B1D8E8AF200CFDBD5 = { 174 | CreatedOnToolsVersion = 8.0; 175 | LastSwiftMigration = 0800; 176 | ProvisioningStyle = Automatic; 177 | }; 178 | AEBAE94F1D8E8AF200CFDBD5 = { 179 | CreatedOnToolsVersion = 8.0; 180 | LastSwiftMigration = 1030; 181 | ProvisioningStyle = Automatic; 182 | TestTargetID = AEBAE93B1D8E8AF200CFDBD5; 183 | }; 184 | }; 185 | }; 186 | buildConfigurationList = AEBAE9371D8E8AF200CFDBD5 /* Build configuration list for PBXProject "TimedSilverExample" */; 187 | compatibilityVersion = "Xcode 3.2"; 188 | developmentRegion = English; 189 | hasScannedForEncodings = 0; 190 | knownRegions = ( 191 | English, 192 | en, 193 | Base, 194 | ); 195 | mainGroup = AEBAE9331D8E8AF200CFDBD5; 196 | productRefGroup = AEBAE93D1D8E8AF200CFDBD5 /* Products */; 197 | projectDirPath = ""; 198 | projectRoot = ""; 199 | targets = ( 200 | AEBAE93B1D8E8AF200CFDBD5 /* TimedSilverExample */, 201 | AEBAE94F1D8E8AF200CFDBD5 /* TimedSilverExampleTests */, 202 | ); 203 | }; 204 | /* End PBXProject section */ 205 | 206 | /* Begin PBXResourcesBuildPhase section */ 207 | AEBAE93A1D8E8AF200CFDBD5 /* Resources */ = { 208 | isa = PBXResourcesBuildPhase; 209 | buildActionMask = 2147483647; 210 | files = ( 211 | AEBAE94A1D8E8AF200CFDBD5 /* LaunchScreen.storyboard in Resources */, 212 | AEBAE9471D8E8AF200CFDBD5 /* Assets.xcassets in Resources */, 213 | AEBAE9451D8E8AF200CFDBD5 /* Main.storyboard in Resources */, 214 | ); 215 | runOnlyForDeploymentPostprocessing = 0; 216 | }; 217 | AEBAE94E1D8E8AF200CFDBD5 /* Resources */ = { 218 | isa = PBXResourcesBuildPhase; 219 | buildActionMask = 2147483647; 220 | files = ( 221 | ); 222 | runOnlyForDeploymentPostprocessing = 0; 223 | }; 224 | /* End PBXResourcesBuildPhase section */ 225 | 226 | /* Begin PBXShellScriptBuildPhase section */ 227 | 0249506483674692E9C1309A /* [CP] Check Pods Manifest.lock */ = { 228 | isa = PBXShellScriptBuildPhase; 229 | buildActionMask = 2147483647; 230 | files = ( 231 | ); 232 | inputPaths = ( 233 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 234 | "${PODS_ROOT}/Manifest.lock", 235 | ); 236 | name = "[CP] Check Pods Manifest.lock"; 237 | outputPaths = ( 238 | "$(DERIVED_FILE_DIR)/Pods-TimedSilverExample-checkManifestLockResult.txt", 239 | ); 240 | runOnlyForDeploymentPostprocessing = 0; 241 | shellPath = /bin/sh; 242 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 243 | showEnvVarsInLog = 0; 244 | }; 245 | ED52BC296B9EF83249163349 /* [CP] Embed Pods Frameworks */ = { 246 | isa = PBXShellScriptBuildPhase; 247 | buildActionMask = 2147483647; 248 | files = ( 249 | ); 250 | inputPaths = ( 251 | "${PODS_ROOT}/Target Support Files/Pods-TimedSilverExample/Pods-TimedSilverExample-frameworks.sh", 252 | "${BUILT_PRODUCTS_DIR}/TimedSilver/TimedSilver.framework", 253 | ); 254 | name = "[CP] Embed Pods Frameworks"; 255 | outputPaths = ( 256 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TimedSilver.framework", 257 | ); 258 | runOnlyForDeploymentPostprocessing = 0; 259 | shellPath = /bin/sh; 260 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-TimedSilverExample/Pods-TimedSilverExample-frameworks.sh\"\n"; 261 | showEnvVarsInLog = 0; 262 | }; 263 | /* End PBXShellScriptBuildPhase section */ 264 | 265 | /* Begin PBXSourcesBuildPhase section */ 266 | AEBAE9381D8E8AF200CFDBD5 /* Sources */ = { 267 | isa = PBXSourcesBuildPhase; 268 | buildActionMask = 2147483647; 269 | files = ( 270 | AEBAE9421D8E8AF200CFDBD5 /* ViewController.swift in Sources */, 271 | AEBAE9401D8E8AF200CFDBD5 /* AppDelegate.swift in Sources */, 272 | ); 273 | runOnlyForDeploymentPostprocessing = 0; 274 | }; 275 | AEBAE94C1D8E8AF200CFDBD5 /* Sources */ = { 276 | isa = PBXSourcesBuildPhase; 277 | buildActionMask = 2147483647; 278 | files = ( 279 | AEBAE9551D8E8AF200CFDBD5 /* TimedSilverExampleTests.swift in Sources */, 280 | ); 281 | runOnlyForDeploymentPostprocessing = 0; 282 | }; 283 | /* End PBXSourcesBuildPhase section */ 284 | 285 | /* Begin PBXTargetDependency section */ 286 | AEBAE9521D8E8AF200CFDBD5 /* PBXTargetDependency */ = { 287 | isa = PBXTargetDependency; 288 | target = AEBAE93B1D8E8AF200CFDBD5 /* TimedSilverExample */; 289 | targetProxy = AEBAE9511D8E8AF200CFDBD5 /* PBXContainerItemProxy */; 290 | }; 291 | /* End PBXTargetDependency section */ 292 | 293 | /* Begin PBXVariantGroup section */ 294 | AEBAE9431D8E8AF200CFDBD5 /* Main.storyboard */ = { 295 | isa = PBXVariantGroup; 296 | children = ( 297 | AEBAE9441D8E8AF200CFDBD5 /* Base */, 298 | ); 299 | name = Main.storyboard; 300 | sourceTree = ""; 301 | }; 302 | AEBAE9481D8E8AF200CFDBD5 /* LaunchScreen.storyboard */ = { 303 | isa = PBXVariantGroup; 304 | children = ( 305 | AEBAE9491D8E8AF200CFDBD5 /* Base */, 306 | ); 307 | name = LaunchScreen.storyboard; 308 | sourceTree = ""; 309 | }; 310 | /* End PBXVariantGroup section */ 311 | 312 | /* Begin XCBuildConfiguration section */ 313 | AEBAE9571D8E8AF200CFDBD5 /* Debug */ = { 314 | isa = XCBuildConfiguration; 315 | buildSettings = { 316 | ALWAYS_SEARCH_USER_PATHS = NO; 317 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 318 | CLANG_ANALYZER_NONNULL = YES; 319 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 320 | CLANG_CXX_LIBRARY = "libc++"; 321 | CLANG_ENABLE_MODULES = YES; 322 | CLANG_ENABLE_OBJC_ARC = YES; 323 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 324 | CLANG_WARN_BOOL_CONVERSION = YES; 325 | CLANG_WARN_COMMA = YES; 326 | CLANG_WARN_CONSTANT_CONVERSION = YES; 327 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 328 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 329 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 330 | CLANG_WARN_EMPTY_BODY = YES; 331 | CLANG_WARN_ENUM_CONVERSION = YES; 332 | CLANG_WARN_INFINITE_RECURSION = YES; 333 | CLANG_WARN_INT_CONVERSION = YES; 334 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 335 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 336 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 337 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 338 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 339 | CLANG_WARN_STRICT_PROTOTYPES = YES; 340 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 341 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 342 | CLANG_WARN_UNREACHABLE_CODE = YES; 343 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 344 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 345 | COPY_PHASE_STRIP = NO; 346 | DEBUG_INFORMATION_FORMAT = dwarf; 347 | ENABLE_STRICT_OBJC_MSGSEND = YES; 348 | ENABLE_TESTABILITY = YES; 349 | GCC_C_LANGUAGE_STANDARD = gnu99; 350 | GCC_DYNAMIC_NO_PIC = NO; 351 | GCC_NO_COMMON_BLOCKS = YES; 352 | GCC_OPTIMIZATION_LEVEL = 0; 353 | GCC_PREPROCESSOR_DEFINITIONS = ( 354 | "DEBUG=1", 355 | "$(inherited)", 356 | ); 357 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 358 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 359 | GCC_WARN_UNDECLARED_SELECTOR = YES; 360 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 361 | GCC_WARN_UNUSED_FUNCTION = YES; 362 | GCC_WARN_UNUSED_VARIABLE = YES; 363 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 364 | MTL_ENABLE_DEBUG_INFO = YES; 365 | ONLY_ACTIVE_ARCH = YES; 366 | SDKROOT = iphoneos; 367 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 368 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 369 | }; 370 | name = Debug; 371 | }; 372 | AEBAE9581D8E8AF200CFDBD5 /* Release */ = { 373 | isa = XCBuildConfiguration; 374 | buildSettings = { 375 | ALWAYS_SEARCH_USER_PATHS = NO; 376 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 377 | CLANG_ANALYZER_NONNULL = YES; 378 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 379 | CLANG_CXX_LIBRARY = "libc++"; 380 | CLANG_ENABLE_MODULES = YES; 381 | CLANG_ENABLE_OBJC_ARC = YES; 382 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 383 | CLANG_WARN_BOOL_CONVERSION = YES; 384 | CLANG_WARN_COMMA = YES; 385 | CLANG_WARN_CONSTANT_CONVERSION = YES; 386 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 387 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 388 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 389 | CLANG_WARN_EMPTY_BODY = YES; 390 | CLANG_WARN_ENUM_CONVERSION = YES; 391 | CLANG_WARN_INFINITE_RECURSION = YES; 392 | CLANG_WARN_INT_CONVERSION = YES; 393 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 394 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 395 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 396 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 397 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 398 | CLANG_WARN_STRICT_PROTOTYPES = YES; 399 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 400 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 401 | CLANG_WARN_UNREACHABLE_CODE = YES; 402 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 403 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 404 | COPY_PHASE_STRIP = NO; 405 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 406 | ENABLE_NS_ASSERTIONS = NO; 407 | ENABLE_STRICT_OBJC_MSGSEND = YES; 408 | GCC_C_LANGUAGE_STANDARD = gnu99; 409 | GCC_NO_COMMON_BLOCKS = YES; 410 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 411 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 412 | GCC_WARN_UNDECLARED_SELECTOR = YES; 413 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 414 | GCC_WARN_UNUSED_FUNCTION = YES; 415 | GCC_WARN_UNUSED_VARIABLE = YES; 416 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 417 | MTL_ENABLE_DEBUG_INFO = NO; 418 | SDKROOT = iphoneos; 419 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 420 | VALIDATE_PRODUCT = YES; 421 | }; 422 | name = Release; 423 | }; 424 | AEBAE95A1D8E8AF200CFDBD5 /* Debug */ = { 425 | isa = XCBuildConfiguration; 426 | baseConfigurationReference = 74904B260662CC4D85CA4568 /* Pods-TimedSilverExample.debug.xcconfig */; 427 | buildSettings = { 428 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 429 | INFOPLIST_FILE = TimedSilverExample/Info.plist; 430 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 431 | PRODUCT_BUNDLE_IDENTIFIER = Hilen.TimedSilverExample; 432 | PRODUCT_NAME = "$(TARGET_NAME)"; 433 | SWIFT_VERSION = 5.0; 434 | }; 435 | name = Debug; 436 | }; 437 | AEBAE95B1D8E8AF200CFDBD5 /* Release */ = { 438 | isa = XCBuildConfiguration; 439 | baseConfigurationReference = 96DEBC55B60AFCE4512BCF98 /* Pods-TimedSilverExample.release.xcconfig */; 440 | buildSettings = { 441 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 442 | INFOPLIST_FILE = TimedSilverExample/Info.plist; 443 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 444 | PRODUCT_BUNDLE_IDENTIFIER = Hilen.TimedSilverExample; 445 | PRODUCT_NAME = "$(TARGET_NAME)"; 446 | SWIFT_VERSION = 5.0; 447 | }; 448 | name = Release; 449 | }; 450 | AEBAE95D1D8E8AF200CFDBD5 /* Debug */ = { 451 | isa = XCBuildConfiguration; 452 | buildSettings = { 453 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 454 | BUNDLE_LOADER = "$(TEST_HOST)"; 455 | INFOPLIST_FILE = TimedSilverExampleTests/Info.plist; 456 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 457 | PRODUCT_BUNDLE_IDENTIFIER = Hilen.TimedSilverExampleTests; 458 | PRODUCT_NAME = "$(TARGET_NAME)"; 459 | SWIFT_VERSION = 5.0; 460 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TimedSilverExample.app/TimedSilverExample"; 461 | }; 462 | name = Debug; 463 | }; 464 | AEBAE95E1D8E8AF200CFDBD5 /* Release */ = { 465 | isa = XCBuildConfiguration; 466 | buildSettings = { 467 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 468 | BUNDLE_LOADER = "$(TEST_HOST)"; 469 | INFOPLIST_FILE = TimedSilverExampleTests/Info.plist; 470 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 471 | PRODUCT_BUNDLE_IDENTIFIER = Hilen.TimedSilverExampleTests; 472 | PRODUCT_NAME = "$(TARGET_NAME)"; 473 | SWIFT_VERSION = 5.0; 474 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TimedSilverExample.app/TimedSilverExample"; 475 | }; 476 | name = Release; 477 | }; 478 | /* End XCBuildConfiguration section */ 479 | 480 | /* Begin XCConfigurationList section */ 481 | AEBAE9371D8E8AF200CFDBD5 /* Build configuration list for PBXProject "TimedSilverExample" */ = { 482 | isa = XCConfigurationList; 483 | buildConfigurations = ( 484 | AEBAE9571D8E8AF200CFDBD5 /* Debug */, 485 | AEBAE9581D8E8AF200CFDBD5 /* Release */, 486 | ); 487 | defaultConfigurationIsVisible = 0; 488 | defaultConfigurationName = Release; 489 | }; 490 | AEBAE9591D8E8AF200CFDBD5 /* Build configuration list for PBXNativeTarget "TimedSilverExample" */ = { 491 | isa = XCConfigurationList; 492 | buildConfigurations = ( 493 | AEBAE95A1D8E8AF200CFDBD5 /* Debug */, 494 | AEBAE95B1D8E8AF200CFDBD5 /* Release */, 495 | ); 496 | defaultConfigurationIsVisible = 0; 497 | defaultConfigurationName = Release; 498 | }; 499 | AEBAE95C1D8E8AF200CFDBD5 /* Build configuration list for PBXNativeTarget "TimedSilverExampleTests" */ = { 500 | isa = XCConfigurationList; 501 | buildConfigurations = ( 502 | AEBAE95D1D8E8AF200CFDBD5 /* Debug */, 503 | AEBAE95E1D8E8AF200CFDBD5 /* Release */, 504 | ); 505 | defaultConfigurationIsVisible = 0; 506 | defaultConfigurationName = Release; 507 | }; 508 | /* End XCConfigurationList section */ 509 | }; 510 | rootObject = AEBAE9341D8E8AF200CFDBD5 /* Project object */; 511 | } 512 | -------------------------------------------------------------------------------- /Demo/TimedSilverExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/TimedSilverExample.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Demo/TimedSilverExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // TimedSilverExample 4 | // 5 | // Created by Hilen on 9/18/16. 6 | // Copyright © 2016 Hilen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Demo/TimedSilverExample/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 | } -------------------------------------------------------------------------------- /Demo/TimedSilverExample/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 | -------------------------------------------------------------------------------- /Demo/TimedSilverExample/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 | -------------------------------------------------------------------------------- /Demo/TimedSilverExample/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 | -------------------------------------------------------------------------------- /Demo/TimedSilverExample/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // TimedSilverExample 4 | // 5 | // Created by Hilen on 9/18/16. 6 | // Copyright © 2016 Hilen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import TimedSilver 11 | 12 | class ViewController: UIViewController { 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | print(UIViewController.ts_className) 17 | // Do any additional setup after loading the view, typically from a nib. 18 | } 19 | 20 | override func didReceiveMemoryWarning() { 21 | super.didReceiveMemoryWarning() 22 | // Dispose of any resources that can be recreated. 23 | } 24 | 25 | 26 | } 27 | 28 | -------------------------------------------------------------------------------- /Demo/TimedSilverExampleTests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Demo/TimedSilverExampleTests/TimedSilverExampleTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TimedSilverExampleTests.swift 3 | // TimedSilverExampleTests 4 | // 5 | // Created by Hilen on 9/18/16. 6 | // Copyright © 2016 Hilen. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import TimedSilverExample 11 | 12 | class TimedSilverExampleTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Hilen Lai 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 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilen/TimedSilver/6cb922c3fce5ae7073debbb5b210fbd7b20e7983/Package.swift -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TimedSilver 2 | All about swift extension 😉 3 | 4 | 5 | ## Installation 6 | 7 | Swift 5.0 8 | ```ruby 9 | source 'https://github.com/CocoaPods/Specs.git' 10 | platform :ios, '9.0' 11 | use_frameworks! 12 | 13 | target 'YourApp' do 14 | pod 'TimedSilver', '1.2.0' 15 | end 16 | ``` 17 | 18 | Swift 4.0 19 | ```ruby 20 | source 'https://github.com/CocoaPods/Specs.git' 21 | platform :ios, '8.0' 22 | use_frameworks! 23 | 24 | target 'YourApp' do 25 | pod 'TimedSilver', '1.1.0' 26 | end 27 | ``` 28 | 29 | 30 | ## Documentation 31 | 32 | ``` 33 | ├── Foundation 34 | │   ├── Bundle+TSExtension.swift 35 | │   ├── Data+TSExtension.swift 36 | │   ├── Date+TSExtension.swift 37 | │   ├── DispatchQueue+TSExtension.swift 38 | │   ├── FileManager+TSExtension.swift 39 | │   ├── IndexPath+TSOffset.swift 40 | │   ├── IndexSet+TSExtension.swift 41 | │   ├── NSDictionary+TSExtension.swift 42 | │   ├── NSObject+TSExtension.swift 43 | │   ├── NSRange+TSExtension.swift 44 | │   ├── NSString+TSExtension.swift 45 | │   ├── NotificationCenter+TSBlock.swift 46 | │   ├── Timer+TSBlock.swift 47 | │   ├── URLRequest+TScURLCommand.swift 48 | │   ├── UserDefaults+TSArchiveData.swift 49 | │   └── UserDefaults+TSExtension.swift 50 | ├── Struct 51 | │   ├── Array+TSExtension.swift 52 | │   ├── CGSize+TSExtension.swift 53 | │   ├── Dictionary+TSExtension.swift 54 | │   ├── Double+TSExtension.swift 55 | │   ├── String+TSCrypto.swift 56 | │   └── String+TSExtension.swift 57 | ├── TimedSilverHeader.h 58 | └── UIKit 59 | ├── UIAlertController+TSExtension.swift 60 | ├── UIApplication+TSExtension.swift 61 | ├── UIButton+TSExtension.swift 62 | ├── UIButton+TSTouchArea.swift 63 | ├── UICollectionView+TSExtension.swift 64 | ├── UICollectionView+TSGeneric.swift 65 | ├── UIColor+TSExtension.swift 66 | ├── UIControl+TSExtension.swift 67 | ├── UIControl+TSSound.swift 68 | ├── UIDevice+TSExtension.swift 69 | ├── UIDevice+TSType.swift 70 | ├── UIImage+TSExtension.swift 71 | ├── UIImage+TSLaunchImage.swift 72 | ├── UIImage+TSOrientation.swift 73 | ├── UIImage+TSResize.swift 74 | ├── UIImage+TSRoundedCorner.swift 75 | ├── UILabel+TSExtension.swift 76 | ├── UINavigationController+TSExtension.swift 77 | ├── UINavigationItem+TSExtension.swift 78 | ├── UIScreen+TSExtension.swift 79 | ├── UIScrollView+TSExtension.swift 80 | ├── UIScrollView+TSPage.swift 81 | ├── UISearchBar+TSExtension.swift 82 | ├── UITableView+TSExtension.swift 83 | ├── UITableView+TSGeneric.swift 84 | ├── UITableView+TSiOS7SettingStyle.swift 85 | ├── UIView+TSExtension.swift 86 | ├── UIView+TSFrame.swift 87 | ├── UIView+TSGestureBlock.swift 88 | ├── UIViewController+TSExtension.swift 89 | ├── UIViewController+TSVisible.swift 90 | └── UIWindow+TSHierarchy.swift 91 | ``` 92 | 93 | ## License 94 | 95 | TimedSilver is released under the MIT license. See LICENSE for details. 96 | -------------------------------------------------------------------------------- /Sources/Foundation/Bundle+TSExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bundle+TSExtension.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/5/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | import UIKit 12 | 13 | public extension Bundle { 14 | /// The app's name 15 | static var ts_appName: String? { 16 | guard let name = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String else { 17 | return nil 18 | } 19 | return name 20 | } 21 | 22 | /// The app's version 23 | static var ts_appVersion: String { 24 | return Bundle.main.infoDictionary!["CFBundleShortVersionString"] as! String 25 | } 26 | 27 | /// The app's build number 28 | static var ts_appBuild: String { 29 | return Bundle.main.object(forInfoDictionaryKey: kCFBundleVersionKey as String) as! String 30 | } 31 | 32 | /// The app's bundle identifier 33 | static var ts_bundleIdentifier: String { 34 | return Bundle.main.infoDictionary!["CFBundleIdentifier"] as! String 35 | } 36 | 37 | /// The app's bundle name 38 | static var ts_bundleName: String { 39 | return Bundle.main.infoDictionary!["CFBundleName"] as! String 40 | } 41 | 42 | /// The app's version and build number 43 | static var ts_appVersionAndBuild: String { 44 | let version = ts_appVersion, build = ts_appBuild 45 | return version == build ? "v\(version)" : "v\(version)(\(build))" 46 | } 47 | 48 | /// App's icon file path 49 | class var ts_iconFilePath: String { 50 | let iconFilename = Bundle.main.object(forInfoDictionaryKey: "CFBundleIconFile") 51 | let iconBasename = (iconFilename as! NSString).deletingPathExtension 52 | let iconExtension = (iconFilename as! NSString).pathExtension 53 | return Bundle.main.path(forResource: iconBasename, ofType: iconExtension)! 54 | } 55 | 56 | /** 57 | App's icon image 58 | 59 | - returns: UIImage 60 | */ 61 | class func ts_iconImage() -> UIImage? { 62 | guard let image = UIImage(contentsOfFile:self.ts_iconFilePath) else { 63 | return nil 64 | } 65 | return image 66 | } 67 | 68 | } 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /Sources/Foundation/Data+TSExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Data+TSExtension.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 11/25/15. 7 | // Copyright © 2015 Hilen. All rights reserved. 8 | // 9 | 10 | import UIKit 11 | import Foundation 12 | 13 | public extension Data { 14 | /// Convert the iOS deviceToken to String 15 | var ts_tokenString: String { 16 | let characterSet: CharacterSet = CharacterSet(charactersIn:"<>") 17 | let deviceTokenString: String = (self.description as NSString).trimmingCharacters(in: characterSet).replacingOccurrences(of: " ", with:"") as String 18 | return deviceTokenString 19 | } 20 | 21 | /** 22 | Create NSData from JSON file 23 | 24 | - parameter fileName: name 25 | 26 | - returns: NSData 27 | */ 28 | static func ts_dataFromJSONFile(_ fileName: String) -> Data? { 29 | if let path = Bundle.main.path(forResource: fileName, ofType: "json") { 30 | do { 31 | let data = try Data(contentsOf: URL(fileURLWithPath: path), options: NSData.ReadingOptions.mappedIfSafe) 32 | return data 33 | } catch let error as NSError { 34 | print(error.localizedDescription) 35 | return nil 36 | } 37 | } else { 38 | print("Invalid filename/path.") 39 | return nil 40 | } 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /Sources/Foundation/Date+TSExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Date+TSExtension.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 2/22/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | 12 | public extension Date { 13 | /// Convert NSDate to Millisecond 14 | static var ts_milliseconds: TimeInterval { 15 | get { return Date().timeIntervalSince1970 * 1000 } 16 | } 17 | 18 | // MARK: Intervals In Seconds 19 | static func ts_minuteInSeconds() -> Double { return 60 } 20 | static func ts_hourInSeconds() -> Double { return 3600 } 21 | static func ts_dayInSeconds() -> Double { return 86400 } 22 | static func ts_weekInSeconds() -> Double { return 604800 } 23 | static func ts_yearInSeconds() -> Double { return 31556926 } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Foundation/DispatchQueue+TSExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DispatchQueue+TSExtension.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 9/18/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | import UIKit 12 | 13 | public extension DispatchQueue { 14 | // This method will dispatch the `block` to self. 15 | // If `self` is the main queue, and current threxad is main thread, the block 16 | // will be invoked immediately instead of being dispatched. 17 | func ts_safeAsync(_ block: @escaping ()->()) { 18 | if self === DispatchQueue.main && Thread.isMainThread { 19 | block() 20 | } else { 21 | async { block() } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Foundation/FileManager+TSExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileManager+TSExtension.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/10/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // https://github.com/shaojiankui/JKCategories/blob/master/JKCategories/Foundation/NSFileManager/NSFileManager%2BJKPaths.h 9 | 10 | import Foundation 11 | 12 | public extension FileManager { 13 | /** 14 | Get URL of Document directory. 15 | 16 | - returns: Document directory URL. 17 | */ 18 | class func ts_documentURL() -> URL { 19 | return ts_URLForDirectory(.documentDirectory)! 20 | } 21 | 22 | /** 23 | Get String of Document directory. 24 | 25 | - returns: Document directory String. 26 | */ 27 | class func ts_documentPath() -> String { 28 | return ts_pathForDirectory(.documentDirectory)! 29 | } 30 | 31 | /** 32 | Get URL of Library directory 33 | 34 | - returns: Library directory URL 35 | */ 36 | class func ts_libraryURL() -> URL { 37 | return ts_URLForDirectory(.libraryDirectory)! 38 | } 39 | 40 | /** 41 | Get String of Library directory 42 | 43 | - returns: Library directory String 44 | */ 45 | class func ts_libraryPath() -> String { 46 | return ts_pathForDirectory(.libraryDirectory)! 47 | } 48 | 49 | /** 50 | Get URL of Caches directory 51 | 52 | - returns: Caches directory URL 53 | */ 54 | class func ts_cachesURL() -> URL { 55 | return ts_URLForDirectory(.cachesDirectory)! 56 | } 57 | 58 | /** 59 | Get String of Caches directory 60 | 61 | - returns: Caches directory String 62 | */ 63 | class func ts_cachesPath() -> String { 64 | return ts_pathForDirectory(.cachesDirectory)! 65 | } 66 | 67 | /** 68 | Adds a special filesystem flag to a file to avoid iCloud backup it. 69 | 70 | - parameter filePath: Path to a file to set an attribute. 71 | */ 72 | class func ts_addSkipBackupAttributeToFile(_ filePath: String) { 73 | let url: URL = URL(fileURLWithPath: filePath) 74 | do { 75 | try (url as NSURL).setResourceValue(NSNumber(value: true as Bool), forKey: URLResourceKey.isExcludedFromBackupKey) 76 | } catch {} 77 | } 78 | 79 | /** 80 | Check available disk space in MB 81 | 82 | - returns: Double in MB 83 | */ 84 | class func ts_availableDiskSpaceMb() -> Double { 85 | let fileAttributes = try? `default`.attributesOfFileSystem(forPath: ts_documentPath()) 86 | if let fileSize = (fileAttributes![FileAttributeKey.systemSize] as AnyObject).doubleValue { 87 | return fileSize / Double(0x100000) 88 | } 89 | return 0 90 | } 91 | 92 | fileprivate class func ts_URLForDirectory(_ directory: FileManager.SearchPathDirectory) -> URL? { 93 | return `default`.urls(for: directory, in: .userDomainMask).last 94 | } 95 | 96 | fileprivate class func ts_pathForDirectory(_ directory: FileManager.SearchPathDirectory) -> String? { 97 | return NSSearchPathForDirectoriesInDomains(directory, .userDomainMask, true)[0] 98 | } 99 | } 100 | 101 | -------------------------------------------------------------------------------- /Sources/Foundation/IndexPath+TSOffset.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IndexPath+TSOffset.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/5/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | 12 | public extension IndexPath { 13 | /// The previous row's NSIndexPath. UITableView 14 | var ts_previousRow: IndexPath { 15 | let indexPath = IndexPath(row: (self as NSIndexPath).row - 1, section: (self as NSIndexPath).section) 16 | return indexPath 17 | } 18 | 19 | /// The next row's NSIndexPath. UITableView 20 | var ts_nextRow: IndexPath { 21 | let indexPath = IndexPath(row: (self as NSIndexPath).row + 1, section: (self as NSIndexPath).section) 22 | return indexPath 23 | } 24 | 25 | /// The previous item's NSIndexPath. UICollectionView 26 | var ts_previousItem: IndexPath { 27 | let indexPath = IndexPath(item: (self as NSIndexPath).item - 1, section: (self as NSIndexPath).section) 28 | return indexPath 29 | } 30 | 31 | /// The next item's NSIndexPath. UICollectionView 32 | var ts_nextItem: IndexPath { 33 | let indexPath = IndexPath(item: (self as NSIndexPath).item + 1, section: (self as NSIndexPath).section) 34 | return indexPath 35 | } 36 | 37 | /// The previous section. Both of UICollectionView and UITableView 38 | var ts_previousSection: IndexPath { 39 | let indexPath = IndexPath(row: (self as NSIndexPath).row, section: (self as NSIndexPath).section - 1) 40 | return indexPath 41 | } 42 | 43 | /// The next section. Both of UICollectionView and UITableView 44 | var ts_nextSection: IndexPath { 45 | let indexPath = IndexPath(row: (self as NSIndexPath).row, section: (self as NSIndexPath).section + 1) 46 | return indexPath 47 | } 48 | } 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /Sources/Foundation/IndexSet+TSExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSIndexSet+TSExtension.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/9/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | 12 | public extension IndexSet { 13 | func ts_indexPathsFromIndexes(section: NSInteger) -> [IndexPath] { 14 | var indexPaths: [IndexPath] = [] 15 | indexPaths.reserveCapacity(self.count) 16 | (self as NSIndexSet).enumerate({idx, stop in 17 | indexPaths.append(IndexPath(item: idx, section: section)) 18 | }) 19 | return indexPaths 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Foundation/NSDictionary+TSExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSDictionary+TSExtension.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/4/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | 12 | public extension NSDictionary { 13 | /** 14 | Init from json string 15 | 16 | - parameter ts_JSONString: json string 17 | 18 | - returns: NSDictionary or nil 19 | */ 20 | convenience init? (ts_JSONString: String) { 21 | if let data = (try? JSONSerialization.jsonObject(with: ts_JSONString.data(using: String.Encoding.utf8, allowLossyConversion: true)!, options: JSONSerialization.ReadingOptions.mutableContainers)) as? NSDictionary { 22 | self.init(dictionary: data) 23 | } else { 24 | self.init() 25 | return nil 26 | } 27 | } 28 | 29 | /** 30 | Make the NSDictionary to json string 31 | 32 | - returns: string or nil 33 | */ 34 | func ts_formatJSON() -> String? { 35 | if let jsonData = try? JSONSerialization.data(withJSONObject: self, options: JSONSerialization.WritingOptions()) { 36 | let jsonStr = NSString(data: jsonData, encoding: String.Encoding.utf8.rawValue) 37 | return String(jsonStr ?? "") 38 | } 39 | return nil 40 | } 41 | } 42 | 43 | 44 | -------------------------------------------------------------------------------- /Sources/Foundation/NSObject+TSExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+TSExtension.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 11/3/15. 7 | // Copyright © 2015 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | import UIKit 12 | 13 | public extension NSObject { 14 | /// The class's name 15 | class var ts_className: String { 16 | return NSStringFromClass(self).components(separatedBy: ".").last! as String 17 | } 18 | 19 | /// The class's identifier, for UITableView,UICollectionView register its cell 20 | class var ts_identifier: String { 21 | return String(format: "%@_identifier", self.ts_className) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/Foundation/NSRange+TSExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSRange+TSExtension.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 9/15/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | 12 | public extension NSRange { 13 | /** 14 | Init with location and length 15 | 16 | - parameter ts_location: location 17 | - parameter ts_length: length 18 | 19 | - returns: NSRange 20 | */ 21 | init(ts_location:Int, ts_length:Int) { 22 | self.init() 23 | self.location = ts_location 24 | self.length = ts_length 25 | } 26 | 27 | /** 28 | Init with location and length 29 | 30 | - parameter ts_location: location 31 | - parameter ts_length: length 32 | 33 | - returns: NSRange 34 | */ 35 | init(_ ts_location:Int, _ ts_length:Int) { 36 | self.init() 37 | self.location = ts_location 38 | self.length = ts_length 39 | } 40 | 41 | /** 42 | Init from Range 43 | 44 | - parameter ts_range: Range 45 | 46 | - returns: NSRange 47 | */ 48 | init(ts_range:Range ) { 49 | self.init() 50 | self.location = ts_range.lowerBound 51 | self.length = ts_range.upperBound - ts_range.lowerBound 52 | } 53 | 54 | /** 55 | Init from Range 56 | 57 | - parameter ts_range: Range 58 | 59 | - returns: NSRange 60 | */ 61 | init(_ ts_range:Range ) { 62 | self.init() 63 | self.location = ts_range.lowerBound 64 | self.length = ts_range.upperBound - ts_range.lowerBound 65 | } 66 | 67 | 68 | /// Get NSRange start index 69 | var ts_startIndex:Int { get { return location } } 70 | 71 | /// Get NSRange end index 72 | var ts_endIndex:Int { get { return location + length } } 73 | 74 | /// Convert NSRange to Range 75 | var ts_asRange:Range { get { return location.. Bool { 88 | return index >= location && index < ts_endIndex 89 | } 90 | 91 | /** 92 | Get NSRange's clamp Index 93 | 94 | - parameter index: index 95 | 96 | - returns: Bool 97 | */ 98 | func ts_clamp(index:Int) -> Int { 99 | return max(self.ts_startIndex, min(self.ts_endIndex - 1, index)) 100 | } 101 | 102 | /** 103 | Check NSRange intersects another NSRange 104 | 105 | - parameter range: NSRange 106 | 107 | - returns: Bool 108 | */ 109 | func ts_intersects(range:NSRange) -> Bool { 110 | return NSIntersectionRange(self, range).ts_isEmpty == false 111 | } 112 | 113 | /** 114 | Get the two NSRange's intersection 115 | 116 | - parameter range: NSRange 117 | 118 | - returns: NSRange 119 | */ 120 | func ts_intersection(range:NSRange) -> NSRange { 121 | return NSIntersectionRange(self, range) 122 | } 123 | 124 | /** 125 | Get the two NSRange's union value 126 | 127 | - parameter range: NSRange 128 | 129 | - returns: NSRange 130 | */ 131 | func ts_union(range:NSRange) -> NSRange { 132 | return NSUnionRange(self, range) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /Sources/Foundation/NSString+TSExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+TSExtension.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/9/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | 12 | public extension NSString { 13 | /** 14 | Returns true if email is valid. This validation is checking string for matching regexp only. 15 | It will not check domain extensions. It's not guarantee you that it's a real email address also. 16 | 17 | - parameter email: your email 18 | 19 | - returns: Bool 20 | */ 21 | class func ts_isEmailValid(_ email: String) -> Bool { 22 | let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}" 23 | let emailTest = NSPredicate(format: "SELF MATCHES %@", emailRegEx) 24 | let result = emailTest.evaluate(with: email) 25 | 26 | return result 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/Foundation/NotificationCenter+TSBlock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSNotificationCenter+TSBlock.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 12/18/15. 7 | // Copyright © 2015 Hilen. All rights reserved. 8 | // 9 | 10 | //https://gist.github.com/brentdax/64845dc0b3fec0a27d87 11 | 12 | 13 | import Foundation 14 | 15 | public extension NotificationCenter { 16 | /** 17 | NSNotificationCenter with closure 18 | 19 | - parameter observer: observer 20 | - parameter aName: name 21 | - parameter anObject: object 22 | - parameter queue: queue 23 | - parameter handler: the handler 24 | 25 | - returns: AnyObject 26 | */ 27 | @discardableResult 28 | func ts_addObserver(_ observer: T, name aName: String?, object anObject: AnyObject?, queue: OperationQueue? = OperationQueue.main, handler: @escaping (_ observer: T, _ notification: Notification) -> Void) -> AnyObject { 29 | let observation = addObserver(forName: aName.map { NSNotification.Name(rawValue: $0) }, object: anObject, queue: queue) { [unowned observer] note in 30 | handler(observer, note) 31 | } 32 | 33 | TSObservationRemover(observation).makeRetainedBy(observer) 34 | 35 | return observation 36 | } 37 | } 38 | 39 | private class TSObservationRemover: NSObject { 40 | let observation: NSObjectProtocol 41 | 42 | init(_ obs: NSObjectProtocol) { 43 | observation = obs 44 | super.init() 45 | } 46 | 47 | func makeRetainedBy(_ owner: AnyObject) { 48 | ts_observationRemoversForObject(owner).add(self) 49 | } 50 | 51 | deinit { 52 | NotificationCenter.default.removeObserver(observation) 53 | } 54 | } 55 | 56 | private var TSObservationRemoverKey: UnsafeRawPointer? = nil 57 | 58 | private func ts_observationRemoversForObject(_ object: AnyObject) -> NSMutableArray { 59 | if TSObservationRemoverKey == nil { 60 | withUnsafePointer(to: &TSObservationRemoverKey) { pointer in 61 | TSObservationRemoverKey = UnsafeRawPointer(pointer) 62 | } 63 | } 64 | 65 | var retainedRemovers = objc_getAssociatedObject(object, TSObservationRemoverKey!) as! NSMutableArray? 66 | if retainedRemovers == nil { 67 | retainedRemovers = NSMutableArray() 68 | objc_setAssociatedObject(object, TSObservationRemoverKey!, retainedRemovers, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) 69 | } 70 | return retainedRemovers! 71 | } 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /Sources/Foundation/Timer+TSBlock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Timer+TSBlock.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/10/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | // https://github.com/radex/SwiftyTimer/blob/swift3/Sources/SwiftyTimer.swift 10 | 11 | import Foundation 12 | 13 | extension Timer { 14 | 15 | // MARK: Schedule timers 16 | 17 | /// Create and schedule a timer that will call `block` once after the specified time. 18 | 19 | @discardableResult 20 | public class func ts_after(_ interval: TimeInterval, _ block: @escaping () -> Void) -> Timer { 21 | let timer = Timer.ts_new(after: interval, block) 22 | timer.ts_start() 23 | return timer 24 | } 25 | 26 | /// Create and schedule a timer that will call `block` repeatedly in specified time intervals. 27 | 28 | @discardableResult 29 | public class func ts_every(_ interval: TimeInterval, _ block: @escaping () -> Void) -> Timer { 30 | let timer = Timer.ts_new(every: interval, block) 31 | timer.ts_start() 32 | return timer 33 | } 34 | 35 | /// Create and schedule a timer that will call `block` repeatedly in specified time intervals. 36 | /// (This variant also passes the timer instance to the block) 37 | 38 | @nonobjc @discardableResult 39 | public class func ts_every(_ interval: TimeInterval, _ block: @escaping (Timer) -> Void) -> Timer { 40 | let timer = Timer.ts_new(every: interval, block) 41 | timer.ts_start() 42 | return timer 43 | } 44 | 45 | // MARK: Create timers without scheduling 46 | 47 | /// Create a timer that will call `block` once after the specified time. 48 | /// 49 | /// - Note: The timer won't fire until it's scheduled on the run loop. 50 | /// Use `NSTimer.after` to create and schedule a timer in one step. 51 | /// - Note: The `new` class function is a workaround for a crashing bug when using convenience initializers (rdar://18720947) 52 | 53 | public class func ts_new(after interval: TimeInterval, _ block: @escaping () -> Void) -> Timer { 54 | return CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + interval, 0, 0, 0) { _ in 55 | block() 56 | } 57 | } 58 | 59 | /// Create a timer that will call `block` repeatedly in specified time intervals. 60 | /// 61 | /// - Note: The timer won't fire until it's scheduled on the run loop. 62 | /// Use `NSTimer.every` to create and schedule a timer in one step. 63 | /// - Note: The `new` class function is a workaround for a crashing bug when using convenience initializers (rdar://18720947) 64 | 65 | public class func ts_new(every interval: TimeInterval, _ block: @escaping () -> Void) -> Timer { 66 | return CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + interval, interval, 0, 0) { _ in 67 | block() 68 | } 69 | } 70 | 71 | /// Create a timer that will call `block` repeatedly in specified time intervals. 72 | /// (This variant also passes the timer instance to the block) 73 | /// 74 | /// - Note: The timer won't fire until it's scheduled on the run loop. 75 | /// Use `NSTimer.every` to create and schedule a timer in one step. 76 | /// - Note: The `new` class function is a workaround for a crashing bug when using convenience initializers (rdar://18720947) 77 | 78 | @nonobjc public class func ts_new(every interval: TimeInterval, _ block: @escaping (Timer) -> Void) -> Timer { 79 | var timer: Timer! 80 | timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + interval, interval, 0, 0) { _ in 81 | block(timer) 82 | } 83 | return timer 84 | } 85 | 86 | // MARK: Manual scheduling 87 | 88 | /// Schedule this timer on the run loop 89 | /// 90 | /// By default, the timer is scheduled on the current run loop for the default mode. 91 | /// Specify `runLoop` or `modes` to override these defaults. 92 | 93 | public func ts_start(runLoop: RunLoop = RunLoop.current, modes: RunLoop.Mode...) { 94 | let modes = modes.isEmpty ? [RunLoop.Mode.default] : modes 95 | 96 | for mode in modes { 97 | runLoop.add(self, forMode: mode) 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Sources/Foundation/URLRequest+TScURLCommand.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLRequest+TScURLCommand.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/10/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | 12 | extension URLRequest { 13 | fileprivate func escapeQuotesInString(_ string: String) -> String { 14 | assert(string.count > 0 , "Error: String is not valid") 15 | return string.replacingOccurrences(of: "\"", with:"\\\"", options: NSString.CompareOptions.literal, range: nil) 16 | } 17 | 18 | /** 19 | Sample : 20 | 21 | let URLString = "https://httpbin.org/get" 22 | let parameters = [ 23 | "foo":"bar" 24 | ] 25 | let request = Alamofire.request(.GET, URLString, parameters: parameters, encoding:.JSON).responseJSON { response in 26 | switch response.result { 27 | case .Success(let JSON): 28 | print("Success with JSON: \(JSON)") 29 | success!(JSON as! NSDictionary) 30 | case .Failure(let error): 31 | print("Request failed with error: \(error)") 32 | failure!(error) 33 | } 34 | } 35 | print("\n Request cURL command:\(request!.cURLCommandString()) \n") 36 | 37 | Step1:curl -k -X POST --dump-header - -H "Accept: application/json" -H "Content-Type: application/json" -d "{ \"password\" : \"pass\", \"username\" : \"test\"}" "http://httpbin.org/post" 38 | 39 | Step2: copy the the command and paste it into Terminal. 40 | 41 | - returns: cURL command string 42 | */ 43 | public func cURLCommandString() -> String! { 44 | let curlString: NSMutableString = NSMutableString(string:"curl -k -X \(self.httpMethod!) --dump-header -") 45 | 46 | if let allHTTPHeaderFields: [String : String] = self.allHTTPHeaderFields { 47 | for key: String in allHTTPHeaderFields.keys { 48 | let headerKey: String = self.escapeQuotesInString(key) 49 | let headerValue: String = self.escapeQuotesInString(allHTTPHeaderFields[key]!) 50 | curlString.appendFormat(" -H \"%@: %@\"", headerKey, headerValue) 51 | } 52 | } 53 | 54 | if let body: Data = self.httpBody , self.httpBody != nil { 55 | if var bodyDataString = String(data: body, encoding: String.Encoding.utf8) { 56 | if bodyDataString.count > 0 { 57 | bodyDataString = self.escapeQuotesInString(bodyDataString) 58 | curlString.appendFormat(" -d \"%@\"", bodyDataString) 59 | } 60 | } 61 | } 62 | curlString.appendFormat(" \"%@\"", self.url!.absoluteString) 63 | 64 | let trimmed = curlString.replacingOccurrences(of: "\n", with: "").trimmingCharacters(in: CharacterSet.newlines) 65 | return trimmed as String 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Sources/Foundation/UserDefaults+TSArchiveData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserDefault+TSArchiveData.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/9/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | 12 | public extension UserDefaults { 13 | /** 14 | Set Archive Data 15 | 16 | - parameter object: object 17 | - parameter key: key 18 | */ 19 | func ts_setArchiveData(_ object: T, forKey key: String) { 20 | let data = NSKeyedArchiver.archivedData(withRootObject: object) 21 | set(data, forKey: key) 22 | } 23 | 24 | /** 25 | Get Archive Data 26 | 27 | - parameter _: type 28 | - parameter key: key 29 | 30 | - returns: T 31 | */ 32 | func ts_archiveDataForKey(_: T.Type, key: String) -> T? { 33 | guard let data = object(forKey: key) as? Data else { return nil } 34 | guard let object = NSKeyedUnarchiver.unarchiveObject(with: data) as? T else { return nil } 35 | return object 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/Foundation/UserDefaults+TSExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserDefault+TSExtension.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/9/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | 12 | /// Shortcut for `UserDefaults.standard` 13 | /// 14 | /// **Pro-Tip:** If you want to use shared user defaults, just 15 | /// redefine this global shortcut in your app target, like so: 16 | /// ~~~ 17 | /// TSUserDefaults = UserDefaults(suiteName: "com.my.app")! 18 | /// ~~~ 19 | public var TSUserDefaults = UserDefaults.standard 20 | 21 | public extension UserDefaults { 22 | // MARK: - Getter 23 | /** 24 | Get object from NSUserDefaults 25 | 26 | - parameter key: key 27 | - parameter defaultValue: defaultValue, this can be nil 28 | 29 | - returns: AnyObject 30 | */ 31 | class func ts_objectForKey(_ key: String, defaultValue: AnyObject? = nil) -> AnyObject? { 32 | if (defaultValue != nil) && ts_objectForKey(key) == nil { 33 | return defaultValue 34 | } 35 | return TSUserDefaults.object(forKey: key) as AnyObject? 36 | } 37 | 38 | /** 39 | Get integer from NSUserDefaults 40 | 41 | - parameter key: key 42 | - parameter defaultValue: defaultValue, this can be nil 43 | 44 | - returns: AnyObject 45 | */ 46 | class func ts_integerForKey(_ key: String, defaultValue: Int? = nil) -> Int { 47 | if (defaultValue != nil) && ts_objectForKey(key) == nil { 48 | return defaultValue! 49 | } 50 | return TSUserDefaults.integer(forKey: key) 51 | } 52 | 53 | /** 54 | Get bool from NSUserDefaults 55 | 56 | - parameter key: key 57 | - parameter defaultValue: defaultValue, this can be nil 58 | 59 | - returns: AnyObject 60 | */ 61 | class func ts_boolForKey(_ key: String, defaultValue: Bool? = nil) -> Bool { 62 | if (defaultValue != nil) && ts_objectForKey(key) == nil { 63 | return defaultValue! 64 | } 65 | return TSUserDefaults.bool(forKey: key) 66 | } 67 | 68 | /** 69 | Get float from NSUserDefaults 70 | 71 | - parameter key: key 72 | - parameter defaultValue: defaultValue, this can be nil 73 | 74 | - returns: AnyObject 75 | */ 76 | class func ts_floatForKey(_ key: String, defaultValue: Float? = nil) -> Float { 77 | if (defaultValue != nil) && ts_objectForKey(key) == nil { 78 | return defaultValue! 79 | } 80 | return TSUserDefaults.float(forKey: key) 81 | } 82 | 83 | /** 84 | Get double from NSUserDefaults 85 | 86 | - parameter key: key 87 | - parameter defaultValue: defaultValue, this can be nil 88 | 89 | - returns: AnyObject 90 | */ 91 | class func ts_doubleForKey(_ key: String, defaultValue: Double? = nil) -> Double { 92 | if (defaultValue != nil) && ts_objectForKey(key) == nil { 93 | return defaultValue! 94 | } 95 | return TSUserDefaults.double(forKey: key) 96 | } 97 | 98 | /** 99 | Get string from NSUserDefaults 100 | 101 | - parameter key: key 102 | - parameter defaultValue: defaultValue, this can be nil 103 | 104 | - returns: AnyObject 105 | */ 106 | class func ts_stringForKey(_ key: String, defaultValue: String? = nil) -> String? { 107 | if (defaultValue != nil) && ts_objectForKey(key) == nil { 108 | return defaultValue! 109 | } 110 | return TSUserDefaults.string(forKey: key) 111 | } 112 | 113 | /** 114 | Get NSData from NSUserDefaults 115 | 116 | - parameter key: key 117 | - parameter defaultValue: defaultValue, this can be nil 118 | 119 | - returns: AnyObject 120 | */ 121 | class func ts_dataForKey(_ key: String, defaultValue: Data? = nil) -> Data? { 122 | if (defaultValue != nil) && ts_objectForKey(key) == nil { 123 | return defaultValue! 124 | } 125 | return TSUserDefaults.data(forKey: key) 126 | } 127 | 128 | /** 129 | Get NSURL from NSUserDefaults 130 | 131 | - parameter key: key 132 | - parameter defaultValue: defaultValue, this can be nil 133 | 134 | - returns: AnyObject 135 | */ 136 | class func ts_URLForKey(_ key: String, defaultValue: URL? = nil) -> URL? { 137 | if (defaultValue != nil) && ts_objectForKey(key) == nil { 138 | return defaultValue! 139 | } 140 | return TSUserDefaults.url(forKey: key) 141 | } 142 | 143 | /** 144 | Get array from NSUserDefaults 145 | 146 | - parameter key: key 147 | - parameter defaultValue: defaultValue, this can be nil 148 | 149 | - returns: AnyObject 150 | */ 151 | class func ts_arrayForKey(_ key: String, defaultValue: [AnyObject]? = nil) -> [AnyObject]? { 152 | if (defaultValue != nil) && ts_objectForKey(key) == nil { 153 | return defaultValue! 154 | } 155 | return TSUserDefaults.array(forKey: key) as [AnyObject]? 156 | } 157 | 158 | /** 159 | Get dictionary from NSUserDefaults 160 | 161 | - parameter key: key 162 | - parameter defaultValue: defaultValue, this can be nil 163 | 164 | - returns: AnyObject 165 | */ 166 | class func ts_dictionaryForKey(_ key: String, defaultValue: [String : AnyObject]? = nil) -> [String : AnyObject]? { 167 | if (defaultValue != nil) && ts_objectForKey(key) == nil { 168 | return defaultValue! 169 | } 170 | return TSUserDefaults.dictionary(forKey: key) as [String : AnyObject]? 171 | } 172 | 173 | // MARK: - Setter 174 | 175 | /** 176 | Set object for key 177 | 178 | - parameter key: key 179 | - parameter value: value 180 | */ 181 | class func ts_setObject(_ key: String, value: AnyObject?) { 182 | if value == nil { 183 | TSUserDefaults.removeObject(forKey: key) 184 | } else { 185 | TSUserDefaults.set(value, forKey: key) 186 | } 187 | TSUserDefaults.synchronize() 188 | } 189 | 190 | /** 191 | Set integer for key 192 | 193 | - parameter key: key 194 | - parameter value: value 195 | */ 196 | class func ts_setInteger(_ key: String, value: Int) { 197 | TSUserDefaults.set(value, forKey: key) 198 | TSUserDefaults.synchronize() 199 | } 200 | 201 | /** 202 | Set bool for key 203 | 204 | - parameter key: key 205 | - parameter value: value 206 | */ 207 | class func ts_setBool(_ key: String, value: Bool) { 208 | TSUserDefaults.set(value, forKey: key) 209 | TSUserDefaults.synchronize() 210 | } 211 | 212 | /** 213 | Set float for key 214 | 215 | - parameter key: key 216 | - parameter value: value 217 | */ 218 | class func ts_setFloat(_ key: String, value: Float) { 219 | TSUserDefaults.set(value, forKey: key) 220 | TSUserDefaults.synchronize() 221 | } 222 | 223 | /** 224 | Set string for key 225 | 226 | - parameter key: key 227 | - parameter value: value 228 | */ 229 | class func ts_setString(_ key: String, value: String?) { 230 | if (value == nil) { 231 | TSUserDefaults.removeObject(forKey: key) 232 | } else { 233 | TSUserDefaults.set(value, forKey: key) 234 | } 235 | TSUserDefaults.synchronize() 236 | } 237 | 238 | /** 239 | Set data for key 240 | 241 | - parameter key: key 242 | - parameter value: value 243 | */ 244 | class func ts_setData(_ key: String, value: Data) { 245 | self.ts_setObject(key, value: value as AnyObject?) 246 | } 247 | 248 | /** 249 | Set array for key 250 | 251 | - parameter key: key 252 | - parameter value: value 253 | */ 254 | class func ts_setArray(_ key: String, value: [AnyObject]) { 255 | self.ts_setObject(key, value: value as AnyObject?) 256 | } 257 | 258 | /** 259 | Set dictionary for key 260 | 261 | - parameter key: key 262 | - parameter value: value 263 | */ 264 | class func ts_setDictionary(_ key: String, value: [String : AnyObject]) { 265 | self.ts_setObject(key, value: value as AnyObject?) 266 | } 267 | } 268 | 269 | 270 | 271 | -------------------------------------------------------------------------------- /Sources/Struct/Array+TSExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array+TSExtension.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/9/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | 12 | extension Array { 13 | 14 | } -------------------------------------------------------------------------------- /Sources/Struct/CGSize+TSExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGSize+TSExtension.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/9/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import UIKit 11 | 12 | public extension CGSize { 13 | /** 14 | Aspect fit size 15 | 16 | - parameter boundingSize: boundingSize 17 | 18 | - returns: CGSize 19 | */ 20 | func ts_aspectFit(_ boundingSize: CGSize) -> CGSize { 21 | let minRatio = min(boundingSize.width / width, boundingSize.height / height) 22 | return CGSize(width: width*minRatio, height: height*minRatio) 23 | } 24 | 25 | /** 26 | Pixel size 27 | 28 | - returns: CGSize 29 | */ 30 | func ts_toPixel() -> CGSize { 31 | let scale = UIScreen.main.scale 32 | return CGSize(width: self.width * scale, height: self.height * scale) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/Struct/Dictionary+TSExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dictionary+TSExtension.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 11/23/15. 7 | // Copyright © 2015 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | 12 | public extension Dictionary { 13 | /** 14 | Merges the dictionary with dictionaries passed. The latter dictionaries will override 15 | values of the keys that are already set 16 | 17 | - parameter dictionaries: A comma seperated list of dictionaries 18 | */ 19 | mutating func ts_merge(_ dictionaries: Dictionary...) { 20 | for dict in dictionaries { 21 | for (key, value) in dict { 22 | self.updateValue(value as! Value, forKey: key as! Key) 23 | } 24 | } 25 | } 26 | 27 | /** 28 | Combine the two dictionary 29 | 30 | - parameter left: The left dictionary 31 | - parameter right: The right dictionary 32 | 33 | - returns: The new dictionary. 34 | */ 35 | func ts_combine(_ left: Dictionary, right: Dictionary) -> Dictionary { 36 | var map = Dictionary() 37 | for (k, v) in left { 38 | map[k] = v 39 | } 40 | for (k, v) in right { 41 | map[k] = v 42 | } 43 | return map 44 | } 45 | 46 | //https://github.com/pNre/ExSwift/blob/master/ExSwift/Dictionary.swift 47 | /** 48 | Creates an Array with values generated by running 49 | each [key: value] of self through the mapFunction. 50 | 51 | - parameter map: map Function 52 | 53 | - returns: Mapped array 54 | */ 55 | func ts_toArray(_ map: (Key, Value) -> V) -> [V] { 56 | var mapped = [V]() 57 | ts_each { 58 | mapped.append(map($0, $1)) 59 | } 60 | return mapped 61 | } 62 | 63 | /** 64 | Loops trough each [key: value] pair in self. 65 | 66 | - parameter each: Function to inovke on each loop 67 | */ 68 | func ts_each(_ each: (Key, Value) -> ()) { 69 | for (key, value) in self { 70 | each(key, value) 71 | } 72 | } 73 | 74 | /** 75 | Returns a copy of self, filtered to only have values for the whitelisted keys. 76 | 77 | - parameter keys: Whitelisted keys 78 | 79 | - returns: Filtered dictionary 80 | */ 81 | func ts_pick(_ keys: [Key]) -> Dictionary { 82 | return ts_filter { (key: Key, _) -> Bool in 83 | return keys.contains(key) 84 | } 85 | } 86 | 87 | /** 88 | Constructs a dictionary containing every [key: value] pair from self 89 | for which testFunction evaluates to true. 90 | 91 | - parameter test: Function called to test each key, value 92 | 93 | - returns: Filtered dictionary 94 | */ 95 | func ts_filter(_ test: (Key, Value) -> Bool) -> Dictionary { 96 | var result = Dictionary() 97 | for (key, value) in self { 98 | if test(key, value) { 99 | result[key] = value 100 | } 101 | } 102 | return result 103 | } 104 | } 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /Sources/Struct/Double+TSExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Double+TSExtension.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/10/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | 12 | // MARK: - Time extensions 13 | 14 | public extension Double { 15 | var ts_millisecond: TimeInterval { return self / 1000 } 16 | var ts_milliseconds: TimeInterval { return self / 1000 } 17 | var ts_ms: TimeInterval { return self / 1000 } 18 | 19 | var ts_second: TimeInterval { return self } 20 | var ts_seconds: TimeInterval { return self } 21 | 22 | var ts_minute: TimeInterval { return self * 60 } 23 | var ts_minutes: TimeInterval { return self * 60 } 24 | 25 | var ts_hour: TimeInterval { return self * 3600 } 26 | var ts_hours: TimeInterval { return self * 3600 } 27 | 28 | var ts_day: TimeInterval { return self * 3600 * 24 } 29 | var ts_days: TimeInterval { return self * 3600 * 24 } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/Struct/String+TSCrypto.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+TSCrypto.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 2/19/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | import CommonCrypto 12 | 13 | public extension String { 14 | var ts_MD5String: String { 15 | let data = Data(self.utf8) 16 | let hash = data.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) -> [UInt8] in 17 | var hash = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH)) 18 | CC_MD5(bytes.baseAddress, CC_LONG(data.count), &hash) 19 | return hash 20 | } 21 | return hash.map { String(format: "%02x", $0) }.joined() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/Struct/String+TSExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+TSExtension.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 1/19/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | import UIKit 12 | 13 | public extension String { 14 | /// String's length 15 | var ts_length: Int { 16 | return self.count 17 | } 18 | 19 | /** 20 | Calculate the size of string, and limit the width 21 | 22 | - parameter width: width 23 | - parameter font: font 24 | 25 | - returns: size value 26 | */ 27 | func ts_sizeWithConstrainedWidth(_ width: CGFloat, font: UIFont) -> CGSize { 28 | let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude) 29 | let size: CGSize = self.boundingRect( 30 | with: constraintRect, 31 | options: NSStringDrawingOptions.usesLineFragmentOrigin, 32 | attributes: [NSAttributedString.Key.font: font], 33 | context: nil 34 | ).size 35 | return size 36 | } 37 | 38 | /** 39 | Calculate the height of string, and limit the width 40 | 41 | - parameter width: width 42 | - parameter font: font 43 | 44 | - returns: height value 45 | */ 46 | func ts_heightWithConstrainedWidth(_ width: CGFloat, font: UIFont) -> CGFloat { 47 | let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude) 48 | let boundingBox = self.boundingRect( 49 | with: constraintRect, 50 | options: .usesLineFragmentOrigin, 51 | attributes: [NSAttributedString.Key.font: font], 52 | context: nil) 53 | return boundingBox.height 54 | } 55 | 56 | /** 57 | Calculate the width of string with current font size. 58 | 59 | - parameter font: font 60 | 61 | - returns: height value 62 | */ 63 | func ts_widthWithCurrentFont(_ font: UIFont) -> CGFloat { 64 | let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: font.pointSize) 65 | let boundingBox = self.boundingRect( 66 | with: constraintRect, 67 | options: .usesLineFragmentOrigin, 68 | attributes: [NSAttributedString.Key.font: font], 69 | context: nil) 70 | return boundingBox.width 71 | } 72 | 73 | /** 74 | NSRange to Range 75 | http://stackoverflow.com/questions/25138339/nsrange-to-rangestring-index 76 | 77 | - parameter range: Range 78 | 79 | - returns: NSRange 80 | */ 81 | 82 | func ts_NSRange(fromRange range: Range) -> NSRange { 83 | let from = range.lowerBound 84 | let to = range.upperBound 85 | 86 | let location = self.distance(from: startIndex, to: from) 87 | let length = self.distance(from: from, to: to) 88 | 89 | return NSRange(location: location, length: length) 90 | } 91 | 92 | /** 93 | Range to NSRange 94 | http://stackoverflow.com/questions/25138339/nsrange-to-rangestring-index 95 | 96 | - parameter nsRange: The NSRange 97 | 98 | - returns: Range 99 | */ 100 | func ts_Range(from nsRange: NSRange) -> Range? { 101 | guard 102 | let from16 = utf16.index(utf16.startIndex, offsetBy: nsRange.location, limitedBy: utf16.endIndex), 103 | let to16 = utf16.index(from16, offsetBy: nsRange.length, limitedBy: utf16.endIndex), 104 | let from = String.Index(from16, within: self), 105 | let to = String.Index(to16, within: self) 106 | else { return nil } 107 | return from ..< to 108 | } 109 | } 110 | 111 | 112 | -------------------------------------------------------------------------------- /Sources/TimedSilverHeader.h: -------------------------------------------------------------------------------- 1 | // 2 | // TimedSilverHeader.h 3 | // TimedSilver 4 | // 5 | // Created by Hilen on 8/11/16. 6 | // Copyright © 2016 Hilen. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for TimedSilver. 12 | FOUNDATION_EXPORT double TimedSilverVersionNumber; 13 | 14 | //! Project version string for TimedSilver. 15 | FOUNDATION_EXPORT const unsigned char TimedSilverVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | -------------------------------------------------------------------------------- /Sources/UIKit/UIAlertController+TSExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIAlertController+TSExtension.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/11/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import UIKit 11 | 12 | public extension UIAlertController { 13 | /** 14 | Single button alert 15 | 16 | - parameter title: title 17 | - parameter message: message 18 | - parameter buttonText: buttonText can be nil, but default is "OK" 19 | - parameter completion: completion 20 | 21 | - returns: UIAlertController 22 | */ 23 | @discardableResult 24 | class func ts_singleButtonAlertWithTitle( 25 | _ title: String, 26 | message: String, 27 | buttonText: String? = "OK", 28 | completion: (() -> Void)?) -> UIAlertController 29 | { 30 | let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) 31 | alert.addAction(UIAlertAction(title: buttonText, style: .default, handler: { 32 | (action:UIAlertAction!) -> Void in 33 | if let completion = completion { 34 | completion() 35 | } 36 | })) 37 | alert.ts_show() 38 | return alert 39 | } 40 | 41 | /** 42 | Two button alert 43 | 44 | - parameter title: title 45 | - parameter message: message 46 | - parameter buttonText: buttonText can be nil, but default is "OK" 47 | - parameter otherButtonTitle: otherButtonTitle 48 | - parameter completion: completion 49 | 50 | - returns: UIAlertController 51 | */ 52 | @discardableResult 53 | class func ts_doubleButtonAlertWithTitle( 54 | _ title: String, 55 | message: String, 56 | buttonText: String? = "OK", 57 | otherButtonTitle: String, 58 | completion: (() -> Void)?) -> UIAlertController 59 | { 60 | let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) 61 | alert.addAction(UIAlertAction(title: buttonText, style: .cancel, handler: nil)) 62 | alert.addAction(UIAlertAction(title: otherButtonTitle, style: .default, handler: { 63 | (action:UIAlertAction!) -> Void in 64 | if let completion = completion { 65 | completion() 66 | } 67 | })) 68 | alert.ts_show() 69 | return alert 70 | } 71 | 72 | /** 73 | Show UIAlertController 74 | */ 75 | func ts_show() { 76 | self.ts_present(true, completion: nil) 77 | } 78 | 79 | @available(iOS 8.0, *) 80 | func ts_present(_ animated: Bool, completion: (() -> Void)?) { 81 | guard let app = UIApplication.ts_sharedApplication() else { 82 | return 83 | } 84 | if let rootVC = app.keyWindow?.rootViewController { 85 | self.ts_presentFromController(rootVC, animated: animated, completion: completion) 86 | } 87 | } 88 | 89 | func ts_presentFromController(_ controller: UIViewController, animated: Bool, completion: (() -> Void)?) { 90 | if let navVC = controller as? UINavigationController, 91 | let visibleVC = navVC.visibleViewController { 92 | self.ts_presentFromController(visibleVC, animated: animated, completion: completion) 93 | } else { 94 | controller.present(self, animated: animated, completion: completion); 95 | } 96 | } 97 | 98 | func ts_dismiss() { 99 | 100 | } 101 | } 102 | 103 | -------------------------------------------------------------------------------- /Sources/UIKit/UIApplication+TSExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIApplication+TSExtension.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/10/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | import UIKit 12 | 13 | extension UIApplication { 14 | 15 | /// Avoid the error: [UIApplication sharedApplication] is unavailable in xxx extension 16 | /// 17 | /// - returns: UIApplication? 18 | public class func ts_sharedApplication() -> UIApplication? { 19 | let selector = NSSelectorFromString("sharedApplication") 20 | guard UIApplication.responds(to: selector) else { return nil } 21 | return UIApplication.perform(selector).takeUnretainedValue() as? UIApplication 22 | } 23 | 24 | ///Get screen orientation 25 | public class var ts_screenOrientation: UIInterfaceOrientation? { 26 | guard let app = self.ts_sharedApplication() else { 27 | return nil 28 | } 29 | return app.statusBarOrientation 30 | } 31 | 32 | ///Get status bar's height 33 | @available(iOS 8.0, *) 34 | public class var ts_screenStatusBarHeight: CGFloat { 35 | guard let app = UIApplication.ts_sharedApplication() else { 36 | return 0 37 | } 38 | return app.statusBarFrame.height 39 | } 40 | 41 | /** 42 | Run a block in background after app resigns activity 43 | 44 | - parameter closure: The closure 45 | - parameter expirationHandler: The expiration handler 46 | */ 47 | public func ts_runIntoBackground(_ closure: @escaping () -> Void, expirationHandler: (() -> Void)? = nil) { 48 | DispatchQueue.main.async { 49 | let taskID: UIBackgroundTaskIdentifier 50 | if let expirationHandler = expirationHandler { 51 | taskID = self.beginBackgroundTask(expirationHandler: expirationHandler) 52 | } else { 53 | taskID = self.beginBackgroundTask(expirationHandler: { }) 54 | } 55 | closure() 56 | self.endBackgroundTask(taskID) 57 | } 58 | } 59 | } 60 | 61 | -------------------------------------------------------------------------------- /Sources/UIKit/UIButton+TSExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIButton+TSExtension.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/4/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import UIKit 11 | 12 | public extension UIButton { 13 | /** 14 | Set UIButton's backgroundColor with a UIImage 15 | 16 | - parameter color: color 17 | - parameter forState: UIControlState 18 | */ 19 | func ts_setBackgroundColor(_ color: UIColor, forState: UIControl.State) { 20 | UIGraphicsBeginImageContext(CGSize(width: 1, height: 1)) 21 | UIGraphicsGetCurrentContext()?.setFillColor(color.cgColor) 22 | UIGraphicsGetCurrentContext()?.fill(CGRect(x: 0, y: 0, width: 1, height: 1)) 23 | let theImage = UIGraphicsGetImageFromCurrentImageContext() 24 | UIGraphicsEndImageContext() 25 | self.setBackgroundImage(theImage, for: forState) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/UIKit/UIButton+TSTouchArea.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIButton+TSTouchArea.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 9/22/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import UIKit 11 | 12 | private var ts_touchAreaEdgeInsets: UIEdgeInsets = .zero 13 | 14 | extension UIButton { 15 | /// Increase your button touch area. 16 | /// If your button frame is (0,0,40,40). Then call button.ts_touchInsets = UIEdgeInsetsMake(-30, -30, -30, -30), it will Increase the touch area 17 | public var ts_touchInsets: UIEdgeInsets { 18 | get { 19 | if let value = objc_getAssociatedObject(self, &ts_touchAreaEdgeInsets) as? NSValue { 20 | var edgeInsets: UIEdgeInsets = .zero 21 | value.getValue(&edgeInsets) 22 | return edgeInsets 23 | } 24 | else { 25 | return .zero 26 | } 27 | } 28 | set(newValue) { 29 | var newValueCopy = newValue 30 | let objCType = NSValue(uiEdgeInsets: .zero).objCType 31 | let value = NSValue(&newValueCopy, withObjCType: objCType) 32 | objc_setAssociatedObject(self, &ts_touchAreaEdgeInsets, value, .OBJC_ASSOCIATION_RETAIN) 33 | } 34 | } 35 | 36 | open override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { 37 | if self.ts_touchInsets == .zero || !self.isEnabled || self.isHidden { 38 | return super.point(inside: point, with: event) 39 | } 40 | 41 | let relativeFrame = self.bounds 42 | let hitFrame = relativeFrame.inset(by:self.ts_touchInsets)//UIEdgeInsetsInsetRect(relativeFrame, self.ts_touchInsets) 43 | return hitFrame.contains(point) 44 | } 45 | } 46 | 47 | 48 | -------------------------------------------------------------------------------- /Sources/UIKit/UICollectionView+TSExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionView+TSExtension.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/5/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | import UIKit 12 | 13 | public extension UICollectionView { 14 | /** 15 | Last indexPath in section 16 | 17 | - parameter section: section 18 | 19 | - returns: NSIndexPath 20 | */ 21 | func ts_lastIndexPathInSection(_ section: Int) -> IndexPath? { 22 | return IndexPath(row: self.numberOfItems(inSection: section)-1, section: section) 23 | } 24 | 25 | /// Last indexPath in UICollectionView 26 | var ts_lastIndexPath: IndexPath? { 27 | if (self.ts_totalItems - 1) > 0 { 28 | return IndexPath(row: self.ts_totalItems-1, section: self.numberOfSections) 29 | } else { 30 | return nil 31 | } 32 | } 33 | 34 | /// Total items in UICollectionView 35 | var ts_totalItems: Int { 36 | var i = 0 37 | var rowCount = 0 38 | while i < self.numberOfSections { 39 | rowCount += self.numberOfItems(inSection: i) 40 | i += 1 41 | } 42 | return rowCount 43 | } 44 | 45 | /** 46 | Scroll to the bottom 47 | 48 | - parameter animated: animated 49 | */ 50 | func ts_scrollToBottom(_ animated: Bool) { 51 | let section = self.numberOfSections - 1 52 | let row = self.numberOfItems(inSection: section) - 1 53 | if section < 0 || row < 0 { 54 | return 55 | } 56 | let path = IndexPath(row: row, section: section) 57 | let offset = contentOffset.y 58 | self.scrollToItem(at: path, at: .top, animated: animated) 59 | let delay = (animated ? 0.1 : 0.0) * Double(NSEC_PER_SEC) 60 | let time = DispatchTime.now() + Double(Int64(delay)) / Double(NSEC_PER_SEC) 61 | DispatchQueue.main.asyncAfter(deadline: time, execute: { () -> Void in 62 | if self.contentOffset.y != offset { 63 | self.ts_scrollToBottom(false) 64 | } 65 | }) 66 | } 67 | 68 | /** 69 | Reload data without flashing 70 | */ 71 | func ts_reloadWithoutFlashing() { 72 | UIView.setAnimationsEnabled(false) 73 | CATransaction.begin() 74 | CATransaction.setDisableActions(true) 75 | self.reloadData() 76 | CATransaction.commit() 77 | UIView.setAnimationsEnabled(true) 78 | } 79 | 80 | /** 81 | Fetch indexPaths of UICollectionView's visibleCells 82 | 83 | - returns: NSIndexPath Array 84 | */ 85 | func ts_visibleIndexPaths() -> [IndexPath] { 86 | var list = [IndexPath]() 87 | for cell in self.visibleCells { 88 | if let indexPath = self.indexPath(for: cell) { 89 | list.append(indexPath) 90 | } 91 | } 92 | return list 93 | } 94 | 95 | /** 96 | Fetch indexPaths of UICollectionView's rect 97 | 98 | - returns: NSIndexPath Array 99 | */ 100 | func ts_indexPathsForElementsInRect(_ rect: CGRect) -> [IndexPath] { 101 | let allLayoutAttributes = self.collectionViewLayout.layoutAttributesForElements(in: rect) 102 | if (allLayoutAttributes?.count ?? 0) == 0 {return []} 103 | var indexPaths: [IndexPath] = [] 104 | indexPaths.reserveCapacity(allLayoutAttributes!.count) 105 | for layoutAttributes in allLayoutAttributes! { 106 | let indexPath = layoutAttributes.indexPath 107 | indexPaths.append(indexPath) 108 | } 109 | return indexPaths 110 | } 111 | 112 | /** 113 | Reload data with completion block 114 | 115 | - parameter completion: completion block 116 | */ 117 | func ts_reloadData(_ completion: @escaping ()->()) { 118 | UIView.animate(withDuration: 0, animations: { self.reloadData() }, completion: { _ in completion() }) 119 | 120 | } 121 | } 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /Sources/UIKit/UICollectionView+TSGeneric.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionView+TSGeneric.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/22/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import UIKit 11 | 12 | public extension UICollectionView { 13 | 14 | // MARK: - Cell register and reuse 15 | /** 16 | Register cell nib 17 | 18 | - parameter aClass: class 19 | */ 20 | func ts_registerCellNib(_ aClass: T.Type) { 21 | let name = String(describing: aClass) 22 | let nib = UINib(nibName: name, bundle: nil) 23 | self.register(nib, forCellWithReuseIdentifier: name) 24 | } 25 | 26 | /** 27 | Register cell class 28 | 29 | - parameter aClass: class 30 | */ 31 | func ts_registerCellClass(_ aClass: T.Type) { 32 | let name = String(describing: aClass) 33 | self.register(aClass, forCellWithReuseIdentifier: name) 34 | } 35 | 36 | /** 37 | Dequeue reusable cell 38 | 39 | - parameter aClass: class 40 | - parameter indexPath: indexPath 41 | 42 | - returns: cell 43 | */ 44 | func ts_dequeueReusableCell(_ aClass: T.Type, forIndexPath indexPath: IndexPath) -> T! { 45 | let name = String(describing: aClass) 46 | guard let cell = dequeueReusableCell(withReuseIdentifier: name, for: indexPath) as? T else { 47 | fatalError("\(name) is not registed") 48 | } 49 | return cell 50 | } 51 | 52 | // MARK: - Header register and reuse 53 | /** 54 | Register header nib 55 | 56 | - parameter aClass: class 57 | */ 58 | func ts_registerHeaderNib(_ aClass: T.Type) { 59 | let name = String(describing: aClass) 60 | let nib = UINib(nibName: name, bundle: nil) 61 | self.register(nib, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: name) 62 | } 63 | 64 | /** 65 | Register header nib 66 | 67 | - parameter aClass: class 68 | */ 69 | func ts_registerHeaderClass(_ aClass: T.Type) { 70 | let name = String(describing: aClass) 71 | self.register(aClass, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: name) 72 | } 73 | 74 | /** 75 | Dequeue reusable header 76 | 77 | - parameter aClass: class 78 | - parameter indexPath: indexPath 79 | 80 | - returns: cell 81 | */ 82 | func ts_dequeueReusableHeader(_ aClass: T.Type, forIndexPath indexPath: IndexPath) -> T! { 83 | let name = String(describing: aClass) 84 | guard let view = dequeueReusableSupplementaryView( 85 | ofKind: UICollectionView.elementKindSectionHeader, 86 | withReuseIdentifier: name, 87 | for: indexPath) as? T else { 88 | fatalError("\(name) is not registed") 89 | } 90 | return view 91 | } 92 | 93 | // MARK: - Footer register and reuse 94 | /** 95 | Register header nib 96 | 97 | - parameter aClass: class 98 | */ 99 | func ts_registerFooterNib(_ aClass: T.Type) { 100 | let name = String(describing: aClass) 101 | let nib = UINib(nibName: name, bundle: nil) 102 | self.register(nib, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: name) 103 | } 104 | 105 | /** 106 | Register header nib 107 | 108 | - parameter aClass: class 109 | */ 110 | func ts_registerFooterClass(_ aClass: T.Type) { 111 | let name = String(describing: aClass) 112 | self.register(aClass, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: name) 113 | } 114 | 115 | /** 116 | Dequeue reusable header 117 | 118 | - parameter aClass: class 119 | - parameter indexPath: indexPath 120 | 121 | - returns: cell 122 | */ 123 | func ts_dequeueReusableFooter(_ aClass: T.Type, forIndexPath indexPath: IndexPath) -> T! { 124 | let name = String(describing: aClass) 125 | guard let view = dequeueReusableSupplementaryView( 126 | ofKind: UICollectionView.elementKindSectionFooter, 127 | withReuseIdentifier: name, 128 | for: indexPath) as? T else { 129 | fatalError("\(name) is not registed") 130 | } 131 | return view 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /Sources/UIKit/UIColor+TSExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+TSExtension.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/9/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import UIKit 11 | 12 | public extension UIColor { 13 | /** 14 | UIColor with hex string 15 | 16 | - parameter hexString: #000000 17 | - parameter alpha: alpha value 18 | 19 | - returns: UIColor 20 | */ 21 | convenience init(ts_hexString hexString: String, alpha: Double = 1.0) { 22 | let hex = hexString.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) 23 | var int = UInt32() 24 | Scanner(string: hex).scanHexInt32(&int) 25 | let r, g, b: UInt32 26 | switch hex.count { 27 | case 3: // RGB (12-bit) 28 | (r, g, b) = ((int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) 29 | case 6: // RGB (24-bit) 30 | (r, g, b) = (int >> 16, int >> 8 & 0xFF, int & 0xFF) 31 | default: 32 | (r, g, b) = (1, 1, 0) 33 | } 34 | self.init(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(255 * alpha) / 255) 35 | } 36 | 37 | /** 38 | UIColor with RGB 39 | 40 | - parameter red: red value 41 | - parameter green: green value 42 | - parameter blue: blue value 43 | 44 | - returns: UIColor 45 | */ 46 | convenience init(ts_red red: Int, green: Int, blue: Int) { 47 | assert(red >= 0 && red <= 255, "Invalid red component") 48 | assert(green >= 0 && green <= 255, "Invalid green component") 49 | assert(blue >= 0 && blue <= 255, "Invalid blue component") 50 | 51 | self.init(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: 1.0) 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /Sources/UIKit/UIControl+TSExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIControl+TSExtension.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/9/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | import UIKit 12 | import AVFoundation 13 | 14 | fileprivate class TSClosureWrapper : NSObject { 15 | let _callback : () -> Void 16 | init(callback : @escaping () -> Void) { 17 | _callback = callback 18 | } 19 | 20 | @objc func invoke() { 21 | _callback() 22 | } 23 | } 24 | 25 | fileprivate var AssociatedClosure: UInt8 = 0 26 | 27 | 28 | public extension UIControl { 29 | /** 30 | UIControl with closure callback 31 | 32 | - parameter events: UIControlEvents 33 | - parameter callback: callback 34 | */ 35 | func ts_addEventHandler(forControlEvent controlEvent: UIControl.Event, handler callback: @escaping () -> Void) { 36 | let wrapper = TSClosureWrapper(callback: callback) 37 | addTarget(wrapper, action:#selector(TSClosureWrapper.invoke), for: controlEvent) 38 | objc_setAssociatedObject(self, &AssociatedClosure, wrapper, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 39 | } 40 | } 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Sources/UIKit/UIControl+TSSound.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIControl+TSSound.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/10/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | import UIKit 12 | import AVFoundation 13 | 14 | extension UIControl { 15 | fileprivate struct AssociatedKeys { 16 | static var ts_soundKey = "ts_soundKey" 17 | } 18 | 19 | /** 20 | Add sound to UIControl 21 | 22 | - parameter name: music name 23 | - parameter controlEvent: controlEvent 24 | */ 25 | public func ts_addSoundName(_ name: String, forControlEvent controlEvent: UIControl.Event) { 26 | let oldSoundKey: String = "\(controlEvent)" 27 | let oldSound: AVAudioPlayer = self.ts_sounds[oldSoundKey]! 28 | let selector = NSSelectorFromString("play") 29 | self.removeTarget(oldSound, action: selector, for: controlEvent) 30 | do { 31 | try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category(rawValue: "AVAudioSessionCategoryAmbient")) 32 | // Find the sound file. 33 | guard let soundFileURL = Bundle.main.url(forResource: name, withExtension: "") else { 34 | assert(false, "File not exist") 35 | return 36 | } 37 | 38 | let tapSound: AVAudioPlayer = try! AVAudioPlayer(contentsOf: soundFileURL) 39 | let controlEventKey: String = "\(controlEvent)" 40 | var sounds: [AnyHashable: Any] = self.ts_sounds 41 | sounds[controlEventKey] = tapSound 42 | tapSound.prepareToPlay() 43 | self.addTarget(tapSound, action: selector, for: controlEvent) 44 | } 45 | catch _ {} 46 | } 47 | 48 | /// AssociatedObject for UIControl 49 | var ts_sounds: Dictionary { 50 | get { 51 | if let sounds = objc_getAssociatedObject(self, &AssociatedKeys.ts_soundKey) { 52 | return sounds as! Dictionary 53 | } 54 | 55 | var sounds = Dictionary() as Dictionary 56 | sounds = self.ts_sounds 57 | return sounds 58 | } 59 | set { objc_setAssociatedObject(self, &AssociatedKeys.ts_soundKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)} 60 | } 61 | } 62 | 63 | 64 | -------------------------------------------------------------------------------- /Sources/UIKit/UIDevice+TSExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIDevice+TSExtension.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/4/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | import UIKit 12 | import AdSupport 13 | 14 | public extension UIDevice { 15 | 16 | /// let modelName = UIDevice.currentDevice().ts_platform 17 | var ts_platform: String { 18 | var systemInfo = utsname() 19 | uname(&systemInfo) 20 | let machineMirror = Mirror(reflecting: systemInfo.machine) 21 | let identifier = machineMirror.children.reduce("") { identifier, element in 22 | guard let value = element.value as? Int8 , value != 0 else { return identifier } 23 | return identifier + String(UnicodeScalar(UInt8(value))) 24 | } 25 | 26 | switch identifier { 27 | //iPhone 28 | case "iPhone1,1": return "iPhone 1G" 29 | case "iPhone1,2": return "iPhone 3G" 30 | case "iPhone2,1": return "iPhone 3GS" 31 | case "iPhone3,1": return "iPhone 4 (GSM)" 32 | case "iPhone3,3": return "iPhone 4 (CDMA)" 33 | case "iPhone4,1": return "iPhone 4S" 34 | case "iPhone5,1": return "iPhone 5 (GSM)" 35 | case "iPhone5,2": return "iPhone 5 (CDMA)" 36 | case "iPhone5,3": return "iPhone 5c" 37 | case "iPhone5,4": return "iPhone 5c" 38 | case "iPhone6,1": return "iPhone 5s" 39 | case "iPhone6,2": return "iPhone 5s" 40 | case "iPhone7,1": return "iPhone 6 Plus" 41 | case "iPhone7,2": return "iPhone 6" 42 | case "iPhone8,1": return "iPhone 6s" 43 | case "iPhone8,2": return "iPhone 6s Plus" 44 | case "iPhone8,4": return "iPhone SE" 45 | case "iPhone9,1": return "iPhone 7" 46 | case "iPhone9,2": return "iPhone 7 Plus" 47 | //iPod 48 | case "iPod1,1": return "iPod Touch 1G" 49 | case "iPod2,1": return "iPod Touch 2G" 50 | case "iPod3,1": return "iPod Touch 3G" 51 | case "iPod4,1": return "iPod Touch 4G" 52 | case "iPod5,1": return "iPod Touch 5G" 53 | case "iPod7,1": return "iPod Touch 6G" 54 | //iPad 55 | case "iPad1,1": return "iPad" 56 | case "iPad2,1": return "iPad 2 (WiFi)" 57 | case "iPad2,2": return "iPad 2 (GSM)" 58 | case "iPad2,3": return "iPad 2 (CDMA)" 59 | case "iPad2,4": return "iPad 2 (WiFi)" 60 | case "iPad2,5": return "iPad Mini (WiFi)" 61 | case "iPad2,6": return "iPad Mini (GSM)" 62 | case "iPad2,7": return "iPad Mini (CDMA)" 63 | case "iPad3,1": return "iPad 3 (WiFi)" 64 | case "iPad3,2": return "iPad 3 (CDMA)" 65 | case "iPad3,3": return "iPad 3 (GSM)" 66 | case "iPad3,4": return "iPad 4 (WiFi)" 67 | case "iPad3,5": return "iPad 4 (GSM)" 68 | case "iPad3,6": return "iPad 4 (CDMA)" 69 | case "iPad4,1": return "iPad Air (WiFi)" 70 | case "iPad4,2": return "iPad Air (GSM)" 71 | case "iPad4,3": return "iPad Air (CDMA)" 72 | case "iPad4,4": return "iPad Mini Retina (WiFi)" 73 | case "iPad4,5": return "iPad Mini Retina (Cellular)" 74 | case "iPad4,7": return "iPad Mini 3 (WiFi)" 75 | case "iPad4,8": return "iPad Mini 3 (Cellular)" 76 | case "iPad4,9": return "iPad Mini 3 (Cellular)" 77 | case "iPad5,1": return "iPad Mini 4 (WiFi)" 78 | case "iPad5,2": return "iPad Mini 4 (Cellular)" 79 | case "iPad5,3": return "iPad Air 2 (WiFi)" 80 | case "iPad5,4": return "iPad Air 2 (Cellular)" 81 | case "iPad6,3": return "iPad Pro 9.7-inch (WiFi)" 82 | case "iPad6,4": return "iPad Pro 9.7-inch (Cellular)" 83 | case "iPad6,7": return "iPad Pro 12.9-inch (WiFi)" 84 | case "iPad6,8": return "iPad Pro 12.9-inch (Cellular)" 85 | //AppleTV 86 | case "AppleTV5,3": return "Apple TV" 87 | //Simulator 88 | case "i386", "x86_64": return "Simulator" 89 | default: return identifier 90 | } 91 | } 92 | 93 | /// UUID 94 | var ts_UUIDString: String { 95 | return UIDevice.current.identifierForVendor!.uuidString 96 | } 97 | 98 | /// Disk total 99 | var ts_totalDiskSpaceInBytes: Int64 { 100 | do { 101 | let systemAttributes = try FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String) 102 | let space = (systemAttributes[FileAttributeKey.systemSize] as? NSNumber)?.int64Value 103 | return space! 104 | } catch { 105 | return 0 106 | } 107 | } 108 | 109 | /// Disk free 110 | var ts_freeDiskSpaceInBytes: Int64 { 111 | do { 112 | let systemAttributes = try FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String) 113 | let freeSpace = (systemAttributes[FileAttributeKey.systemFreeSize] as? NSNumber)?.int64Value 114 | return freeSpace! 115 | } catch { 116 | return 0 117 | } 118 | } 119 | 120 | /// Disk used 121 | var ts_usedDiskSpaceInBytes: Int64 { 122 | let usedSpace = ts_totalDiskSpaceInBytes - ts_freeDiskSpaceInBytes 123 | return usedSpace 124 | } 125 | 126 | /// IDFA 127 | var ts_IDFA: String { 128 | return ASIdentifierManager.shared().advertisingIdentifier.uuidString 129 | } 130 | 131 | /// IDFV 132 | var ts_IDFV: String { 133 | return UIDevice.current.identifierForVendor!.uuidString 134 | } 135 | 136 | } 137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /Sources/UIKit/UIDevice+TSType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIDevice+TSType.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/9/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import UIKit 11 | 12 | public extension UIDevice { 13 | // MARK: - The iOS screen size 14 | enum ts_DeviceMaxWidth: Float { 15 | case iPhone4 = 480.0 16 | case iPhone5 = 568.0 17 | case iPhone6 = 667.0 18 | case iPhone6Plus = 736.0 19 | case iPad = 1024.0 20 | case iPadPro = 1366.0 21 | } 22 | 23 | enum ts_DeviceType: String { 24 | case iPhone 25 | case iPhone4 26 | case iPhone5 27 | case iPhone6 28 | case iPhone6Plus 29 | case iPad 30 | case iPadPro 31 | case iTV 32 | case Unknown 33 | } 34 | 35 | class func ts_maxDeviceWidth() -> Float { 36 | let w = Float(UIScreen.main.bounds.width) 37 | let h = Float(UIScreen.main.bounds.height) 38 | return fmax(w, h) 39 | } 40 | 41 | /** 42 | Get device type 43 | 44 | - returns: enum ts_DeviceType 45 | */ 46 | class func ts_deviceType() -> ts_DeviceType { 47 | if ts_isPhone4() { return ts_DeviceType.iPhone4 } 48 | if ts_isPhone5() { return ts_DeviceType.iPhone5 } 49 | if ts_isPhone6() { return ts_DeviceType.iPhone6 } 50 | if ts_isPhone6Plus() { return ts_DeviceType.iPhone6Plus } 51 | if ts_isPadPro() { return ts_DeviceType.iPadPro } 52 | if ts_isPad() { return ts_DeviceType.iPad } 53 | if ts_isPhone() { return ts_DeviceType.iPhone } 54 | if #available(iOS 9.0, *) { 55 | if ts_isTV() { return ts_DeviceType.iTV } 56 | } 57 | return ts_DeviceType.Unknown 58 | } 59 | 60 | /** 61 | Device is iPhone ? 62 | 63 | - returns: Bool 64 | */ 65 | class func ts_isPhone() -> Bool { 66 | return UIDevice.current.userInterfaceIdiom == .phone 67 | } 68 | 69 | /** 70 | Device is iPad ? 71 | 72 | - returns: Bool 73 | */ 74 | class func ts_isPad() -> Bool { 75 | return UIDevice.current.userInterfaceIdiom == .pad 76 | } 77 | 78 | /** 79 | Device is iPadPro ? 80 | 81 | - returns: Bool 82 | */ 83 | class func ts_isPadPro() -> Bool { 84 | return ts_isPad() && ts_maxDeviceWidth() == ts_DeviceMaxWidth.iPadPro.rawValue 85 | } 86 | 87 | /** 88 | Device is AppleTV ? 89 | 90 | - returns: Bool 91 | */ 92 | @available(iOS 9, *) 93 | class func ts_isTV() -> Bool { 94 | return UIDevice.current.userInterfaceIdiom == .tv 95 | } 96 | 97 | /** 98 | Device < iPhone4 99 | 100 | - returns: Bool 101 | */ 102 | class func ts_isPhone4Earlier() -> Bool { 103 | return ts_isPhone() && ts_maxDeviceWidth() < ts_DeviceMaxWidth.iPhone4.rawValue 104 | } 105 | 106 | /** 107 | Device == iPhone4 108 | 109 | - returns: Bool 110 | */ 111 | class func ts_isPhone4() -> Bool { 112 | return ts_isPhone() && ts_maxDeviceWidth() == ts_DeviceMaxWidth.iPhone4.rawValue 113 | } 114 | 115 | /** 116 | Device >= iPhone4 117 | 118 | - returns: Bool 119 | */ 120 | class func ts_isPhone4Later() -> Bool { 121 | return ts_isPhone() && ts_maxDeviceWidth() >= ts_DeviceMaxWidth.iPhone4.rawValue 122 | } 123 | 124 | /** 125 | Device < iPhone5 126 | 127 | - returns: Bool 128 | */ 129 | class func ts_isPhone5Earlier() -> Bool { 130 | return ts_isPhone() && ts_maxDeviceWidth() < ts_DeviceMaxWidth.iPhone5.rawValue 131 | } 132 | 133 | /** 134 | Device == iPhone5 135 | 136 | - returns: Bool 137 | */ 138 | class func ts_isPhone5() -> Bool { 139 | return ts_isPhone() && ts_maxDeviceWidth() == ts_DeviceMaxWidth.iPhone5.rawValue 140 | } 141 | 142 | /** 143 | Device >= iPhone5 144 | 145 | - returns: Bool 146 | */ 147 | class func ts_isPhone5Later() -> Bool { 148 | return ts_isPhone() && ts_maxDeviceWidth() >= ts_DeviceMaxWidth.iPhone5.rawValue 149 | } 150 | 151 | /** 152 | Device < iPhone6 153 | 154 | - returns: Bool 155 | */ 156 | class func ts_isPhone6Earlier() -> Bool { 157 | return ts_isPhone() && ts_maxDeviceWidth() < ts_DeviceMaxWidth.iPhone6.rawValue 158 | } 159 | 160 | /** 161 | Device == iPhone6 162 | 163 | - returns: Bool 164 | */ 165 | class func ts_isPhone6() -> Bool { 166 | return ts_isPhone() && ts_maxDeviceWidth() == ts_DeviceMaxWidth.iPhone6.rawValue 167 | } 168 | 169 | /** 170 | Device >= iPhone6 171 | 172 | - returns: Bool 173 | */ 174 | class func ts_isPhone6Later() -> Bool { 175 | return ts_isPhone() && ts_maxDeviceWidth() >= ts_DeviceMaxWidth.iPhone6.rawValue 176 | } 177 | 178 | /** 179 | Device < iPhone 6 Plus 180 | 181 | - returns: Bool 182 | */ 183 | class func ts_isPhone6PlusEarlier() -> Bool { 184 | return ts_isPhone() && ts_maxDeviceWidth() < ts_DeviceMaxWidth.iPhone6Plus.rawValue 185 | } 186 | 187 | /** 188 | Device == iPhone 6 Plus 189 | 190 | - returns: Bool 191 | */ 192 | class func ts_isPhone6Plus() -> Bool { 193 | return ts_isPhone() && ts_maxDeviceWidth() == ts_DeviceMaxWidth.iPhone6Plus.rawValue 194 | } 195 | 196 | /** 197 | Device >= iPhone 6 Plus 198 | 199 | - returns: Bool 200 | */ 201 | class func ts_isPhone6PlusLater() -> Bool { 202 | return ts_isPhone() && ts_maxDeviceWidth() >= ts_DeviceMaxWidth.iPhone6Plus.rawValue 203 | } 204 | 205 | // MARK: - The iOS system version 206 | 207 | enum ts_iOSType: Float { 208 | case iOS7 = 7.0 209 | case iOS8 = 8.0 210 | case iOS9 = 9.0 211 | case unknown = 0.0 212 | } 213 | 214 | /// Device's system version 215 | class var ts_systemVersion: Float { 216 | struct Singleton { 217 | static let version = (UIDevice.current.systemVersion as NSString).floatValue 218 | } 219 | return Singleton.version 220 | } 221 | 222 | /** 223 | Device's system version type 224 | 225 | - returns: ts_iOSType 226 | */ 227 | class func ts_systemType() -> ts_iOSType { 228 | if ts_iOS7() { return ts_iOSType.iOS7 } 229 | if ts_iOS8() { return ts_iOSType.iOS8 } 230 | if ts_iOS9() { return ts_iOSType.iOS9 } 231 | return ts_iOSType.unknown 232 | } 233 | 234 | /** 235 | Device's system version is iOS7 ? 236 | 237 | - returns: Bool 238 | */ 239 | class func ts_iOS7() -> Bool { 240 | return ts_isPhone() && ts_systemVersion == ts_iOSType.iOS7.rawValue 241 | } 242 | 243 | /** 244 | Device's system version is iOS8 ? 245 | 246 | - returns: Bool 247 | */ 248 | class func ts_iOS8() -> Bool { 249 | return ts_isPhone() && ts_systemVersion == ts_iOSType.iOS8.rawValue 250 | } 251 | 252 | /** 253 | Device's system version is iOS9 ? 254 | 255 | - returns: Bool 256 | */ 257 | class func ts_iOS9() -> Bool { 258 | return ts_isPhone() && ts_systemVersion == ts_iOSType.iOS9.rawValue 259 | } 260 | } 261 | 262 | -------------------------------------------------------------------------------- /Sources/UIKit/UIImage+TSExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+TSExtension.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 11/24/15. 7 | // Copyright © 2015 Hilen. All rights reserved. 8 | // 9 | 10 | //https://github.com/melvitax/AFImageHelper/blob/master/AFImageHelper/AFImageExtension.swift 11 | 12 | import Foundation 13 | import UIKit 14 | import Photos 15 | import QuartzCore 16 | import CoreGraphics 17 | import Accelerate 18 | 19 | public extension UIImage { 20 | /** 21 | Applies gradient color overlay to an image. 22 | 23 | - parameter gradientColors: An array of colors to use for the gradient 24 | - parameter blendMode: The blending type to use 25 | 26 | - returns: new image 27 | */ 28 | func ts_gradientImageWithColors(_ gradientColors: [UIColor], blendMode: CGBlendMode = CGBlendMode.normal) -> UIImage { 29 | UIGraphicsBeginImageContextWithOptions(size, false, scale) 30 | let context = UIGraphicsGetCurrentContext() 31 | context?.translateBy(x: 0, y: size.height) 32 | context?.scaleBy(x: 1.0, y: -1.0) 33 | context?.setBlendMode(blendMode) 34 | let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height) 35 | context?.draw(self.cgImage!, in: rect) 36 | // Create gradient 37 | let colorSpace = CGColorSpaceCreateDeviceRGB() 38 | let colors = gradientColors.map {(color: UIColor) -> AnyObject? in return color.cgColor as AnyObject? } as NSArray 39 | let gradient = CGGradient(colorsSpace: colorSpace, colors: colors, locations: nil) 40 | // Apply gradient 41 | context?.clip(to: rect, mask: self.cgImage!) 42 | context?.drawLinearGradient(gradient!, start: CGPoint(x: 0, y: 0), end: CGPoint(x: 0, y: size.height), options: CGGradientDrawingOptions(rawValue: 0)) 43 | let image = UIGraphicsGetImageFromCurrentImageContext() 44 | UIGraphicsEndImageContext(); 45 | return image!; 46 | } 47 | 48 | 49 | /** 50 | Capture screen 51 | 52 | - parameter view: view 53 | - parameter rect: rect 54 | 55 | - returns: UIImage 56 | */ 57 | @discardableResult 58 | class func ts_screenCaptureWithView(_ view: UIView, rect: CGRect) -> UIImage { 59 | let capture: UIImage 60 | UIGraphicsBeginImageContextWithOptions(rect.size, false, 1.0) 61 | let context: CGContext = UIGraphicsGetCurrentContext()! 62 | context.translateBy(x: -rect.origin.x, y: -rect.origin.y) 63 | // let layer: CALayer = view.layer 64 | let selector = NSSelectorFromString("drawViewHierarchyInRect:afterScreenUpdates:") 65 | if view.responds(to: selector) { 66 | //if view.responds(to, selector(UIView.drawViewHierarchyInRect(_:afterScreenUpdates:))) { 67 | view.drawHierarchy(in: view.frame, afterScreenUpdates: true) 68 | } else { 69 | view.layer.render(in: UIGraphicsGetCurrentContext()!) 70 | } 71 | capture = UIGraphicsGetImageFromCurrentImageContext()! 72 | UIGraphicsEndImageContext() 73 | return capture 74 | } 75 | 76 | /** 77 | Create UIImage with the color 78 | 79 | - parameter color: color 80 | 81 | - returns: UIImage 82 | */ 83 | class func ts_imageWithColor(_ color: UIColor) -> UIImage { 84 | let rect = CGRect(x: 0, y: 0, width: 1.0, height: 1.0) 85 | UIGraphicsBeginImageContext(rect.size) 86 | let context = UIGraphicsGetCurrentContext() 87 | context?.setFillColor(color.cgColor) 88 | context?.fill(rect) 89 | let image = UIGraphicsGetImageFromCurrentImageContext() 90 | UIGraphicsEndImageContext() 91 | 92 | return image! 93 | } 94 | 95 | /** 96 | The Round corner radius 97 | 98 | - parameter cornerRadius: value 99 | 100 | - returns: UIImage 101 | */ 102 | func ts_roundWithCornerRadius(_ cornerRadius: CGFloat) -> UIImage { 103 | let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: self.size) 104 | UIGraphicsBeginImageContextWithOptions(self.size, false, 1) 105 | UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).addClip() 106 | draw(in: rect) 107 | return UIGraphicsGetImageFromCurrentImageContext()! 108 | } 109 | 110 | /** 111 | Check the image has alpha or not 112 | 113 | - returns: Bool 114 | */ 115 | func ts_hasAlpha() -> Bool { 116 | let alpha: CGImageAlphaInfo = self.cgImage!.alphaInfo 117 | return (alpha == .first || alpha == .last || alpha == .premultipliedFirst || alpha == .premultipliedLast) 118 | } 119 | 120 | /** 121 | Returns a copy of the given image, adding an alpha channel if it doesn't already have one. 122 | 123 | - returns: a new image 124 | */ 125 | func ts_applyAlpha() -> UIImage? { 126 | if ts_hasAlpha() { 127 | return self 128 | } 129 | 130 | let imageRef = self.cgImage; 131 | let width = imageRef?.width; 132 | let height = imageRef?.height; 133 | let colorSpace = imageRef?.colorSpace 134 | 135 | // The bitsPerComponent and bitmapInfo values are hard-coded to prevent an "unsupported parameter combination" error 136 | let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo().rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue) 137 | let offscreenContext = CGContext(data: nil, width: width!, height: height!, bitsPerComponent: 8, bytesPerRow: 0, space: colorSpace!, bitmapInfo: bitmapInfo.rawValue) 138 | 139 | // Draw the image into the context and retrieve the new image, which will now have an alpha layer 140 | offscreenContext?.draw(imageRef!, in: CGRect(x: 0, y: 0, width: CGFloat(width!), height: CGFloat(height!))) 141 | let imageWithAlpha = UIImage(cgImage: (offscreenContext?.makeImage()!)!) 142 | return imageWithAlpha 143 | } 144 | 145 | 146 | /** 147 | Creates a new image with a border. 148 | 149 | - parameter border: The size of the border. 150 | - parameter color: The color of the border. 151 | 152 | - returns: a new image 153 | */ 154 | func ts_applyBorder(_ border:CGFloat, color:UIColor) -> UIImage? { 155 | UIGraphicsBeginImageContextWithOptions(size, false, 0) 156 | let width = self.cgImage?.width 157 | let height = self.cgImage?.height 158 | let bits = self.cgImage?.bitsPerComponent 159 | let colorSpace = self.cgImage?.colorSpace 160 | let bitmapInfo = self.cgImage?.bitmapInfo 161 | let context = CGContext(data: nil, width: width!, height: height!, bitsPerComponent: bits!, bytesPerRow: 0, space: colorSpace!, bitmapInfo: (bitmapInfo?.rawValue)!) 162 | var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0 163 | color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) 164 | context?.setStrokeColor(red: red, green: green, blue: blue, alpha: alpha) 165 | context?.setLineWidth(border) 166 | let rect = CGRect(x: 0, y: 0, width: size.width*scale, height: size.height*scale) 167 | let inset = rect.insetBy(dx: border*scale, dy: border*scale) 168 | context?.strokeEllipse(in: inset) 169 | context?.draw(self.cgImage!, in: inset) 170 | let image = UIImage(cgImage: (context?.makeImage()!)!) 171 | UIGraphicsEndImageContext() 172 | return image 173 | } 174 | 175 | /** 176 | Applies a blur to an image based on the specified radius, tint color saturation and mask image 177 | 178 | - parameter blurRadius: The radius of the blur. 179 | - parameter tintColor: The optional tint color. 180 | - parameter saturationDeltaFactor: The detla for saturation. 181 | - parameter maskImage: The optional image for masking. 182 | 183 | - returns: New image or nil 184 | */ 185 | func ts_applyBlur(_ blurRadius:CGFloat, tintColor:UIColor?, saturationDeltaFactor:CGFloat, maskImage:UIImage? = nil) -> UIImage? { 186 | guard size.width > 0 && size.height > 0 && cgImage != nil else { 187 | return nil 188 | } 189 | if maskImage != nil { 190 | guard maskImage?.cgImage != nil else { 191 | return nil 192 | } 193 | } 194 | let imageRect = CGRect(origin: CGPoint.zero, size: size) 195 | var effectImage = self 196 | let hasBlur = blurRadius > CGFloat(Float.ulpOfOne) 197 | let hasSaturationChange = abs(saturationDeltaFactor - 1.0) > CGFloat(Float.ulpOfOne) 198 | if (hasBlur || hasSaturationChange) { 199 | 200 | UIGraphicsBeginImageContextWithOptions(size, false, 0.0) 201 | let effectInContext = UIGraphicsGetCurrentContext() 202 | effectInContext?.scaleBy(x: 1.0, y: -1.0) 203 | effectInContext?.translateBy(x: 0, y: -size.height) 204 | effectInContext?.draw(cgImage!, in: imageRect) 205 | 206 | var effectInBuffer = vImage_Buffer( 207 | data: effectInContext?.data, 208 | height: UInt((effectInContext?.height)!), 209 | width: UInt((effectInContext?.width)!), 210 | rowBytes: (effectInContext?.bytesPerRow)!) 211 | 212 | UIGraphicsBeginImageContextWithOptions(size, false, 0.0); 213 | let effectOutContext = UIGraphicsGetCurrentContext() 214 | 215 | var effectOutBuffer = vImage_Buffer( 216 | data: effectOutContext?.data, 217 | height: UInt((effectOutContext?.height)!), 218 | width: UInt((effectOutContext?.width)!), 219 | rowBytes: (effectOutContext?.bytesPerRow)!) 220 | 221 | if hasBlur { 222 | let inputRadius = blurRadius * UIScreen.main.scale 223 | let sqrtValue = CGFloat(sqrt(2.0 * Double.pi)) 224 | var radius = UInt32(floor(inputRadius * 3.0 * sqrtValue / 4.0 + 0.5)) 225 | if radius % 2 != 1 { 226 | radius += 1 // force radius to be odd so that the three box-blur methodology works. 227 | } 228 | let imageEdgeExtendFlags = vImage_Flags(kvImageEdgeExtend) 229 | vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, nil, 0, 0, radius, radius, nil, imageEdgeExtendFlags) 230 | vImageBoxConvolve_ARGB8888(&effectOutBuffer, &effectInBuffer, nil, 0, 0, radius, radius, nil, imageEdgeExtendFlags) 231 | vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, nil, 0, 0, radius, radius, nil, imageEdgeExtendFlags) 232 | } 233 | 234 | var effectImageBuffersAreSwapped = false 235 | 236 | if hasSaturationChange { 237 | let s: CGFloat = saturationDeltaFactor 238 | let floatingPointSaturationMatrix: [CGFloat] = [ 239 | 0.0722 + 0.9278 * s, 0.0722 - 0.0722 * s, 0.0722 - 0.0722 * s, 0, 240 | 0.7152 - 0.7152 * s, 0.7152 + 0.2848 * s, 0.7152 - 0.7152 * s, 0, 241 | 0.2126 - 0.2126 * s, 0.2126 - 0.2126 * s, 0.2126 + 0.7873 * s, 0, 242 | 0, 0, 0, 1 243 | ] 244 | 245 | let divisor: CGFloat = 256 246 | let matrixSize = floatingPointSaturationMatrix.count 247 | var saturationMatrix = [Int16](repeating: 0, count: matrixSize) 248 | 249 | for i: Int in 0 ..< matrixSize { 250 | saturationMatrix[i] = Int16(round(floatingPointSaturationMatrix[i] * divisor)) 251 | } 252 | 253 | if hasBlur { 254 | vImageMatrixMultiply_ARGB8888(&effectOutBuffer, &effectInBuffer, saturationMatrix, Int32(divisor), nil, nil, vImage_Flags(kvImageNoFlags)) 255 | effectImageBuffersAreSwapped = true 256 | } else { 257 | vImageMatrixMultiply_ARGB8888(&effectInBuffer, &effectOutBuffer, saturationMatrix, Int32(divisor), nil, nil, vImage_Flags(kvImageNoFlags)) 258 | } 259 | } 260 | 261 | if !effectImageBuffersAreSwapped { 262 | effectImage = UIGraphicsGetImageFromCurrentImageContext()! 263 | } 264 | 265 | UIGraphicsEndImageContext() 266 | 267 | if effectImageBuffersAreSwapped { 268 | effectImage = UIGraphicsGetImageFromCurrentImageContext()! 269 | } 270 | 271 | UIGraphicsEndImageContext() 272 | } 273 | 274 | // Set up output context. 275 | UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale) 276 | let outputContext = UIGraphicsGetCurrentContext() 277 | outputContext?.scaleBy(x: 1.0, y: -1.0) 278 | outputContext?.translateBy(x: 0, y: -size.height) 279 | 280 | // Draw base image. 281 | outputContext?.draw(self.cgImage!, in: imageRect) 282 | 283 | // Draw effect image. 284 | if hasBlur { 285 | outputContext?.saveGState() 286 | if let image = maskImage { 287 | outputContext?.clip(to: imageRect, mask: image.cgImage!); 288 | } 289 | outputContext?.draw(effectImage.cgImage!, in: imageRect) 290 | outputContext?.restoreGState() 291 | } 292 | 293 | // Add in color tint. 294 | if let color = tintColor { 295 | outputContext?.saveGState() 296 | outputContext?.setFillColor(color.cgColor) 297 | outputContext?.fill(imageRect) 298 | outputContext?.restoreGState() 299 | } 300 | 301 | // Output image is ready. 302 | let outputImage = UIGraphicsGetImageFromCurrentImageContext() 303 | UIGraphicsEndImageContext() 304 | 305 | return outputImage 306 | 307 | } 308 | } 309 | 310 | -------------------------------------------------------------------------------- /Sources/UIKit/UIImage+TSLaunchImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+TSLaunchImage.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/9/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import UIKit 11 | 12 | public extension UIImage { 13 | /** 14 | LaunchImage 15 | 16 | - returns: UIImage 17 | */ 18 | class func ts_launchImage() -> UIImage { 19 | func name() -> String { 20 | switch UIScreen.main.bounds.height { 21 | case 480: return "LaunchImage-700" 22 | case 568: return "LaunchImage-700-568h" 23 | case 667: return "LaunchImage-800-667h" 24 | case 736: return "LaunchImage-800-Portrait-736h" 25 | default: 26 | abort() 27 | } 28 | } 29 | return UIImage(named: name()) ?? UIImage() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/UIKit/UIImage+TSOrientation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Orientation.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 2/17/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | import UIKit 12 | import QuartzCore 13 | import CoreGraphics 14 | import Accelerate 15 | 16 | //https://github.com/cosnovae/fixUIImageOrientation/blob/master/fixImageOrientation.swift 17 | 18 | public extension UIImage { 19 | /** 20 | Fix the image's orientation 21 | 22 | - parameter src: the source image 23 | 24 | - returns: new image 25 | */ 26 | class func ts_fixImageOrientation(_ src:UIImage) -> UIImage { 27 | 28 | if src.imageOrientation == UIImage.Orientation.up { 29 | return src 30 | } 31 | 32 | var transform: CGAffineTransform = CGAffineTransform.identity 33 | 34 | switch src.imageOrientation { 35 | case UIImageOrientation.down, UIImageOrientation.downMirrored: 36 | transform = transform.translatedBy(x: src.size.width, y: src.size.height) 37 | transform = transform.rotated(by: CGFloat(Double.pi)) 38 | break 39 | case UIImageOrientation.left, UIImageOrientation.leftMirrored: 40 | transform = transform.translatedBy(x: src.size.width, y: 0) 41 | transform = transform.rotated(by: CGFloat(Double.pi)) 42 | break 43 | case UIImageOrientation.right, UIImageOrientation.rightMirrored: 44 | transform = transform.translatedBy(x: 0, y: src.size.height) 45 | transform = transform.rotated(by: CGFloat(-Double.pi)) 46 | break 47 | case UIImageOrientation.up, UIImageOrientation.upMirrored: 48 | break 49 | @unknown default: 50 | break 51 | } 52 | 53 | switch src.imageOrientation { 54 | case UIImageOrientation.upMirrored, UIImageOrientation.downMirrored: 55 | transform.translatedBy(x: src.size.width, y: 0) 56 | transform.scaledBy(x: -1, y: 1) 57 | break 58 | case UIImageOrientation.leftMirrored, UIImageOrientation.rightMirrored: 59 | transform.translatedBy(x: src.size.height, y: 0) 60 | transform.scaledBy(x: -1, y: 1) 61 | case UIImageOrientation.up, UIImageOrientation.down, UIImageOrientation.left, UIImageOrientation.right: 62 | break 63 | @unknown default: 64 | break 65 | } 66 | 67 | let ctx:CGContext = CGContext(data: nil, width: Int(src.size.width), height: Int(src.size.height), bitsPerComponent: src.cgImage!.bitsPerComponent, bytesPerRow: 0, space: src.cgImage!.colorSpace!, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)! 68 | 69 | ctx.concatenate(transform) 70 | 71 | switch src.imageOrientation { 72 | case UIImageOrientation.left, UIImageOrientation.leftMirrored, UIImageOrientation.right, UIImageOrientation.rightMirrored: 73 | ctx.draw(src.cgImage!, in: CGRect(x: 0, y: 0, width: src.size.height, height: src.size.width)) 74 | break 75 | default: 76 | ctx.draw(src.cgImage!, in: CGRect(x: 0, y: 0, width: src.size.width, height: src.size.height)) 77 | break 78 | } 79 | 80 | let cgimage:CGImage = ctx.makeImage()! 81 | let image:UIImage = UIImage(cgImage: cgimage) 82 | 83 | return image 84 | } 85 | } 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /Sources/UIKit/UIImage+TSResize.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+TSResize.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/5/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | import UIKit 12 | import QuartzCore 13 | import CoreGraphics 14 | import Accelerate 15 | 16 | extension UIImage { 17 | //https://github.com/melvitax/AFImageHelper/blob/master/AFImageHelper%2FAFImageExtension.swift 18 | public enum ts_UIImageContentMode { 19 | case scaleToFill, scaleAspectFit, scaleAspectFill 20 | } 21 | 22 | /** 23 | Creates a resized copy of an image. 24 | 25 | - Parameter size: The new size of the image. 26 | - Parameter contentMode: The way to handle the content in the new size. 27 | - Parameter quality: The image quality 28 | 29 | - Returns A new image 30 | */ 31 | public func ts_resize(_ size:CGSize, contentMode: ts_UIImageContentMode = .scaleToFill, quality: CGInterpolationQuality = .medium) -> UIImage? { 32 | let horizontalRatio = size.width / self.size.width; 33 | let verticalRatio = size.height / self.size.height; 34 | var ratio: CGFloat! 35 | 36 | switch contentMode { 37 | case .scaleToFill: 38 | ratio = 1 39 | case .scaleAspectFill: 40 | ratio = max(horizontalRatio, verticalRatio) 41 | case .scaleAspectFit: 42 | ratio = min(horizontalRatio, verticalRatio) 43 | } 44 | 45 | let rect = CGRect(x: 0, y: 0, width: size.width * ratio, height: size.height * ratio) 46 | 47 | // Fix for a colorspace / transparency issue that affects some types of 48 | // images. See here: http://vocaro.com/trevor/blog/2009/10/12/resize-a-uiimage-the-right-way/comment-page-2/#comment-39951 49 | 50 | let colorSpace = CGColorSpaceCreateDeviceRGB() 51 | let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue) 52 | let context = CGContext(data: nil, width: Int(rect.size.width), height: Int(rect.size.height), bitsPerComponent: 8, bytesPerRow: 0, space: colorSpace, bitmapInfo: bitmapInfo.rawValue) 53 | 54 | let transform = CGAffineTransform.identity 55 | 56 | // Rotate and/or flip the image if required by its orientation 57 | context?.concatenate(transform); 58 | 59 | // Set the quality level to use when rescaling 60 | context!.interpolationQuality = quality 61 | 62 | 63 | //CGContextSetInterpolationQuality(context, CGInterpolationQuality(kCGInterpolationHigh.value)) 64 | 65 | // Draw into the context; this scales the image 66 | context?.draw(self.cgImage!, in: rect) 67 | 68 | // Get the resized image from the context and a UIImage 69 | let newImage = UIImage(cgImage: (context?.makeImage()!)!, scale: self.scale, orientation: self.imageOrientation) 70 | return newImage; 71 | } 72 | 73 | public func ts_crop(_ bounds: CGRect) -> UIImage? { 74 | return UIImage(cgImage: (self.cgImage?.cropping(to: bounds)!)!, scale: 0.0, orientation: self.imageOrientation) 75 | } 76 | 77 | public func ts_cropToSquare() -> UIImage? { 78 | let size = CGSize(width: self.size.width * self.scale, height: self.size.height * self.scale) 79 | let shortest = min(size.width, size.height) 80 | let left: CGFloat = size.width > shortest ? (size.width-shortest)/2 : 0 81 | let top: CGFloat = size.height > shortest ? (size.height-shortest)/2 : 0 82 | let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height) 83 | let insetRect = rect.insetBy(dx: left, dy: top) 84 | return ts_crop(insetRect) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Sources/UIKit/UIImage+TSRoundedCorner.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+TSRoundedCorner.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/5/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | import UIKit 12 | import QuartzCore 13 | import CoreGraphics 14 | import Accelerate 15 | 16 | public extension UIImage { 17 | /** 18 | Creates a new image with rounded corners. 19 | 20 | - parameter cornerRadius: The corner radius 21 | 22 | - returns: a new image 23 | */ 24 | func ts_roundCorners(_ cornerRadius:CGFloat) -> UIImage? { 25 | // If the image does not have an alpha layer, add one 26 | let imageWithAlpha = ts_applyAlpha() 27 | if imageWithAlpha == nil { 28 | return nil 29 | } 30 | 31 | UIGraphicsBeginImageContextWithOptions(size, false, 0) 32 | let width = imageWithAlpha?.cgImage?.width 33 | let height = imageWithAlpha?.cgImage?.height 34 | let bits = imageWithAlpha?.cgImage?.bitsPerComponent 35 | let colorSpace = imageWithAlpha?.cgImage?.colorSpace 36 | let bitmapInfo = imageWithAlpha?.cgImage?.bitmapInfo 37 | let context = CGContext(data: nil, width: width!, height: height!, bitsPerComponent: bits!, bytesPerRow: 0, space: colorSpace!, bitmapInfo: (bitmapInfo?.rawValue)!) 38 | let rect = CGRect(x: 0, y: 0, width: CGFloat(width!)*scale, height: CGFloat(height!)*scale) 39 | 40 | context?.beginPath() 41 | if (cornerRadius == 0) { 42 | context?.addRect(rect) 43 | } else { 44 | context?.saveGState() 45 | context?.translateBy(x: rect.minX, y: rect.minY) 46 | context?.scaleBy(x: cornerRadius, y: cornerRadius) 47 | let fw = rect.size.width / cornerRadius 48 | let fh = rect.size.height / cornerRadius 49 | context?.move(to: CGPoint(x: fw, y: fh/2)) 50 | context?.addArc(tangent1End: CGPoint.init(x: fw, y: fh), tangent2End: CGPoint.init(x: fw/2, y: fh), radius: 1) 51 | context?.addArc(tangent1End: CGPoint.init(x: 0, y: fh), tangent2End: CGPoint.init(x: 0, y: fh/2), radius: 1) 52 | context?.addArc(tangent1End: CGPoint.init(x: 0, y: 0), tangent2End: CGPoint.init(x: fw/2, y: 0), radius: 1) 53 | context?.addArc(tangent1End: CGPoint.init(x: fw, y: 0), tangent2End: CGPoint.init(x: fw, y: fh/2), radius: 1) 54 | context?.restoreGState() 55 | } 56 | context?.closePath() 57 | context?.clip() 58 | 59 | context?.draw((imageWithAlpha?.cgImage)!, in: rect) 60 | let image = UIImage(cgImage: (context?.makeImage()!)!, scale:scale, orientation: .up) 61 | UIGraphicsEndImageContext() 62 | return image 63 | } 64 | 65 | } 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /Sources/UIKit/UILabel+TSExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UILabel+Extension.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 1/19/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | import UIKit 12 | 13 | public extension UILabel { 14 | /** 15 | The content size of UILabel 16 | 17 | - returns: CGSize 18 | */ 19 | func ts_contentSize() -> CGSize { 20 | let paragraphStyle = NSMutableParagraphStyle() 21 | paragraphStyle.lineBreakMode = self.lineBreakMode 22 | paragraphStyle.alignment = self.textAlignment 23 | let attributes: [NSAttributedString.Key : AnyObject] = [ 24 | .font: self.font, 25 | .paragraphStyle: paragraphStyle] 26 | let contentSize: CGSize = self.text!.boundingRect( 27 | with: self.frame.size, 28 | options: ([.usesLineFragmentOrigin, .usesFontLeading]), 29 | attributes: attributes, 30 | context: nil 31 | ).size 32 | return contentSize 33 | } 34 | 35 | /** 36 | Set UILabel's frame with the string, and limit the width. 37 | 38 | - parameter string: text 39 | - parameter width: your limit width 40 | */ 41 | func ts_setFrameWithString(_ string: String, width: CGFloat) { 42 | self.numberOfLines = 0 43 | let attributes: [NSAttributedString.Key : AnyObject] = [ 44 | .font: self.font, 45 | ] 46 | let resultSize: CGSize = string.boundingRect( 47 | with: CGSize(width: width, height: CGFloat.greatestFiniteMagnitude), 48 | options: NSStringDrawingOptions.usesLineFragmentOrigin, 49 | attributes: attributes, 50 | context: nil 51 | ).size 52 | let resultHeight: CGFloat = resultSize.height 53 | let resultWidth: CGFloat = resultSize.width 54 | var frame: CGRect = self.frame 55 | frame.size.height = resultHeight 56 | frame.size.width = resultWidth 57 | self.frame = frame 58 | } 59 | } 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /Sources/UIKit/UINavigationController+TSExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UINavigationController+TSExtension.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/10/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import UIKit 11 | 12 | extension UINavigationController { 13 | /** 14 | Push with UIViewAnimationTransition 15 | 16 | - parameter controller: target viewController 17 | - parameter transition: UIViewAnimationTransition 18 | */ 19 | public func ts_pushViewController(_ controller: UIViewController, transition: UIView.AnimationTransition) { 20 | UIView.beginAnimations(nil, context: nil) 21 | self.pushViewController(controller, animated: false) 22 | UIView.setAnimationDuration(0.5) 23 | UIView.setAnimationBeginsFromCurrentState(true) 24 | UIView.setAnimationTransition(transition, for: self.view, cache: true) 25 | UIView.commitAnimations() 26 | } 27 | 28 | /** 29 | Pop with UIViewAnimationTransition 30 | 31 | - parameter controller: target viewController 32 | - parameter transition: UIViewAnimationTransition 33 | 34 | - returns: UIViewController 35 | */ 36 | public func ts_popViewController(_ controller: UIViewController, transition: UIView.AnimationTransition) -> UIViewController { 37 | UIView.beginAnimations(nil, context: nil) 38 | let controller = self.popViewController(animated: false) 39 | UIView.setAnimationDuration(0.5) 40 | UIView.setAnimationBeginsFromCurrentState(true) 41 | UIView.setAnimationTransition(transition, for: self.view, cache: true) 42 | UIView.commitAnimations() 43 | return controller! 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/UIKit/UINavigationItem+TSExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UINavigationItem+TSExtension.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/4/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import UIKit 11 | 12 | public extension UINavigationItem { 13 | /** 14 | UINavigationItem's button position 15 | 16 | - LeftItem: Left 17 | - RightItem: Right 18 | */ 19 | enum ts_ButtonItemPosition { 20 | case leftItem, rightItem 21 | } 22 | 23 | /** 24 | Custom UINavigationItem with a button 25 | 26 | - parameter button: Your button, the class method 'ts_itemButton' can return a button with normal image or text. And you can also create your own button. 27 | - parameter position: Control the item's position, left or right 28 | - parameter action: Handler 29 | */ 30 | 31 | func ts_buttonItemAction( 32 | _ button: UIButton, 33 | position: ts_ButtonItemPosition, 34 | action:@escaping () -> Void) 35 | { 36 | button.ts_addEventHandler(forControlEvent: .touchUpInside, handler: { 37 | action() 38 | }) 39 | let barButton = UIBarButtonItem(customView: button) 40 | let gapItem = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil) 41 | gapItem.width = -6 //fix the space 42 | 43 | switch position { 44 | case .leftItem: 45 | button.contentHorizontalAlignment = .left 46 | self.leftBarButtonItems = [gapItem, barButton] 47 | case .rightItem: 48 | button.contentHorizontalAlignment = .right 49 | self.rightBarButtonItems = [gapItem, barButton] 50 | } 51 | } 52 | 53 | /** 54 | Create an item button. 55 | 56 | - parameter image: Button image, 20*20 is perfect one 57 | - parameter text: Button text 58 | 59 | - returns: Item button 60 | */ 61 | class func ts_itemButton(_ image: UIImage? = nil, text: String? = nil) -> UIButton { 62 | let button: UIButton = UIButton(type: UIButton.ButtonType.custom) 63 | if let aImage = image { 64 | button.setImage(aImage, for: UIControl.State()) 65 | } 66 | 67 | var buttonWidth: CGFloat = 40 68 | if let aText = text { 69 | button.titleLabel?.font = UIFont.systemFont(ofSize: 16) 70 | let attributes: [NSAttributedString.Key : AnyObject] = [ 71 | .font: button.titleLabel!.font, 72 | ] 73 | let size: CGSize = aText.boundingRect( 74 | with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: 30), 75 | options: NSStringDrawingOptions.usesLineFragmentOrigin, 76 | attributes: attributes, 77 | context: nil 78 | ).size 79 | 80 | buttonWidth = size.width 81 | button.setTitle(aText, for: UIControl.State()) 82 | } 83 | button.imageView!.contentMode = .scaleAspectFit; 84 | button.frame = CGRect(x: 0, y: 0, width: buttonWidth, height: 30) 85 | return button 86 | } 87 | 88 | /** 89 | Control leftItem enable 90 | 91 | - parameter enabled: enabled 92 | */ 93 | func ts_enableLeftItem(_ enabled: Bool ) { 94 | guard let barItems = self.leftBarButtonItems , barItems.count > 0 else { 95 | return 96 | } 97 | barItems.forEach({ obj in 98 | if obj.isKind(of: UIBarButtonItem.self) || obj.isMember(of: UIBarButtonItem.self) { 99 | obj.isEnabled = !enabled 100 | } 101 | }) 102 | } 103 | 104 | /** 105 | Control rightItem enable 106 | 107 | - parameter enabled: enabled 108 | */ 109 | func ts_enableRightItem(_ enabled: Bool ) { 110 | guard let barItems = self.rightBarButtonItems , barItems.count > 0 else { 111 | return 112 | } 113 | barItems.forEach({ obj in 114 | if obj.isKind(of: UIBarButtonItem.self) || obj.isMember(of: UIBarButtonItem.self) { 115 | obj.isEnabled = !enabled 116 | } 117 | }) 118 | } 119 | 120 | } 121 | 122 | 123 | -------------------------------------------------------------------------------- /Sources/UIKit/UIScreen+TSExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIScreen+TSExtension.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 11/3/15. 7 | // Copyright © 2015 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | import UIKit 12 | 13 | public extension UIScreen { 14 | /// The screen size 15 | class var ts_size: CGSize { 16 | return UIScreen.main.bounds.size 17 | } 18 | 19 | /// The screen's width 20 | class var ts_width: CGFloat { 21 | return UIScreen.main.bounds.size.width 22 | } 23 | 24 | /// The screen's height 25 | class var ts_height: CGFloat { 26 | return UIScreen.main.bounds.size.height 27 | } 28 | 29 | /// The screen's orientation size 30 | @available(iOS 8.0, *) 31 | class var ts_orientationSize: CGSize { 32 | guard let app = UIApplication.ts_sharedApplication() else { 33 | return CGSize.zero 34 | } 35 | let systemVersion = (UIDevice.current.systemVersion as NSString).floatValue 36 | let isLand: Bool = app.statusBarOrientation.isLandscape 37 | return (systemVersion > 8.0 && isLand) ? UIScreen.ts_swapSize(self.ts_size) : self.ts_size 38 | } 39 | 40 | /// The screen's orientation width 41 | class var ts_orientationWidth: CGFloat { 42 | return self.ts_orientationSize.width 43 | } 44 | 45 | /// The screen's orientation height 46 | class var ts_orientationHeight: CGFloat { 47 | return self.ts_orientationSize.height 48 | } 49 | 50 | /// The screen's DPI size 51 | class var ts_DPISize: CGSize { 52 | let size: CGSize = UIScreen.main.bounds.size 53 | let scale: CGFloat = UIScreen.main.scale 54 | return CGSize(width: size.width * scale, height: size.height * scale) 55 | } 56 | 57 | /** 58 | Swap size 59 | 60 | - parameter size: The target size 61 | 62 | - returns: CGSize 63 | */ 64 | class func ts_swapSize(_ size: CGSize) -> CGSize { 65 | return CGSize(width: size.height, height: size.width) 66 | } 67 | 68 | /// The screen's height without status bar's height 69 | @available(iOS 8.0, *) 70 | class var ts_screenHeightWithoutStatusBar: CGFloat { 71 | guard let app = UIApplication.ts_sharedApplication() else { 72 | return 0 73 | } 74 | 75 | if app.statusBarOrientation.isLandscape { 76 | return UIScreen.main.bounds.size.height - app.statusBarFrame.height 77 | } else { 78 | return UIScreen.main.bounds.size.width - app.statusBarFrame.height 79 | } 80 | } 81 | } 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /Sources/UIKit/UIScrollView+TSExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIScrollView+TSExtension.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/5/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | import UIKit 12 | 13 | extension UIScrollView { 14 | fileprivate struct AssociatedKeys { 15 | static var kKeyScrollViewVerticalIndicator = "_verticalScrollIndicator" 16 | static var kKeyScrollViewHorizontalIndicator = "_horizontalScrollIndicator" 17 | } 18 | 19 | /// YES if the scrollView's offset is at the very top. 20 | public var ts_isAtTop: Bool { 21 | get { return self.contentOffset.y == 0.0 ? true : false } 22 | } 23 | 24 | /// YES if the scrollView's offset is at the very bottom. 25 | public var ts_isAtBottom: Bool { 26 | get { 27 | let bottomOffset = self.contentSize.height - self.bounds.size.height 28 | return self.contentOffset.y == bottomOffset ? true : false 29 | } 30 | } 31 | 32 | /// YES if the scrollView can scroll from it's current offset position to the bottom. 33 | public var ts_canScrollToBottom: Bool { 34 | get { return self.contentSize.height > self.bounds.size.height ? true : false } 35 | } 36 | 37 | /// The vertical scroll indicator view. 38 | public var ts_verticalScroller: UIView { 39 | get { 40 | if (objc_getAssociatedObject(self, #function) == nil) { 41 | objc_setAssociatedObject(self, #function, self.ts_safeValueForKey(AssociatedKeys.kKeyScrollViewVerticalIndicator), objc_AssociationPolicy.OBJC_ASSOCIATION_ASSIGN); 42 | } 43 | return objc_getAssociatedObject(self, #function) as! UIView 44 | } 45 | } 46 | 47 | /// The horizontal scroll indicator view. 48 | public var ts_horizontalScroller: UIView { 49 | get { 50 | if (objc_getAssociatedObject(self, #function) == nil) { 51 | objc_setAssociatedObject(self, #function, self.ts_safeValueForKey(AssociatedKeys.kKeyScrollViewHorizontalIndicator), objc_AssociationPolicy.OBJC_ASSOCIATION_ASSIGN); 52 | } 53 | return objc_getAssociatedObject(self, #function) as! UIView 54 | } 55 | } 56 | 57 | fileprivate func ts_safeValueForKey(_ key: String) -> AnyObject{ 58 | let instanceVariable: Ivar = class_getInstanceVariable(type(of: self), key.cString(using: String.Encoding.utf8)!)! 59 | return object_getIvar(self, instanceVariable) as AnyObject; 60 | } 61 | 62 | 63 | /** 64 | Sets the content offset to the top. 65 | 66 | - parameter animated: animated YES to animate the transition at a constant velocity to the new offset, NO to make the transition immediate. 67 | */ 68 | public func ts_scrollToTopAnimated(_ animated: Bool) { 69 | if !self.ts_isAtTop { 70 | let bottomOffset = CGPoint.zero; 71 | self.setContentOffset(bottomOffset, animated: animated) 72 | } 73 | } 74 | 75 | /** 76 | Sets the content offset to the bottom. 77 | 78 | - parameter animated: animated YES to animate the transition at a constant velocity to the new offset, NO to make the transition immediate. 79 | */ 80 | public func ts_scrollToBottomAnimated(_ animated: Bool) { 81 | if self.ts_canScrollToBottom && !self.ts_isAtBottom { 82 | let bottomOffset = CGPoint(x: 0.0, y: self.contentSize.height - self.bounds.size.height) 83 | self.setContentOffset(bottomOffset, animated: animated) 84 | } 85 | } 86 | 87 | /** 88 | Stops scrolling, if it was scrolling. 89 | */ 90 | public func ts_stopScrolling() { 91 | guard self.isDragging else { 92 | return 93 | } 94 | var offset = self.contentOffset 95 | offset.y -= 1.0 96 | self.setContentOffset(offset, animated: false) 97 | 98 | offset.y += 1.0 99 | self.setContentOffset(offset, animated: false) 100 | } 101 | } 102 | 103 | 104 | -------------------------------------------------------------------------------- /Sources/UIKit/UIScrollView+TSPage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIScrollView+TSPage.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/10/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import UIKit 11 | 12 | extension UIScrollView { 13 | /// All pages 14 | public var ts_pages: Int { 15 | get { 16 | let pages = Int(self.contentSize.width / self.frame.size.width); 17 | return pages 18 | } 19 | } 20 | 21 | /// The current page 22 | public var ts_currentPage:Int { 23 | get { 24 | let pages = Float(self.contentSize.width / self.frame.size.width); 25 | let scrollPercent = Float(self.ts_scrollPercent) 26 | let currentPage = roundf((pages-1)*scrollPercent) 27 | return Int(currentPage) 28 | } 29 | } 30 | 31 | /// UIScrollView scroll percent 32 | public var ts_scrollPercent: CGFloat { 33 | get { 34 | let width = self.contentSize.width - self.frame.size.width 35 | let scrollPercent = self.contentOffset.x / width 36 | return scrollPercent 37 | } 38 | } 39 | 40 | /** 41 | Set the horizontal UIScrollView to specified page 42 | 43 | - parameter page: Page number 44 | - parameter animated: Animated 45 | */ 46 | public func ts_setPageX(_ page: Int, animated: Bool? = nil) { 47 | let pageWidth = self.frame.size.width; 48 | let offsetY = self.contentOffset.y; 49 | let offsetX = CGFloat(page) * pageWidth; 50 | let offset = CGPoint(x: offsetX, y: offsetY); 51 | if animated == nil { 52 | self.setContentOffset(offset, animated: false) 53 | } else { 54 | self.setContentOffset(offset, animated: animated!) 55 | } 56 | } 57 | 58 | /** 59 | Set the vertical UIScrollView to specified page 60 | 61 | - parameter page: Page number 62 | - parameter animated: Animated 63 | */ 64 | public func ts_setPageY(_ page: Int, animated: Bool? = nil) { 65 | let pageHeight = self.frame.size.height; 66 | let offsetX = self.contentOffset.x; 67 | let offsetY = CGFloat(page) * pageHeight; 68 | let offset = CGPoint(x: offsetX, y: offsetY); 69 | if animated == nil { 70 | self.setContentOffset(offset, animated: false) 71 | } else { 72 | self.setContentOffset(offset, animated: animated!) 73 | } 74 | } 75 | } 76 | 77 | 78 | -------------------------------------------------------------------------------- /Sources/UIKit/UISearchBar+TSExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UISearchBar+TSExtension.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 16/1/5. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | import UIKit 12 | 13 | public extension UISearchBar { 14 | /// Get the canlcel button 15 | var ts_cancelButton: UIButton { 16 | get { 17 | var button = UIButton() 18 | for view in self.subviews { 19 | for subView in view.subviews { 20 | if subView.isKind(of: UIButton.self) { 21 | button = subView as! UIButton 22 | return button 23 | } 24 | } 25 | } 26 | return button 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/UIKit/UITableView+TSExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITableView+TSExtension.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/5/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | import UIKit 12 | 13 | public extension UITableView { 14 | /** 15 | Last indexPath in section 16 | 17 | - parameter section: section 18 | 19 | - returns: NSIndexPath 20 | */ 21 | func ts_lastIndexPathInSection(_ section: Int) -> IndexPath? { 22 | return IndexPath(row: self.numberOfRows(inSection: section)-1, section: section) 23 | } 24 | 25 | /// Last indexPath in UITableView 26 | var ts_lastIndexPath: IndexPath? { 27 | if (self.ts_totalRows - 1) > 0{ 28 | return IndexPath(row: self.ts_totalRows-1, section: self.numberOfSections) 29 | } else { 30 | return nil 31 | } 32 | } 33 | 34 | /// Total rows in UITableView 35 | var ts_totalRows: Int { 36 | var i = 0 37 | var rowCount = 0 38 | while i < self.numberOfSections { 39 | rowCount += self.numberOfRows(inSection: i) 40 | i += 1 41 | } 42 | return rowCount 43 | } 44 | 45 | /** 46 | Remove table header view 47 | */ 48 | func ts_removeTableHeaderView() { 49 | self.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.ts_width, height: 0.1)) 50 | } 51 | 52 | /** 53 | Remove table footer view 54 | */ 55 | func ts_removeTableFooterView() { 56 | self.tableFooterView = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.ts_width, height: 0.1)) 57 | } 58 | 59 | /** 60 | Scroll to the bottom 61 | 62 | - parameter animated: animated 63 | */ 64 | func ts_scrollToBottom(_ animated: Bool) { 65 | let section = self.numberOfSections - 1 66 | let row = self.numberOfRows(inSection: section) - 1 67 | if section < 0 || row < 0 { 68 | return 69 | } 70 | let path = IndexPath(row: row, section: section) 71 | let offset = contentOffset.y 72 | self.scrollToRow(at: path, at: .top, animated: animated) 73 | let delay = (animated ? 0.1 : 0.0) * Double(NSEC_PER_SEC) 74 | let time = DispatchTime.now() + Double(Int64(delay)) / Double(NSEC_PER_SEC) 75 | DispatchQueue.main.asyncAfter(deadline: time, execute: { () -> Void in 76 | if self.contentOffset.y != offset { 77 | self.ts_scrollToBottom(false) 78 | } 79 | }) 80 | } 81 | 82 | /** 83 | Scroll to the bottom without flashing 84 | */ 85 | func ts_scrollBottomWithoutFlashing() { 86 | guard let indexPath = self.ts_lastIndexPath else { 87 | return 88 | } 89 | UIView.setAnimationsEnabled(false) 90 | CATransaction.begin() 91 | CATransaction.setDisableActions(true) 92 | self.scrollToRow(at: indexPath, at: .bottom, animated: false) 93 | CATransaction.commit() 94 | UIView.setAnimationsEnabled(true) 95 | } 96 | 97 | /** 98 | Reload data without flashing 99 | */ 100 | func ts_reloadWithoutFlashing() { 101 | UIView.setAnimationsEnabled(false) 102 | CATransaction.begin() 103 | CATransaction.setDisableActions(true) 104 | self.reloadData() 105 | CATransaction.commit() 106 | UIView.setAnimationsEnabled(true) 107 | } 108 | 109 | /** 110 | Fetch indexPaths of UITableView's visibleCells 111 | 112 | - returns: NSIndexPath Array 113 | */ 114 | func ts_visibleIndexPaths() -> [IndexPath] { 115 | var list = [IndexPath]() 116 | for cell in self.visibleCells { 117 | if let indexPath = self.indexPath(for: cell) { 118 | list.append(indexPath) 119 | } 120 | } 121 | return list 122 | } 123 | 124 | /** 125 | Reload data with completion block 126 | 127 | - parameter completion: completion block 128 | */ 129 | func ts_reloadData(_ completion: @escaping ()->()) { 130 | UIView.animate(withDuration: 0, animations: { self.reloadData() }, completion: { _ in completion() }) 131 | 132 | } 133 | } 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /Sources/UIKit/UITableView+TSGeneric.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITableView+TSGeneric.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/22/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import UIKit 11 | 12 | public extension UITableView { 13 | // MARK: - Cell register and reuse 14 | /** 15 | Register cell nib 16 | 17 | - parameter aClass: class 18 | */ 19 | func ts_registerCellNib(_ aClass: T.Type) { 20 | let name = String(describing: aClass) 21 | let nib = UINib(nibName: name, bundle: nil) 22 | self.register(nib, forCellReuseIdentifier: name) 23 | } 24 | 25 | /** 26 | Register cell class 27 | 28 | - parameter aClass: class 29 | */ 30 | func ts_registerCellClass(_ aClass: T.Type) { 31 | let name = String(describing: aClass) 32 | self.register(aClass, forCellReuseIdentifier: name) 33 | } 34 | 35 | /** 36 | Reusable Cell 37 | 38 | - parameter aClass: class 39 | 40 | - returns: cell 41 | */ 42 | func ts_dequeueReusableCell(_ aClass: T.Type) -> T! { 43 | let name = String(describing: aClass) 44 | guard let cell = dequeueReusableCell(withIdentifier: name) as? T else { 45 | fatalError("\(name) is not registed") 46 | } 47 | return cell 48 | } 49 | 50 | // MARK: - HeaderFooter register and reuse 51 | /** 52 | Register cell nib 53 | 54 | - parameter aClass: class 55 | */ 56 | func ts_registerHeaderFooterNib(_ aClass: T.Type) { 57 | let name = String(describing: aClass) 58 | let nib = UINib(nibName: name, bundle: nil) 59 | self.register(nib, forHeaderFooterViewReuseIdentifier: name) 60 | } 61 | 62 | /** 63 | Register cell class 64 | 65 | - parameter aClass: class 66 | */ 67 | func ts_registerHeaderFooterClass(_ aClass: T.Type) { 68 | let name = String(describing: aClass) 69 | self.register(aClass, forHeaderFooterViewReuseIdentifier: name) 70 | } 71 | 72 | /** 73 | Reusable Cell 74 | 75 | - parameter aClass: class 76 | 77 | - returns: cell 78 | */ 79 | func ts_dequeueReusableHeaderFooter(_ aClass: T.Type) -> T! { 80 | let name = String(describing: aClass) 81 | guard let cell = dequeueReusableHeaderFooterView(withIdentifier: name) as? T else { 82 | fatalError("\(name) is not registed") 83 | } 84 | return cell 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /Sources/UIKit/UITableView+TSiOS7SettingStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITableView+TSiOS7SettingStyle.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/4/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | import UIKit 12 | 13 | public extension UITableView { 14 | //http://stackoverflow.com/questions/18822619/ios-7-tableview-like-in-settings-app-on-ipad 15 | /** 16 | iOS 7 TableView like in Settings App on iPad 17 | 18 | - parameter cell: cell 19 | - parameter indexPath: indexPath 20 | */ 21 | func ts_applyiOS7SettingsStyleGrouping(_ cell: UITableViewCell, indexPath: IndexPath) { 22 | if (!cell.responds(to: #selector(getter: UIView.tintColor))){ 23 | return 24 | } 25 | 26 | let cornerRadius : CGFloat = 12.0 27 | cell.backgroundColor = UIColor.clear 28 | let layer: CAShapeLayer = CAShapeLayer() 29 | let pathRef:CGMutablePath = CGMutablePath() 30 | let bounds: CGRect = cell.bounds.insetBy(dx: 25, dy: 0) 31 | var addLine: Bool = false 32 | 33 | if ((indexPath as NSIndexPath).row == 0 && (indexPath as NSIndexPath).row == self.numberOfRows(inSection: (indexPath as NSIndexPath).section)-1) { 34 | pathRef.__addRoundedRect(transform: nil, rect: bounds, cornerWidth: cornerRadius, cornerHeight: cornerRadius) 35 | } else if ((indexPath as NSIndexPath).row == 0) { 36 | pathRef.move(to: CGPoint.init(x: bounds.minX, y: bounds.maxY)) 37 | pathRef.addArc(tangent1End: CGPoint.init(x: bounds.minX, y: bounds.minY), tangent2End: CGPoint.init(x: bounds.midX, y: bounds.minY), radius: cornerRadius) 38 | pathRef.addArc(tangent1End: CGPoint.init(x: bounds.maxX, y: bounds.minY), tangent2End: CGPoint.init(x: bounds.maxX, y: bounds.midY), radius: cornerRadius) 39 | pathRef.addLine(to: CGPoint.init(x: bounds.maxX, y: bounds.maxY)) 40 | addLine = true 41 | } else if ((indexPath as NSIndexPath).row == self.numberOfRows(inSection: (indexPath as NSIndexPath).section)-1) { 42 | pathRef.move(to: CGPoint.init(x: bounds.minX, y: bounds.minY)) 43 | pathRef.addArc(tangent1End: CGPoint.init(x: bounds.minX, y: bounds.maxY), tangent2End: CGPoint.init(x: bounds.midX, y: bounds.maxY), radius: cornerRadius) 44 | pathRef.addArc(tangent1End: CGPoint.init(x: bounds.maxY, y: bounds.maxY), tangent2End: CGPoint.init(x: bounds.maxX, y: bounds.midY), radius: cornerRadius) 45 | pathRef.addLine(to: CGPoint.init(x: bounds.maxX, y: bounds.minY)) 46 | } else { 47 | pathRef.addRect(bounds) 48 | addLine = true 49 | } 50 | 51 | layer.path = pathRef 52 | layer.fillColor = UIColor(red: 255/255.0, green: 255/255.0, blue: 255/255.0, alpha: 0.8).cgColor 53 | 54 | if (addLine == true) { 55 | let lineLayer: CALayer = CALayer() 56 | let lineHeight: CGFloat = (1.0 / UIScreen.main.scale) 57 | lineLayer.frame = CGRect(x: bounds.minX+10, y: bounds.size.height-lineHeight, width: bounds.size.width-10, height: lineHeight) 58 | lineLayer.backgroundColor = self.separatorColor!.cgColor 59 | layer.addSublayer(lineLayer) 60 | } 61 | let testView: UIView = UIView(frame: bounds) 62 | testView.layer.insertSublayer(layer, at: 0) 63 | testView.backgroundColor = UIColor.clear 64 | cell.backgroundView = testView 65 | } 66 | 67 | 68 | } 69 | -------------------------------------------------------------------------------- /Sources/UIKit/UIView+TSExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+TSExtension.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 11/6/15. 7 | // Copyright © 2015 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | import UIKit 12 | 13 | public extension UIView { 14 | /** 15 | Init from nib 16 | Notice: The nib file name is the same as the calss name 17 | 18 | - returns: UINib 19 | */ 20 | class func ts_Nib() -> UINib { 21 | let hasNib: Bool = Bundle.main.path(forResource: self.ts_className, ofType: "nib") != nil 22 | guard hasNib else { 23 | assert(!hasNib, "Nib is not exist") 24 | return UINib() 25 | } 26 | return UINib(nibName: self.ts_className, bundle:nil) 27 | } 28 | 29 | /** 30 | Init from nib and get the view 31 | Notice: The nib file name is the same as the calss name 32 | 33 | Demo: UIView.ts_viewFromNib(TSCustomView) 34 | 35 | - parameter aClass: your class 36 | 37 | - returns: Your class's view 38 | */ 39 | class func ts_viewFromNib(_ aClass: T.Type) -> T { 40 | let name = String(describing: aClass) 41 | if Bundle.main.path(forResource: name, ofType: "nib") != nil { 42 | return UINib(nibName: name, bundle:nil).instantiate(withOwner: nil, options: nil)[0] as! T 43 | } else { 44 | fatalError("\(String(describing: aClass)) nib is not exist") 45 | } 46 | } 47 | 48 | /** 49 | All subviews of the UIView 50 | 51 | - returns: A group of UIView 52 | */ 53 | func ts_allSubviews() -> [UIView] { 54 | var stack = [self] 55 | var views = [UIView]() 56 | while !stack.isEmpty { 57 | let subviews = stack[0].subviews as [UIView] 58 | views += subviews 59 | stack += subviews 60 | stack.remove(at: 0) 61 | } 62 | return views 63 | } 64 | 65 | /** 66 | Take snap shot 67 | 68 | - returns: UIImage 69 | */ 70 | func ts_takeSnapshot() -> UIImage { 71 | UIGraphicsBeginImageContextWithOptions(bounds.size, false, UIScreen.main.scale) 72 | drawHierarchy(in: self.bounds, afterScreenUpdates: true) 73 | let image = UIGraphicsGetImageFromCurrentImageContext() 74 | UIGraphicsEndImageContext() 75 | return image! 76 | } 77 | 78 | /// Check the view is visible 79 | @available(iOS 8.0, *) 80 | var ts_visible: Bool { 81 | get { 82 | if self.window == nil || self.isHidden || self.alpha == 0 { 83 | return true 84 | } 85 | 86 | let viewRect = self.convert(self.bounds, to: nil) 87 | guard let app = UIApplication.ts_sharedApplication() else { 88 | return false 89 | } 90 | guard let window = app.keyWindow else { 91 | return true 92 | } 93 | return viewRect.intersects(window.bounds) == false 94 | } 95 | } 96 | } 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /Sources/UIKit/UIView+TSFrame.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+TSFrame.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 1/6/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | import UIKit 12 | 13 | public extension UIView { 14 | var ts_width: CGFloat { 15 | get { return self.frame.size.width } 16 | set { 17 | var frame = self.frame 18 | frame.size.width = newValue 19 | self.frame = frame 20 | } 21 | } 22 | 23 | var ts_height: CGFloat { 24 | get { return self.frame.size.height } 25 | set { 26 | var frame = self.frame 27 | frame.size.height = newValue 28 | self.frame = frame 29 | } 30 | } 31 | 32 | var ts_size: CGSize { 33 | get { return self.frame.size } 34 | set { 35 | var frame = self.frame 36 | frame.size = newValue 37 | self.frame = frame 38 | } 39 | } 40 | 41 | var ts_origin: CGPoint { 42 | get { return self.frame.origin } 43 | set { 44 | var frame = self.frame 45 | frame.origin = newValue 46 | self.frame = frame 47 | } 48 | } 49 | 50 | var ts_x: CGFloat { 51 | get { return self.frame.origin.x } 52 | set { 53 | var frame = self.frame 54 | frame.origin.x = newValue 55 | self.frame = frame 56 | } 57 | } 58 | var ts_y: CGFloat { 59 | get { return self.frame.origin.y } 60 | set { 61 | var frame = self.frame 62 | frame.origin.y = newValue 63 | self.frame = frame 64 | } 65 | } 66 | 67 | var ts_centerX: CGFloat { 68 | get { return self.center.x } 69 | set { self.center = CGPoint(x: newValue, y: self.center.y) } 70 | } 71 | 72 | var ts_centerY: CGFloat { 73 | get { return self.center.y } 74 | set { self.center = CGPoint(x: self.center.x, y: newValue) } 75 | } 76 | 77 | var ts_top : CGFloat { 78 | get { return self.frame.origin.y } 79 | set { 80 | var frame = self.frame 81 | frame.origin.y = newValue 82 | self.frame = frame 83 | } 84 | } 85 | 86 | var ts_bottom : CGFloat { 87 | get { return frame.origin.y + frame.size.height } 88 | set { 89 | var frame = self.frame 90 | frame.origin.y = newValue - self.frame.size.height 91 | self.frame = frame 92 | } 93 | } 94 | 95 | var ts_right : CGFloat { 96 | get { return self.frame.origin.x + self.frame.size.width } 97 | set { 98 | var frame = self.frame 99 | frame.origin.x = newValue - self.frame.size.width 100 | self.frame = frame 101 | } 102 | } 103 | 104 | var ts_left : CGFloat { 105 | get { return self.frame.origin.x } 106 | set { 107 | var frame = self.frame 108 | frame.origin.x = newValue 109 | self.frame = frame 110 | } 111 | } 112 | } 113 | 114 | -------------------------------------------------------------------------------- /Sources/UIKit/UIView+TSGestureBlock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+TSGestureBlock.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 1/6/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | import UIKit 12 | 13 | fileprivate class TSClosureWrapper : NSObject { 14 | let _callback : () -> Void 15 | init(callback : @escaping () -> Void) { 16 | _callback = callback 17 | } 18 | 19 | @objc func invoke() { 20 | _callback() 21 | } 22 | } 23 | 24 | fileprivate var AssociatedClosure: UInt8 = 18 25 | 26 | public extension UIView { 27 | /// Adds a tap gesture to the view with a block that will be invoked whenever 28 | /// 29 | /// - parameter callback: callback Invoked whenever the gesture's state changes. 30 | /// 31 | /// - returns: The gesture. 32 | @discardableResult 33 | func ts_tapped(callback: @escaping () -> Void) -> UITapGestureRecognizer { 34 | self.isUserInteractionEnabled = true 35 | let wrapper = TSClosureWrapper(callback: callback) 36 | let gesture = UITapGestureRecognizer.init(target: wrapper, action: #selector(TSClosureWrapper.invoke)) 37 | addGestureRecognizer(gesture) 38 | objc_setAssociatedObject(self, &AssociatedClosure, wrapper, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 39 | return gesture 40 | } 41 | 42 | /// Adds a long press gesture to the view with a block that will be invoked whenever 43 | /// 44 | /// - parameter callback: callback Invoked whenever the gesture's state changes. 45 | /// 46 | /// - returns: The gesture. 47 | @discardableResult 48 | func ts_longPressed(callback: @escaping () -> Void) -> UILongPressGestureRecognizer { 49 | self.isUserInteractionEnabled = true 50 | let wrapper = TSClosureWrapper(callback: callback) 51 | let gesture = UILongPressGestureRecognizer.init(target: wrapper, action: #selector(TSClosureWrapper.invoke)) 52 | addGestureRecognizer(gesture) 53 | objc_setAssociatedObject(self, &AssociatedClosure, wrapper, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 54 | return gesture 55 | } 56 | 57 | /// Adds a pinch press gesture to the view with a block that will be invoked whenever 58 | /// 59 | /// - parameter callback: callback Invoked whenever the gesture's state changes. 60 | /// 61 | /// - returns: The gesture. 62 | @discardableResult 63 | func ts_pinched(callback: @escaping () -> Void) -> UIPinchGestureRecognizer { 64 | self.isUserInteractionEnabled = true 65 | let wrapper = TSClosureWrapper(callback: callback) 66 | let gesture = UIPinchGestureRecognizer.init(target: wrapper, action: #selector(TSClosureWrapper.invoke)) 67 | addGestureRecognizer(gesture) 68 | objc_setAssociatedObject(self, &AssociatedClosure, wrapper, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 69 | return gesture 70 | } 71 | 72 | /// Adds a pan gesture to the view with a block that will be invoked whenever 73 | /// 74 | /// - parameter callback: callback Invoked whenever the gesture's state changes. 75 | /// 76 | /// - returns: The gesture. 77 | @discardableResult 78 | func ts_panned(callback: @escaping () -> Void) -> UIPanGestureRecognizer { 79 | self.isUserInteractionEnabled = true 80 | let wrapper = TSClosureWrapper(callback: callback) 81 | let gesture = UIPanGestureRecognizer.init(target: wrapper, action: #selector(TSClosureWrapper.invoke)) 82 | addGestureRecognizer(gesture) 83 | objc_setAssociatedObject(self, &AssociatedClosure, wrapper, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 84 | return gesture 85 | } 86 | 87 | /// Adds a rotation gesture to the view with a block that will be invoked whenever 88 | /// 89 | /// - parameter callback: callback Invoked whenever the gesture's state changes. 90 | /// 91 | /// - returns: The gesture. 92 | @discardableResult 93 | func ts_rotated(callback: @escaping () -> Void) -> UIRotationGestureRecognizer { 94 | self.isUserInteractionEnabled = true 95 | let wrapper = TSClosureWrapper(callback: callback) 96 | let gesture = UIRotationGestureRecognizer.init(target: wrapper, action: #selector(TSClosureWrapper.invoke)) 97 | addGestureRecognizer(gesture) 98 | objc_setAssociatedObject(self, &AssociatedClosure, wrapper, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 99 | return gesture 100 | } 101 | } 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /Sources/UIKit/UIViewController+TSExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+TSExtension.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 11/6/15. 7 | // Copyright © 2015 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | import UIKit 12 | 13 | public extension UIViewController { 14 | /** 15 | UIViewController init from a inb with same name of the class. 16 | 17 | - returns: UIViewController 18 | */ 19 | class func ts_initFromNib() -> UIViewController { 20 | let hasNib: Bool = Bundle.main.path(forResource: self.ts_className, ofType: "nib") != nil 21 | guard hasNib else { 22 | assert(!hasNib, "Invalid parameter") // here 23 | return UIViewController() 24 | } 25 | return self.init(nibName: self.ts_className, bundle: nil) 26 | } 27 | 28 | /** 29 | Back bar with go to previous action 30 | 31 | - parameter backImage: Your image. 20px * 20px is perfect 32 | */ 33 | func ts_leftBackToPrevious(_ backImage: UIImage) { 34 | self.ts_leftBackBarButton(backImage, action: {}) 35 | } 36 | 37 | /** 38 | Be sure of your viewController has a UINavigationController 39 | Left back bar. If your viewController is from push action, then handler will execute popViewControllerAnimated method. If your viewController is from present action, then handler will excute dismissViewControllerAnimated method. 40 | 41 | - parameter backImage: Your image. 20px * 20px is perfect 42 | - parameter action: Handler 43 | */ 44 | func ts_leftBackBarButton(_ backImage: UIImage, action: () -> Void) { 45 | guard self.navigationController != nil else { 46 | assert(false, "Your target ViewController doesn't have a UINavigationController") 47 | return 48 | } 49 | 50 | let button: UIButton = UIButton(type: UIButton.ButtonType.custom) 51 | button.setImage(backImage, for: UIControl.State()) 52 | button.frame = CGRect(x: 0, y: 0, width: 40, height: 30) 53 | button.imageView!.contentMode = .scaleAspectFit; 54 | button.contentHorizontalAlignment = .left 55 | 56 | button.ts_addEventHandler(forControlEvent: .touchUpInside, handler: {[weak self] in 57 | guard let strongSelf = self else { return } 58 | if let navigationController = strongSelf.navigationController { 59 | if navigationController.viewControllers.count > 1 { 60 | navigationController.popViewController(animated: true) 61 | } else if (strongSelf.presentingViewController != nil) { 62 | strongSelf.dismiss(animated: true, completion: nil) 63 | } 64 | } else { 65 | assert(false, "Your target ViewController doesn't have a UINavigationController") 66 | } 67 | }) 68 | 69 | let barButton = UIBarButtonItem(customView: button) 70 | let gapItem = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil) 71 | gapItem.width = -7 //fix the space 72 | navigationItem.leftBarButtonItems = [gapItem, barButton] 73 | } 74 | 75 | /** 76 | Back to previous, pop or dismiss 77 | */ 78 | func ts_backToPrevious() { 79 | if let navigationController = self.navigationController { 80 | if navigationController.viewControllers.count > 1 { 81 | navigationController.popViewController(animated: true) 82 | } else if (self.presentingViewController != nil) { 83 | self.dismiss(animated: true, completion: nil) 84 | } 85 | } else { 86 | assert(false, "Your target ViewController doesn't have a UINavigationController") 87 | } 88 | } 89 | 90 | } 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /Sources/UIKit/UIViewController+TSVisible.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+TSVisible.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/4/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | import UIKit 12 | 13 | public extension UIViewController { 14 | // http://stackoverflow.com/questions/2777438/how-to-tell-if-uiviewcontrollers-view-is-visible 15 | /// Check visible 16 | var ts_isVisible: Bool { 17 | return self.isViewLoaded && view.window != nil 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/UIKit/UIWindow+TSHierarchy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIWindow+TSHierarchy.swift 3 | // TimedSilver 4 | // Source: https://github.com/hilen/TimedSilver 5 | // 6 | // Created by Hilen on 8/4/16. 7 | // Copyright © 2016 Hilen. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | import UIKit 12 | 13 | public extension UIWindow { 14 | /** 15 | The top ViewController 16 | 17 | - returns: UIViewController 18 | */ 19 | func ts_topViewController() -> UIViewController? { 20 | var topController = self.rootViewController 21 | while let pVC = topController?.presentedViewController { 22 | topController = pVC 23 | } 24 | 25 | if topController == nil { 26 | assert(false, "Error: You don't have any views set. You may be calling them in viewDidLoad. Try viewDidAppear instead.") 27 | } 28 | return topController 29 | } 30 | } -------------------------------------------------------------------------------- /TimedSilver.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "TimedSilver" 3 | s.version = "1.2.0" 4 | s.summary = "TimedSilver: A collection of swift extensions." 5 | s.description = <<-DESC 6 | All about swift extension, a collection of useful Swift extensions extending iOS Frameworks such as Foundation, UIKit, swift struct and so on. This would help you to quickly build your swift applications. 7 | DESC 8 | s.homepage = "https://github.com/hilen/TimedSilver" 9 | s.license = { :type => "MIT", :file => "LICENSE" } 10 | s.author = { "Hilen" => "hilenkz@gmail.com" } 11 | s.source = { :git => "https://github.com/hilen/TimedSilver.git", :tag => s.version.to_s } 12 | s.ios.deployment_target = "9.0" 13 | s.requires_arc = true 14 | s.platform = :ios, "9.0" 15 | s.source_files = "Sources/**/*.swift","Sources/*.{h,swift}" 16 | s.frameworks = 'Foundation', 'UIKit', 'QuartzCore', 'AVFoundation' 17 | s.swift_version = '5.0' 18 | s.pod_target_xcconfig = { 'SWIFT_VERSION' => '5.0' } 19 | end 20 | --------------------------------------------------------------------------------