├── .gitignore ├── .swift-version ├── Demo ├── FillableLoaders-Example.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── FillableLoaders.xcscheme ├── FillableLoaders-Example │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard │ ├── DefaultPaths.swift │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ ├── ViewController.swift │ └── bg.png └── FillableLoaders │ ├── FillableLoaders.h │ └── Info.plist ├── FillableLoaders.podspec ├── Images ├── demo.png ├── plain.gif ├── progress.gif ├── rounded.gif ├── spike.gif └── waves.gif ├── LICENSE ├── README.md └── Source ├── FillableLoader.swift ├── PlainLoader.swift ├── RoundedLoader.swift ├── SpikeLoader.swift └── WavesLoader.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | .DS_Store 5 | *.pbxuser 6 | !default.pbxuser 7 | *.mode1v3 8 | !default.mode1v3 9 | *.mode2v3 10 | !default.mode2v3 11 | *.perspectivev3 12 | !default.perspectivev3 13 | xcuserdata 14 | *.xccheckout 15 | *.moved-aside 16 | DerivedData 17 | *.hmap 18 | *.ipa 19 | *.xcuserstate 20 | 21 | # CocoaPods 22 | # 23 | # We recommend against adding the Pods directory to your .gitignore. However 24 | # you should judge for yourself, the pros and cons are mentioned at: 25 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 26 | # 27 | # Pods/ 28 | 29 | # Carthage 30 | # 31 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 32 | # Carthage/Checkouts 33 | 34 | Carthage/Build 35 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 2 | -------------------------------------------------------------------------------- /Demo/FillableLoaders-Example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C17B58D11B97906A00717546 /* bg.png in Resources */ = {isa = PBXBuildFile; fileRef = C17B58CF1B97906A00717546 /* bg.png */; }; 11 | C17B58D21B97906A00717546 /* DefaultPaths.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17B58D01B97906A00717546 /* DefaultPaths.swift */; }; 12 | C17B59021B9790A800717546 /* FillableLoaders.h in Headers */ = {isa = PBXBuildFile; fileRef = C17B59011B9790A800717546 /* FillableLoaders.h */; settings = {ATTRIBUTES = (Public, ); }; }; 13 | C17B59141B9790A800717546 /* FillableLoaders.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C17B58FD1B9790A800717546 /* FillableLoaders.framework */; }; 14 | C17B59211B9790CD00717546 /* FillableLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17B591C1B9790CD00717546 /* FillableLoader.swift */; }; 15 | C17B59221B9790CD00717546 /* PlainLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17B591D1B9790CD00717546 /* PlainLoader.swift */; }; 16 | C17B59231B9790CD00717546 /* RoundedLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17B591E1B9790CD00717546 /* RoundedLoader.swift */; }; 17 | C17B59241B9790CD00717546 /* SpikeLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17B591F1B9790CD00717546 /* SpikeLoader.swift */; }; 18 | C17B59251B9790CD00717546 /* WavesLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17B59201B9790CD00717546 /* WavesLoader.swift */; }; 19 | C17B59261B97913C00717546 /* FillableLoaders.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C17B58FD1B9790A800717546 /* FillableLoaders.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 20 | C1C748991B978FA5001948ED /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C748981B978FA5001948ED /* AppDelegate.swift */; }; 21 | C1C7489B1B978FA5001948ED /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C7489A1B978FA5001948ED /* ViewController.swift */; }; 22 | C1C7489E1B978FA5001948ED /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C1C7489C1B978FA5001948ED /* Main.storyboard */; }; 23 | C1C748A01B978FA5001948ED /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C1C7489F1B978FA5001948ED /* Images.xcassets */; }; 24 | C1C748A31B978FA5001948ED /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = C1C748A11B978FA5001948ED /* LaunchScreen.xib */; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXContainerItemProxy section */ 28 | C17B59121B9790A800717546 /* PBXContainerItemProxy */ = { 29 | isa = PBXContainerItemProxy; 30 | containerPortal = C1C7488B1B978FA5001948ED /* Project object */; 31 | proxyType = 1; 32 | remoteGlobalIDString = C17B58FC1B9790A800717546; 33 | remoteInfo = FillableLoaders; 34 | }; 35 | /* End PBXContainerItemProxy section */ 36 | 37 | /* Begin PBXCopyFilesBuildPhase section */ 38 | C17B58F41B97908000717546 /* Embed Frameworks */ = { 39 | isa = PBXCopyFilesBuildPhase; 40 | buildActionMask = 2147483647; 41 | dstPath = ""; 42 | dstSubfolderSpec = 10; 43 | files = ( 44 | C17B59261B97913C00717546 /* FillableLoaders.framework in Embed Frameworks */, 45 | ); 46 | name = "Embed Frameworks"; 47 | runOnlyForDeploymentPostprocessing = 0; 48 | }; 49 | /* End PBXCopyFilesBuildPhase section */ 50 | 51 | /* Begin PBXFileReference section */ 52 | C17B58CF1B97906A00717546 /* bg.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = bg.png; sourceTree = ""; }; 53 | C17B58D01B97906A00717546 /* DefaultPaths.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultPaths.swift; sourceTree = ""; }; 54 | C17B58FD1B9790A800717546 /* FillableLoaders.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FillableLoaders.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 55 | C17B59001B9790A800717546 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 56 | C17B59011B9790A800717546 /* FillableLoaders.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FillableLoaders.h; sourceTree = ""; }; 57 | C17B591C1B9790CD00717546 /* FillableLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FillableLoader.swift; path = ../../Source/FillableLoader.swift; sourceTree = ""; }; 58 | C17B591D1B9790CD00717546 /* PlainLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PlainLoader.swift; path = ../../Source/PlainLoader.swift; sourceTree = ""; }; 59 | C17B591E1B9790CD00717546 /* RoundedLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RoundedLoader.swift; path = ../../Source/RoundedLoader.swift; sourceTree = ""; }; 60 | C17B591F1B9790CD00717546 /* SpikeLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SpikeLoader.swift; path = ../../Source/SpikeLoader.swift; sourceTree = ""; }; 61 | C17B59201B9790CD00717546 /* WavesLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = WavesLoader.swift; path = ../../Source/WavesLoader.swift; sourceTree = ""; }; 62 | C1C748931B978FA5001948ED /* FillableLoaders-Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "FillableLoaders-Example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 63 | C1C748971B978FA5001948ED /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 64 | C1C748981B978FA5001948ED /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 65 | C1C7489A1B978FA5001948ED /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 66 | C1C7489D1B978FA5001948ED /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 67 | C1C7489F1B978FA5001948ED /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 68 | C1C748A21B978FA5001948ED /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 69 | /* End PBXFileReference section */ 70 | 71 | /* Begin PBXFrameworksBuildPhase section */ 72 | C17B58F91B9790A800717546 /* Frameworks */ = { 73 | isa = PBXFrameworksBuildPhase; 74 | buildActionMask = 2147483647; 75 | files = ( 76 | ); 77 | runOnlyForDeploymentPostprocessing = 0; 78 | }; 79 | C1C748901B978FA5001948ED /* Frameworks */ = { 80 | isa = PBXFrameworksBuildPhase; 81 | buildActionMask = 2147483647; 82 | files = ( 83 | C17B59141B9790A800717546 /* FillableLoaders.framework in Frameworks */, 84 | ); 85 | runOnlyForDeploymentPostprocessing = 0; 86 | }; 87 | /* End PBXFrameworksBuildPhase section */ 88 | 89 | /* Begin PBXGroup section */ 90 | C17B58FE1B9790A800717546 /* FillableLoaders */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | C17B59011B9790A800717546 /* FillableLoaders.h */, 94 | C17B591C1B9790CD00717546 /* FillableLoader.swift */, 95 | C17B591D1B9790CD00717546 /* PlainLoader.swift */, 96 | C17B591E1B9790CD00717546 /* RoundedLoader.swift */, 97 | C17B591F1B9790CD00717546 /* SpikeLoader.swift */, 98 | C17B59201B9790CD00717546 /* WavesLoader.swift */, 99 | C17B58FF1B9790A800717546 /* Supporting Files */, 100 | ); 101 | path = FillableLoaders; 102 | sourceTree = ""; 103 | }; 104 | C17B58FF1B9790A800717546 /* Supporting Files */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | C17B59001B9790A800717546 /* Info.plist */, 108 | ); 109 | name = "Supporting Files"; 110 | sourceTree = ""; 111 | }; 112 | C1C7488A1B978FA5001948ED = { 113 | isa = PBXGroup; 114 | children = ( 115 | C1C748951B978FA5001948ED /* FillableLoaders-Example */, 116 | C17B58FE1B9790A800717546 /* FillableLoaders */, 117 | C1C748941B978FA5001948ED /* Products */, 118 | ); 119 | sourceTree = ""; 120 | }; 121 | C1C748941B978FA5001948ED /* Products */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | C1C748931B978FA5001948ED /* FillableLoaders-Example.app */, 125 | C17B58FD1B9790A800717546 /* FillableLoaders.framework */, 126 | ); 127 | name = Products; 128 | sourceTree = ""; 129 | }; 130 | C1C748951B978FA5001948ED /* FillableLoaders-Example */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | C1C748981B978FA5001948ED /* AppDelegate.swift */, 134 | C1C7489A1B978FA5001948ED /* ViewController.swift */, 135 | C17B58D01B97906A00717546 /* DefaultPaths.swift */, 136 | C17B58CF1B97906A00717546 /* bg.png */, 137 | C1C7489C1B978FA5001948ED /* Main.storyboard */, 138 | C1C7489F1B978FA5001948ED /* Images.xcassets */, 139 | C1C748A11B978FA5001948ED /* LaunchScreen.xib */, 140 | C1C748961B978FA5001948ED /* Supporting Files */, 141 | ); 142 | path = "FillableLoaders-Example"; 143 | sourceTree = ""; 144 | }; 145 | C1C748961B978FA5001948ED /* Supporting Files */ = { 146 | isa = PBXGroup; 147 | children = ( 148 | C1C748971B978FA5001948ED /* Info.plist */, 149 | ); 150 | name = "Supporting Files"; 151 | sourceTree = ""; 152 | }; 153 | /* End PBXGroup section */ 154 | 155 | /* Begin PBXHeadersBuildPhase section */ 156 | C17B58FA1B9790A800717546 /* Headers */ = { 157 | isa = PBXHeadersBuildPhase; 158 | buildActionMask = 2147483647; 159 | files = ( 160 | C17B59021B9790A800717546 /* FillableLoaders.h in Headers */, 161 | ); 162 | runOnlyForDeploymentPostprocessing = 0; 163 | }; 164 | /* End PBXHeadersBuildPhase section */ 165 | 166 | /* Begin PBXNativeTarget section */ 167 | C17B58FC1B9790A800717546 /* FillableLoaders */ = { 168 | isa = PBXNativeTarget; 169 | buildConfigurationList = C17B59161B9790A800717546 /* Build configuration list for PBXNativeTarget "FillableLoaders" */; 170 | buildPhases = ( 171 | C17B58F81B9790A800717546 /* Sources */, 172 | C17B58F91B9790A800717546 /* Frameworks */, 173 | C17B58FA1B9790A800717546 /* Headers */, 174 | C17B58FB1B9790A800717546 /* Resources */, 175 | ); 176 | buildRules = ( 177 | ); 178 | dependencies = ( 179 | ); 180 | name = FillableLoaders; 181 | productName = FillableLoaders; 182 | productReference = C17B58FD1B9790A800717546 /* FillableLoaders.framework */; 183 | productType = "com.apple.product-type.framework"; 184 | }; 185 | C1C748921B978FA5001948ED /* FillableLoaders-Example */ = { 186 | isa = PBXNativeTarget; 187 | buildConfigurationList = C1C748B21B978FA5001948ED /* Build configuration list for PBXNativeTarget "FillableLoaders-Example" */; 188 | buildPhases = ( 189 | C1C7488F1B978FA5001948ED /* Sources */, 190 | C1C748901B978FA5001948ED /* Frameworks */, 191 | C1C748911B978FA5001948ED /* Resources */, 192 | C17B58F41B97908000717546 /* Embed Frameworks */, 193 | ); 194 | buildRules = ( 195 | ); 196 | dependencies = ( 197 | C17B59131B9790A800717546 /* PBXTargetDependency */, 198 | ); 199 | name = "FillableLoaders-Example"; 200 | productName = "FillableLoaders-Example"; 201 | productReference = C1C748931B978FA5001948ED /* FillableLoaders-Example.app */; 202 | productType = "com.apple.product-type.application"; 203 | }; 204 | /* End PBXNativeTarget section */ 205 | 206 | /* Begin PBXProject section */ 207 | C1C7488B1B978FA5001948ED /* Project object */ = { 208 | isa = PBXProject; 209 | attributes = { 210 | LastSwiftUpdateCheck = 0700; 211 | LastUpgradeCheck = 0640; 212 | ORGANIZATIONNAME = "Pol Quintana"; 213 | TargetAttributes = { 214 | C17B58FC1B9790A800717546 = { 215 | CreatedOnToolsVersion = 6.4; 216 | LastSwiftMigration = 0800; 217 | }; 218 | C1C748921B978FA5001948ED = { 219 | CreatedOnToolsVersion = 6.4; 220 | LastSwiftMigration = 0800; 221 | }; 222 | }; 223 | }; 224 | buildConfigurationList = C1C7488E1B978FA5001948ED /* Build configuration list for PBXProject "FillableLoaders-Example" */; 225 | compatibilityVersion = "Xcode 3.2"; 226 | developmentRegion = English; 227 | hasScannedForEncodings = 0; 228 | knownRegions = ( 229 | en, 230 | Base, 231 | ); 232 | mainGroup = C1C7488A1B978FA5001948ED; 233 | productRefGroup = C1C748941B978FA5001948ED /* Products */; 234 | projectDirPath = ""; 235 | projectRoot = ""; 236 | targets = ( 237 | C1C748921B978FA5001948ED /* FillableLoaders-Example */, 238 | C17B58FC1B9790A800717546 /* FillableLoaders */, 239 | ); 240 | }; 241 | /* End PBXProject section */ 242 | 243 | /* Begin PBXResourcesBuildPhase section */ 244 | C17B58FB1B9790A800717546 /* Resources */ = { 245 | isa = PBXResourcesBuildPhase; 246 | buildActionMask = 2147483647; 247 | files = ( 248 | ); 249 | runOnlyForDeploymentPostprocessing = 0; 250 | }; 251 | C1C748911B978FA5001948ED /* Resources */ = { 252 | isa = PBXResourcesBuildPhase; 253 | buildActionMask = 2147483647; 254 | files = ( 255 | C1C7489E1B978FA5001948ED /* Main.storyboard in Resources */, 256 | C1C748A31B978FA5001948ED /* LaunchScreen.xib in Resources */, 257 | C17B58D11B97906A00717546 /* bg.png in Resources */, 258 | C1C748A01B978FA5001948ED /* Images.xcassets in Resources */, 259 | ); 260 | runOnlyForDeploymentPostprocessing = 0; 261 | }; 262 | /* End PBXResourcesBuildPhase section */ 263 | 264 | /* Begin PBXSourcesBuildPhase section */ 265 | C17B58F81B9790A800717546 /* Sources */ = { 266 | isa = PBXSourcesBuildPhase; 267 | buildActionMask = 2147483647; 268 | files = ( 269 | C17B59231B9790CD00717546 /* RoundedLoader.swift in Sources */, 270 | C17B59211B9790CD00717546 /* FillableLoader.swift in Sources */, 271 | C17B59221B9790CD00717546 /* PlainLoader.swift in Sources */, 272 | C17B59241B9790CD00717546 /* SpikeLoader.swift in Sources */, 273 | C17B59251B9790CD00717546 /* WavesLoader.swift in Sources */, 274 | ); 275 | runOnlyForDeploymentPostprocessing = 0; 276 | }; 277 | C1C7488F1B978FA5001948ED /* Sources */ = { 278 | isa = PBXSourcesBuildPhase; 279 | buildActionMask = 2147483647; 280 | files = ( 281 | C17B58D21B97906A00717546 /* DefaultPaths.swift in Sources */, 282 | C1C7489B1B978FA5001948ED /* ViewController.swift in Sources */, 283 | C1C748991B978FA5001948ED /* AppDelegate.swift in Sources */, 284 | ); 285 | runOnlyForDeploymentPostprocessing = 0; 286 | }; 287 | /* End PBXSourcesBuildPhase section */ 288 | 289 | /* Begin PBXTargetDependency section */ 290 | C17B59131B9790A800717546 /* PBXTargetDependency */ = { 291 | isa = PBXTargetDependency; 292 | target = C17B58FC1B9790A800717546 /* FillableLoaders */; 293 | targetProxy = C17B59121B9790A800717546 /* PBXContainerItemProxy */; 294 | }; 295 | /* End PBXTargetDependency section */ 296 | 297 | /* Begin PBXVariantGroup section */ 298 | C1C7489C1B978FA5001948ED /* Main.storyboard */ = { 299 | isa = PBXVariantGroup; 300 | children = ( 301 | C1C7489D1B978FA5001948ED /* Base */, 302 | ); 303 | name = Main.storyboard; 304 | sourceTree = ""; 305 | }; 306 | C1C748A11B978FA5001948ED /* LaunchScreen.xib */ = { 307 | isa = PBXVariantGroup; 308 | children = ( 309 | C1C748A21B978FA5001948ED /* Base */, 310 | ); 311 | name = LaunchScreen.xib; 312 | sourceTree = ""; 313 | }; 314 | /* End PBXVariantGroup section */ 315 | 316 | /* Begin XCBuildConfiguration section */ 317 | C17B59171B9790A800717546 /* Debug */ = { 318 | isa = XCBuildConfiguration; 319 | buildSettings = { 320 | CLANG_ENABLE_MODULES = YES; 321 | CURRENT_PROJECT_VERSION = 1; 322 | DEFINES_MODULE = YES; 323 | DYLIB_COMPATIBILITY_VERSION = 1; 324 | DYLIB_CURRENT_VERSION = 1; 325 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 326 | GCC_PREPROCESSOR_DEFINITIONS = ( 327 | "DEBUG=1", 328 | "$(inherited)", 329 | ); 330 | INFOPLIST_FILE = FillableLoaders/Info.plist; 331 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 332 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 333 | PRODUCT_NAME = "$(TARGET_NAME)"; 334 | SKIP_INSTALL = YES; 335 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 336 | SWIFT_VERSION = 3.0; 337 | VERSIONING_SYSTEM = "apple-generic"; 338 | VERSION_INFO_PREFIX = ""; 339 | }; 340 | name = Debug; 341 | }; 342 | C17B59181B9790A800717546 /* Release */ = { 343 | isa = XCBuildConfiguration; 344 | buildSettings = { 345 | CLANG_ENABLE_MODULES = YES; 346 | CURRENT_PROJECT_VERSION = 1; 347 | DEFINES_MODULE = YES; 348 | DYLIB_COMPATIBILITY_VERSION = 1; 349 | DYLIB_CURRENT_VERSION = 1; 350 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 351 | INFOPLIST_FILE = FillableLoaders/Info.plist; 352 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 353 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 354 | PRODUCT_NAME = "$(TARGET_NAME)"; 355 | SKIP_INSTALL = YES; 356 | SWIFT_VERSION = 3.0; 357 | VERSIONING_SYSTEM = "apple-generic"; 358 | VERSION_INFO_PREFIX = ""; 359 | }; 360 | name = Release; 361 | }; 362 | C1C748B01B978FA5001948ED /* Debug */ = { 363 | isa = XCBuildConfiguration; 364 | buildSettings = { 365 | ALWAYS_SEARCH_USER_PATHS = NO; 366 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 367 | CLANG_CXX_LIBRARY = "libc++"; 368 | CLANG_ENABLE_MODULES = YES; 369 | CLANG_ENABLE_OBJC_ARC = YES; 370 | CLANG_WARN_BOOL_CONVERSION = YES; 371 | CLANG_WARN_CONSTANT_CONVERSION = YES; 372 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 373 | CLANG_WARN_EMPTY_BODY = YES; 374 | CLANG_WARN_ENUM_CONVERSION = YES; 375 | CLANG_WARN_INT_CONVERSION = YES; 376 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 377 | CLANG_WARN_UNREACHABLE_CODE = YES; 378 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 379 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 380 | COPY_PHASE_STRIP = NO; 381 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 382 | ENABLE_STRICT_OBJC_MSGSEND = YES; 383 | GCC_C_LANGUAGE_STANDARD = gnu99; 384 | GCC_DYNAMIC_NO_PIC = NO; 385 | GCC_NO_COMMON_BLOCKS = YES; 386 | GCC_OPTIMIZATION_LEVEL = 0; 387 | GCC_PREPROCESSOR_DEFINITIONS = ( 388 | "DEBUG=1", 389 | "$(inherited)", 390 | ); 391 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 392 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 393 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 394 | GCC_WARN_UNDECLARED_SELECTOR = YES; 395 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 396 | GCC_WARN_UNUSED_FUNCTION = YES; 397 | GCC_WARN_UNUSED_VARIABLE = YES; 398 | IPHONEOS_DEPLOYMENT_TARGET = 8.4; 399 | MTL_ENABLE_DEBUG_INFO = YES; 400 | ONLY_ACTIVE_ARCH = YES; 401 | SDKROOT = iphoneos; 402 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 403 | TARGETED_DEVICE_FAMILY = "1,2"; 404 | }; 405 | name = Debug; 406 | }; 407 | C1C748B11B978FA5001948ED /* Release */ = { 408 | isa = XCBuildConfiguration; 409 | buildSettings = { 410 | ALWAYS_SEARCH_USER_PATHS = NO; 411 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 412 | CLANG_CXX_LIBRARY = "libc++"; 413 | CLANG_ENABLE_MODULES = YES; 414 | CLANG_ENABLE_OBJC_ARC = YES; 415 | CLANG_WARN_BOOL_CONVERSION = YES; 416 | CLANG_WARN_CONSTANT_CONVERSION = YES; 417 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 418 | CLANG_WARN_EMPTY_BODY = YES; 419 | CLANG_WARN_ENUM_CONVERSION = YES; 420 | CLANG_WARN_INT_CONVERSION = YES; 421 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 422 | CLANG_WARN_UNREACHABLE_CODE = YES; 423 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 424 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 425 | COPY_PHASE_STRIP = NO; 426 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 427 | ENABLE_NS_ASSERTIONS = NO; 428 | ENABLE_STRICT_OBJC_MSGSEND = YES; 429 | GCC_C_LANGUAGE_STANDARD = gnu99; 430 | GCC_NO_COMMON_BLOCKS = YES; 431 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 432 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 433 | GCC_WARN_UNDECLARED_SELECTOR = YES; 434 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 435 | GCC_WARN_UNUSED_FUNCTION = YES; 436 | GCC_WARN_UNUSED_VARIABLE = YES; 437 | IPHONEOS_DEPLOYMENT_TARGET = 8.4; 438 | MTL_ENABLE_DEBUG_INFO = NO; 439 | SDKROOT = iphoneos; 440 | TARGETED_DEVICE_FAMILY = "1,2"; 441 | VALIDATE_PRODUCT = YES; 442 | }; 443 | name = Release; 444 | }; 445 | C1C748B31B978FA5001948ED /* Debug */ = { 446 | isa = XCBuildConfiguration; 447 | buildSettings = { 448 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 449 | INFOPLIST_FILE = "FillableLoaders-Example/Info.plist"; 450 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 451 | PRODUCT_NAME = "$(TARGET_NAME)"; 452 | SWIFT_VERSION = 3.0; 453 | }; 454 | name = Debug; 455 | }; 456 | C1C748B41B978FA5001948ED /* Release */ = { 457 | isa = XCBuildConfiguration; 458 | buildSettings = { 459 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 460 | INFOPLIST_FILE = "FillableLoaders-Example/Info.plist"; 461 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 462 | PRODUCT_NAME = "$(TARGET_NAME)"; 463 | SWIFT_VERSION = 3.0; 464 | }; 465 | name = Release; 466 | }; 467 | /* End XCBuildConfiguration section */ 468 | 469 | /* Begin XCConfigurationList section */ 470 | C17B59161B9790A800717546 /* Build configuration list for PBXNativeTarget "FillableLoaders" */ = { 471 | isa = XCConfigurationList; 472 | buildConfigurations = ( 473 | C17B59171B9790A800717546 /* Debug */, 474 | C17B59181B9790A800717546 /* Release */, 475 | ); 476 | defaultConfigurationIsVisible = 0; 477 | defaultConfigurationName = Release; 478 | }; 479 | C1C7488E1B978FA5001948ED /* Build configuration list for PBXProject "FillableLoaders-Example" */ = { 480 | isa = XCConfigurationList; 481 | buildConfigurations = ( 482 | C1C748B01B978FA5001948ED /* Debug */, 483 | C1C748B11B978FA5001948ED /* Release */, 484 | ); 485 | defaultConfigurationIsVisible = 0; 486 | defaultConfigurationName = Release; 487 | }; 488 | C1C748B21B978FA5001948ED /* Build configuration list for PBXNativeTarget "FillableLoaders-Example" */ = { 489 | isa = XCConfigurationList; 490 | buildConfigurations = ( 491 | C1C748B31B978FA5001948ED /* Debug */, 492 | C1C748B41B978FA5001948ED /* Release */, 493 | ); 494 | defaultConfigurationIsVisible = 0; 495 | defaultConfigurationName = Release; 496 | }; 497 | /* End XCConfigurationList section */ 498 | }; 499 | rootObject = C1C7488B1B978FA5001948ED /* Project object */; 500 | } 501 | -------------------------------------------------------------------------------- /Demo/FillableLoaders-Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/FillableLoaders-Example.xcodeproj/xcshareddata/xcschemes/FillableLoaders.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 60 | 61 | 67 | 68 | 69 | 70 | 72 | 73 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /Demo/FillableLoaders-Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // FillableLoaders 4 | // 5 | // Created by Pol Quintana on 2/8/15. 6 | // Copyright (c) 2015 Pol Quintana. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | } -------------------------------------------------------------------------------- /Demo/FillableLoaders-Example/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Demo/FillableLoaders-Example/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 | -------------------------------------------------------------------------------- /Demo/FillableLoaders-Example/DefaultPaths.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultPaths.swift 3 | // FillableLoaders 4 | // 5 | // Created by Pol Quintana on 2/8/15. 6 | // Copyright (c) 2015 Pol Quintana. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | struct Paths { 13 | static func twitterPath() -> CGPath { 14 | //Created with PaintCode 15 | let bezierPath = UIBezierPath() 16 | bezierPath.move(to: CGPoint(x: 190, y: 281)) 17 | bezierPath.addCurve(to: CGPoint(x: 142, y: 329), controlPoint1: CGPoint(x: 163.49, y: 281), controlPoint2: CGPoint(x: 142, y: 302.49)) 18 | bezierPath.addCurve(to: CGPoint(x: 190, y: 377), controlPoint1: CGPoint(x: 142, y: 355.51), controlPoint2: CGPoint(x: 163.49, y: 377)) 19 | bezierPath.addCurve(to: CGPoint(x: 238, y: 329), controlPoint1: CGPoint(x: 216.51, y: 377), controlPoint2: CGPoint(x: 238, y: 355.51)) 20 | bezierPath.addCurve(to: CGPoint(x: 190, y: 281), controlPoint1: CGPoint(x: 238, y: 302.49), controlPoint2: CGPoint(x: 216.51, y: 281)) 21 | bezierPath.close() 22 | bezierPath.move(to: CGPoint(x: 209.52, y: 320.32)) 23 | bezierPath.addCurve(to: CGPoint(x: 209.55, y: 321.55), controlPoint1: CGPoint(x: 209.54, y: 320.73), controlPoint2: CGPoint(x: 209.55, y: 321.14)) 24 | bezierPath.addCurve(to: CGPoint(x: 182.65, y: 348.44), controlPoint1: CGPoint(x: 209.55, y: 334.04), controlPoint2: CGPoint(x: 200.04, y: 348.44)) 25 | bezierPath.addCurve(to: CGPoint(x: 168.16, y: 344.2), controlPoint1: CGPoint(x: 177.31, y: 348.44), controlPoint2: CGPoint(x: 172.34, y: 346.88)) 26 | bezierPath.addCurve(to: CGPoint(x: 170.41, y: 344.33), controlPoint1: CGPoint(x: 168.9, y: 344.28), controlPoint2: CGPoint(x: 169.65, y: 344.33)) 27 | bezierPath.addCurve(to: CGPoint(x: 182.16, y: 340.28), controlPoint1: CGPoint(x: 174.84, y: 344.33), controlPoint2: CGPoint(x: 178.92, y: 342.82)) 28 | bezierPath.addCurve(to: CGPoint(x: 173.32, y: 333.72), controlPoint1: CGPoint(x: 178.02, y: 340.21), controlPoint2: CGPoint(x: 174.53, y: 337.47)) 29 | bezierPath.addCurve(to: CGPoint(x: 175.1, y: 333.89), controlPoint1: CGPoint(x: 173.9, y: 333.83), controlPoint2: CGPoint(x: 174.49, y: 333.89)) 30 | bezierPath.addCurve(to: CGPoint(x: 177.59, y: 333.56), controlPoint1: CGPoint(x: 175.96, y: 333.89), controlPoint2: CGPoint(x: 176.8, y: 333.77)) 31 | bezierPath.addCurve(to: CGPoint(x: 170.01, y: 324.28), controlPoint1: CGPoint(x: 173.27, y: 332.69), controlPoint2: CGPoint(x: 170.01, y: 328.87)) 32 | bezierPath.addCurve(to: CGPoint(x: 170.01, y: 324.17), controlPoint1: CGPoint(x: 170.01, y: 324.24), controlPoint2: CGPoint(x: 170.01, y: 324.2)) 33 | bezierPath.addCurve(to: CGPoint(x: 174.29, y: 325.35), controlPoint1: CGPoint(x: 171.28, y: 324.87), controlPoint2: CGPoint(x: 172.74, y: 325.3)) 34 | bezierPath.addCurve(to: CGPoint(x: 170.09, y: 317.48), controlPoint1: CGPoint(x: 171.75, y: 323.65), controlPoint2: CGPoint(x: 170.09, y: 320.76)) 35 | bezierPath.addCurve(to: CGPoint(x: 171.37, y: 312.73), controlPoint1: CGPoint(x: 170.09, y: 315.75), controlPoint2: CGPoint(x: 170.55, y: 314.12)) 36 | bezierPath.addCurve(to: CGPoint(x: 190.85, y: 322.61), controlPoint1: CGPoint(x: 176.03, y: 318.45), controlPoint2: CGPoint(x: 183, y: 322.21)) 37 | bezierPath.addCurve(to: CGPoint(x: 190.61, y: 320.45), controlPoint1: CGPoint(x: 190.69, y: 321.91), controlPoint2: CGPoint(x: 190.61, y: 321.19)) 38 | bezierPath.addCurve(to: CGPoint(x: 200.06, y: 311), controlPoint1: CGPoint(x: 190.61, y: 315.23), controlPoint2: CGPoint(x: 194.84, y: 311)) 39 | bezierPath.addCurve(to: CGPoint(x: 206.96, y: 313.98), controlPoint1: CGPoint(x: 202.78, y: 311), controlPoint2: CGPoint(x: 205.24, y: 312.14)) 40 | bezierPath.addCurve(to: CGPoint(x: 212.97, y: 311.69), controlPoint1: CGPoint(x: 209.12, y: 313.56), controlPoint2: CGPoint(x: 211.14, y: 312.77)) 41 | bezierPath.addCurve(to: CGPoint(x: 208.81, y: 316.92), controlPoint1: CGPoint(x: 212.26, y: 313.89), controlPoint2: CGPoint(x: 210.76, y: 315.75)) 42 | bezierPath.addCurve(to: CGPoint(x: 214.24, y: 315.43), controlPoint1: CGPoint(x: 210.72, y: 316.69), controlPoint2: CGPoint(x: 212.54, y: 316.18)) 43 | bezierPath.addCurve(to: CGPoint(x: 209.52, y: 320.32), controlPoint1: CGPoint(x: 212.97, y: 317.32), controlPoint2: CGPoint(x: 211.37, y: 318.99)) 44 | bezierPath.close() 45 | bezierPath.miterLimit = 4; 46 | return bezierPath.cgPath 47 | } 48 | 49 | static func githubPath() -> CGPath { 50 | //Created with PaintCode 51 | let bezierPath = UIBezierPath() 52 | bezierPath.move(to: CGPoint(x: 114.86, y: 69.09)) 53 | bezierPath.addCurve(to: CGPoint(x: 115.66, y: 59.2), controlPoint1: CGPoint(x: 115.31, y: 66.12), controlPoint2: CGPoint(x: 115.59, y: 62.86)) 54 | bezierPath.addCurve(to: CGPoint(x: 107, y: 35.39), controlPoint1: CGPoint(x: 115.64, y: 43.53), controlPoint2: CGPoint(x: 108.4, y: 37.99)) 55 | bezierPath.addCurve(to: CGPoint(x: 105.55, y: 16.26), controlPoint1: CGPoint(x: 109.05, y: 23.51), controlPoint2: CGPoint(x: 106.66, y: 18.11)) 56 | bezierPath.addCurve(to: CGPoint(x: 85.72, y: 23.96), controlPoint1: CGPoint(x: 101.45, y: 14.75), controlPoint2: CGPoint(x: 91.27, y: 20.15)) 57 | bezierPath.addCurve(to: CGPoint(x: 50.32, y: 24.67), controlPoint1: CGPoint(x: 76.66, y: 21.21), controlPoint2: CGPoint(x: 57.51, y: 21.48)) 58 | bezierPath.addCurve(to: CGPoint(x: 30.07, y: 16.34), controlPoint1: CGPoint(x: 37.07, y: 14.84), controlPoint2: CGPoint(x: 30.07, y: 16.34)) 59 | bezierPath.addCurve(to: CGPoint(x: 28.87, y: 37.07), controlPoint1: CGPoint(x: 30.07, y: 16.34), controlPoint2: CGPoint(x: 25.54, y: 24.76)) 60 | bezierPath.addCurve(to: CGPoint(x: 21.26, y: 57.69), controlPoint1: CGPoint(x: 24.51, y: 42.83), controlPoint2: CGPoint(x: 21.26, y: 46.9)) 61 | bezierPath.addCurve(to: CGPoint(x: 21.68, y: 65.07), controlPoint1: CGPoint(x: 21.26, y: 60.28), controlPoint2: CGPoint(x: 21.41, y: 62.72)) 62 | bezierPath.addCurve(to: CGPoint(x: 56.44, y: 95.87), controlPoint1: CGPoint(x: 25.43, y: 85.52), controlPoint2: CGPoint(x: 41.07, y: 94.35)) 63 | bezierPath.addCurve(to: CGPoint(x: 50.97, y: 105.13), controlPoint1: CGPoint(x: 54.13, y: 97.69), controlPoint2: CGPoint(x: 51.35, y: 101.13)) 64 | bezierPath.addCurve(to: CGPoint(x: 37.67, y: 106.24), controlPoint1: CGPoint(x: 48.06, y: 107.07), controlPoint2: CGPoint(x: 42.22, y: 107.72)) 65 | bezierPath.addCurve(to: CGPoint(x: 19.33, y: 92.95), controlPoint1: CGPoint(x: 31.31, y: 104.15), controlPoint2: CGPoint(x: 28.87, y: 91.09)) 66 | bezierPath.addCurve(to: CGPoint(x: 19.47, y: 95.96), controlPoint1: CGPoint(x: 17.27, y: 93.35), controlPoint2: CGPoint(x: 17.68, y: 94.76)) 67 | bezierPath.addCurve(to: CGPoint(x: 27.22, y: 105.53), controlPoint1: CGPoint(x: 22.37, y: 97.91), controlPoint2: CGPoint(x: 25.11, y: 100.34)) 68 | bezierPath.addCurve(to: CGPoint(x: 43.01, y: 116.64), controlPoint1: CGPoint(x: 28.84, y: 109.52), controlPoint2: CGPoint(x: 32.24, y: 116.64)) 69 | bezierPath.addCurve(to: CGPoint(x: 50.28, y: 116.12), controlPoint1: CGPoint(x: 47.29, y: 116.64), controlPoint2: CGPoint(x: 50.28, y: 116.12)) 70 | bezierPath.addCurve(to: CGPoint(x: 50.37, y: 130.24), controlPoint1: CGPoint(x: 50.28, y: 116.12), controlPoint2: CGPoint(x: 50.37, y: 126.28)) 71 | bezierPath.addCurve(to: CGPoint(x: 44.43, y: 138.27), controlPoint1: CGPoint(x: 50.37, y: 134.8), controlPoint2: CGPoint(x: 44.43, y: 136.08)) 72 | bezierPath.addCurve(to: CGPoint(x: 47.97, y: 139.22), controlPoint1: CGPoint(x: 44.43, y: 139.14), controlPoint2: CGPoint(x: 46.39, y: 139.22)) 73 | bezierPath.addCurve(to: CGPoint(x: 57.59, y: 131.79), controlPoint1: CGPoint(x: 51.09, y: 139.22), controlPoint2: CGPoint(x: 57.59, y: 136.53)) 74 | bezierPath.addCurve(to: CGPoint(x: 57.65, y: 113.15), controlPoint1: CGPoint(x: 57.59, y: 128.02), controlPoint2: CGPoint(x: 57.65, y: 115.36)) 75 | bezierPath.addCurve(to: CGPoint(x: 60.15, y: 106.76), controlPoint1: CGPoint(x: 57.65, y: 108.3), controlPoint2: CGPoint(x: 60.15, y: 106.76)) 76 | bezierPath.addCurve(to: CGPoint(x: 59.55, y: 136.08), controlPoint1: CGPoint(x: 60.15, y: 106.76), controlPoint2: CGPoint(x: 60.46, y: 132.61)) 77 | bezierPath.addCurve(to: CGPoint(x: 56.55, y: 141.39), controlPoint1: CGPoint(x: 58.48, y: 140.16), controlPoint2: CGPoint(x: 56.55, y: 139.58)) 78 | bezierPath.addCurve(to: CGPoint(x: 66.95, y: 136.13), controlPoint1: CGPoint(x: 56.55, y: 144.1), controlPoint2: CGPoint(x: 64.36, y: 142.05)) 79 | bezierPath.addCurve(to: CGPoint(x: 68.06, y: 106.15), controlPoint1: CGPoint(x: 68.96, y: 131.5), controlPoint2: CGPoint(x: 68.06, y: 106.15)) 80 | bezierPath.addLine(to: CGPoint(x: 70.15, y: 106.1)) 81 | bezierPath.addCurve(to: CGPoint(x: 70.1, y: 123.02), controlPoint1: CGPoint(x: 70.15, y: 106.1), controlPoint2: CGPoint(x: 70.17, y: 117.71)) 82 | bezierPath.addCurve(to: CGPoint(x: 72.63, y: 138.73), controlPoint1: CGPoint(x: 70.03, y: 128.51), controlPoint2: CGPoint(x: 69.48, y: 135.46)) 83 | bezierPath.addCurve(to: CGPoint(x: 81.03, y: 141.22), controlPoint1: CGPoint(x: 74.7, y: 140.89), controlPoint2: CGPoint(x: 81.03, y: 144.67)) 84 | bezierPath.addCurve(to: CGPoint(x: 76.59, y: 132.13), controlPoint1: CGPoint(x: 81.03, y: 139.21), controlPoint2: CGPoint(x: 76.59, y: 137.56)) 85 | bezierPath.addLine(to: CGPoint(x: 76.59, y: 107.12)) 86 | bezierPath.addCurve(to: CGPoint(x: 79.85, y: 115.34), controlPoint1: CGPoint(x: 79.29, y: 107.12), controlPoint2: CGPoint(x: 79.85, y: 115.34)) 87 | bezierPath.addLine(to: CGPoint(x: 80.82, y: 130.61)) 88 | bezierPath.addCurve(to: CGPoint(x: 86.63, y: 138.51), controlPoint1: CGPoint(x: 80.82, y: 130.61), controlPoint2: CGPoint(x: 80.17, y: 136.19)) 89 | bezierPath.addCurve(to: CGPoint(x: 94.01, y: 138.17), controlPoint1: CGPoint(x: 88.91, y: 139.34), controlPoint2: CGPoint(x: 93.78, y: 139.56)) 90 | bezierPath.addCurve(to: CGPoint(x: 88.08, y: 130.41), controlPoint1: CGPoint(x: 94.24, y: 136.78), controlPoint2: CGPoint(x: 88.14, y: 134.73)) 91 | bezierPath.addCurve(to: CGPoint(x: 88.19, y: 114.82), controlPoint1: CGPoint(x: 88.05, y: 127.79), controlPoint2: CGPoint(x: 88.19, y: 126.25)) 92 | bezierPath.addCurve(to: CGPoint(x: 81.55, y: 95.81), controlPoint1: CGPoint(x: 88.19, y: 103.4), controlPoint2: CGPoint(x: 86.71, y: 99.18)) 93 | bezierPath.addCurve(to: CGPoint(x: 114.86, y: 69.09), controlPoint1: CGPoint(x: 96.52, y: 94.22), controlPoint2: CGPoint(x: 112.06, y: 87.3)) 94 | bezierPath.close() 95 | bezierPath.miterLimit = 4; 96 | return bezierPath.cgPath 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Demo/FillableLoaders-Example/Images.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 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Demo/FillableLoaders-Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.polquintana.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UIStatusBarHidden 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Demo/FillableLoaders-Example/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // FillableLoaders 4 | // 5 | // Created by Pol Quintana on 2/8/15. 6 | // Copyright (c) 2015 Pol Quintana. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import FillableLoaders 11 | 12 | class ViewController: UIViewController { 13 | 14 | var segmentedControl: UISegmentedControl = UISegmentedControl() 15 | var button: UIButton = UIButton() 16 | var loader: FillableLoader = FillableLoader() 17 | var firstLogo: Bool = true 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | } 22 | 23 | override func viewDidAppear(_ animated: Bool) { 24 | super.viewDidAppear(animated) 25 | loader = PlainLoader.showLoader(with: path()) 26 | setupSubviews() 27 | } 28 | 29 | func setupSubviews() { 30 | // let imageView = UIImageView(image: UIImage(named: "bg.png")) 31 | // imageView.frame = view.frame 32 | // view.addSubview(imageView) 33 | 34 | let margin: CGFloat = 30 35 | let width: CGFloat = view.frame.width 36 | let height: CGFloat = view.frame.height 37 | let window = UIApplication.shared.delegate?.window! 38 | let items = ["Waves", "Plain", "Spike", "Rounded"] 39 | segmentedControl = UISegmentedControl(items: items) 40 | segmentedControl.selectedSegmentIndex = 0 41 | segmentedControl.layer.cornerRadius = 5.0 42 | segmentedControl.backgroundColor = UIColor.white 43 | segmentedControl.tintColor = UIColor(white: 0.2, alpha: 1) 44 | segmentedControl.frame = CGRect(x: margin, y: height - margin - 40, width: width - margin*2, height: 40) 45 | segmentedControl.addTarget(self, action: #selector(segmentedControlTouch), for: .valueChanged) 46 | window!.addSubview(segmentedControl) 47 | 48 | button.frame = CGRect(x: 0, y: 0, width: 150, height: 35) 49 | button.center = CGPoint(x: view.frame.width/2, y: view.frame.height - 2*margin - 35) 50 | button.backgroundColor = UIColor.white 51 | button.layer.cornerRadius = 5.0 52 | button.setTitle("Change Logo", for: UIControlState()) 53 | button.setTitleColor(UIColor.black, for: UIControlState()) 54 | button.titleLabel?.font = UIFont.systemFont(ofSize: 14) 55 | button.addTarget(self, action: #selector(changeLogo), for: .touchUpInside) 56 | window!.addSubview(button) 57 | } 58 | 59 | func segmentedControlTouch(_ sender: UISegmentedControl) { 60 | presentFillableLoader(at: sender.selectedSegmentIndex) 61 | } 62 | 63 | func changeLogo() { 64 | firstLogo = !firstLogo 65 | presentFillableLoader(at: segmentedControl.selectedSegmentIndex) 66 | } 67 | 68 | func presentFillableLoader(at index: Int) { 69 | loader.removeLoader(false) 70 | switch index { 71 | case 1: 72 | loader = PlainLoader.showLoader(with: path()) 73 | case 2: 74 | loader = SpikeLoader.showLoader(with: path()) 75 | case 3: 76 | loader = RoundedLoader.showLoader(with: path()) 77 | default: 78 | loader = WavesLoader.showLoader(with: path()) 79 | } 80 | let window = UIApplication.shared.delegate?.window! 81 | window!.bringSubview(toFront: segmentedControl) 82 | window!.bringSubview(toFront: button) 83 | } 84 | 85 | func path() -> CGPath{ 86 | return firstLogo ? Paths.twitterPath() : Paths.githubPath() 87 | } 88 | 89 | override var prefersStatusBarHidden : Bool { 90 | return true 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Demo/FillableLoaders-Example/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polqf/FillableLoaders/cadcfc2a5ab2f32465ceac38d0a44c062c8e6ecf/Demo/FillableLoaders-Example/bg.png -------------------------------------------------------------------------------- /Demo/FillableLoaders/FillableLoaders.h: -------------------------------------------------------------------------------- 1 | // 2 | // FillableLoaders.h 3 | // FillableLoaders 4 | // 5 | // Created by Pol Quintana on 2/9/15. 6 | // Copyright (c) 2015 Pol Quintana. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for FillableLoaders. 12 | FOUNDATION_EXPORT double FillableLoadersVersionNumber; 13 | 14 | //! Project version string for FillableLoaders. 15 | FOUNDATION_EXPORT const unsigned char FillableLoadersVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Demo/FillableLoaders/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.polquintana.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /FillableLoaders.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "FillableLoaders" 3 | s.version = "1.3.0" 4 | s.summary = "Completely customizable progress based loaders drawn using custom CGPaths" 5 | s.homepage = "https://github.com/poolqf/FillableLoaders" 6 | s.license = 'MIT' 7 | s.author = "Pol Quintana" 8 | s.source = { :git => "https://github.com/poolqf/FillableLoaders.git", :tag => "1.3.0" } 9 | s.platform = :ios, '8.0' 10 | s.source_files = 'Source' 11 | s.frameworks = 'UIKit' 12 | s.requires_arc = true 13 | s.social_media_url = 'https://twitter.com/poolqf' 14 | end 15 | -------------------------------------------------------------------------------- /Images/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polqf/FillableLoaders/cadcfc2a5ab2f32465ceac38d0a44c062c8e6ecf/Images/demo.png -------------------------------------------------------------------------------- /Images/plain.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polqf/FillableLoaders/cadcfc2a5ab2f32465ceac38d0a44c062c8e6ecf/Images/plain.gif -------------------------------------------------------------------------------- /Images/progress.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polqf/FillableLoaders/cadcfc2a5ab2f32465ceac38d0a44c062c8e6ecf/Images/progress.gif -------------------------------------------------------------------------------- /Images/rounded.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polqf/FillableLoaders/cadcfc2a5ab2f32465ceac38d0a44c062c8e6ecf/Images/rounded.gif -------------------------------------------------------------------------------- /Images/spike.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polqf/FillableLoaders/cadcfc2a5ab2f32465ceac38d0a44c062c8e6ecf/Images/spike.gif -------------------------------------------------------------------------------- /Images/waves.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polqf/FillableLoaders/cadcfc2a5ab2f32465ceac38d0a44c062c8e6ecf/Images/waves.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Pol Quintana 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://img.shields.io/badge/language-swift-blue.svg) 2 | ![](https://img.shields.io/badge/version-1.3.0-red.svg) 3 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 4 | # FillableLoaders 5 | 6 | #### Completely customizable progress based loaders drawn using custom `CGPath`s written in Swift 7 | 8 |

9 | 10 |

11 |

12 | Waves 13 |

14 | 15 |

16 | 17 |

18 |

19 | Plain 20 |

21 | 22 |

23 | 24 |

25 |

26 | Spike 27 |

28 | 29 |

30 | 31 |

32 |

33 | Rounded 34 |

35 | 36 | 37 | ### Demo: 38 |

39 | 40 |

41 | 42 |

43 | 44 |

45 | 46 | ### Changelog: 47 | - __1.3.0__ (24 Sep 2016) 48 | - Swift 3.0 49 | - __1.2.6__ (8 Apr 2016) 50 | - Fixing issue with width assert 51 | - Adapted to Swift 2.2 52 | - __1.2.5__ (11 Dec 2015) 53 | - Precompiled framework using Xcode 7.2 54 | - __1.2.4__ (28 Oct 2015) 55 | - Fixing issue when showing loader after removing it 56 | - __1.2.2__ (27 Oct 2015) 57 | - Precompiled framework using Xcode 7.1 58 | - __1.2.1__ (25 Oct 2015) 59 | - Added the possibility to add a loader to a desired UIView 60 | - Updated to Swift 2.0 61 | - __1.1.1__ (2 Sep 2015) 62 | - Added Carthage Support 63 | - Added animation when hidding loader 64 | - __1.0.1__ (17 Aug 2015) 65 | - Removed unused code 66 | - __1.0.0__ (7 Aug 2015) 67 | - Progress based loaders :tada: 68 | - Added documentation to all the public properties and functions 69 | - __0.0.2 Initial Release__ (3 Aug 2015) 70 | 71 | 72 | ### Quick Start: 73 | #### - Progress based behaviour 74 | Therea are only 2 necessary things to make the progress based loader work: 75 | 76 | - Create the loader using `showProgressBasedLoaderWithPath(path:)` or `createProgressBasedLoaderWithPath(path:)` 77 | - To update the fill progress, update the `progress` property of the loader, which goes from `0.0` to `1.0` 78 | 79 | #### - Creation 80 | There are four main methods to create the loaders: 81 | 82 | `showProgressBasedLoaderWithPath(path:)`, `createProgressBasedLoaderWithPath(path:)`,`showLoaderWithPath(path:)` and `createLoaderWithPath(path:)` 83 | 84 | `showLoaderWithPath(path:)` or `showProgressBasedLoaderWithPath(path:)` are going to call the create one, and after it, are going to call the `showLoader()` method. 85 | 86 | So, it is just a helper method to do everything at once. 87 | 88 | If you want to create the loader, and not show it at the same moment, you can use `createProgressBasedLoaderWithPath(path:)` or `createLoaderWithPath(path:)` to create it, and when you want to show it, just call `showLoader()` 89 | 90 | Sample code: 91 | 92 | ``` swift 93 | //PROGRESS BASED: 94 | 95 | var loader = WavesLoader.createProgressBasedLoaderWithPath(path) 96 | loader.loaderColor = UIColor.redColor() 97 | ... 98 | //Do other stuff 99 | ... 100 | loader.showLoader() 101 | 102 | //BASIC 103 | 104 | var loader = WavesLoader.createLoaderWithPath(path) 105 | loader.loaderColor = UIColor.redColor() 106 | ... 107 | //Do other stuff 108 | ... 109 | loader.showLoader() 110 | ``` 111 | 112 | #### - Showing loader in desired view: 113 | All the methods wave the variant version where you can pass it the view in which you want to add the loader: 114 | 115 | - `showProgressBasedLoaderWithPath(path:onView:)` 116 | - `createProgressBasedLoaderWithPath(path:onView:)` 117 | - `showLoaderWithPath(path:onView:)` 118 | - `createLoaderWithPath(path:onView:)` 119 | 120 | 121 | #### - Deletion: 122 | Just call the method `removeLoader()` and the loader will disappear and will also be removed from its superview. 123 | 124 | Sample code: 125 | 126 | ``` swift 127 | loader.removeLoader() 128 | ``` 129 | 130 | ### Customization: 131 | 132 | Apart from being able to customize the loader shape, you can also customize other properties of the loader. Take a look at the list: 133 | 134 | - __progressBased__: Bool 135 | Indicates if the loader movement is progress based or not (Default: false) 136 | - __progress__: CGFloat 137 | Loader fill progress from 0.0 to 1.0 . It will automatically fire an animation to update the loader fill progress 138 | - __backgroundColor__: UIColor? 139 | Background of the loader view (transparent by default) 140 | - __loaderColor__: UIColor? 141 | Color of the filled loader 142 | - __loaderBackgroundColor__: UIColor? 143 | Color of the unfilled loader 144 | - __loaderStrokeColor__: UIColor? 145 | Color of the path stroke 146 | - __loaderStrokeWidth__: CGFloat 147 | Width of the path stroke 148 | - __loaderAlpha__: CGFloat 149 | Alpha of the loader view (1.0 by default) 150 | - __cornerRadius__: CGFloat 151 | Corner radius of the loader view (0.0 by default) 152 | - __duration__: NSTimeInterval 153 | Duration of the animations (10.0 by default) 154 | - __rectSize__: CGFloat 155 | Height of the loader view 156 | - __swing__: Bool 157 | Bool to indicate if the loader has to swing when going up (small rotation, not available for the Plain loader) 158 | 159 | 160 | ##### Extra property for `Spikes` and `Rounded` loaders: 161 | 162 | - __-spikeHeight__: CGFloat 163 | Height of the spike 164 | 165 | 166 | ### Installation: 167 | #### • CocoaPods 168 | 169 | ``` 170 | use_frameworks! 171 | 172 | pod 'FillableLoaders', '~>1.3.0' 173 | ``` 174 | #### • Carthage 175 | 176 | ``` 177 | github "poolqf/FillableLoaders" ~> "1.3.0" 178 | ``` 179 | #### • Manually 180 | 181 | To manually add `FillableLoaders` to your project you just need to copy the `Source` folder files. 182 | 183 | ### How to create my own CGPath? 184 | 185 | ###### :warning: The CGPath bounds cannot exceed the bounds of the loaderView: 186 | - Width: Screen width 187 | - Height: rectSize property 188 | 189 | #### • Manually 190 | 191 | ``` swift 192 | let path = CGPathCreateMutable() 193 | CGPathMoveToPoint(path, nil, 0, height/2) 194 | CGPathAddLineToPoint(path, nil, width + 100, height/2) 195 | CGPathAddLineToPoint(path, nil, width + 100, height*2) 196 | CGPathAddLineToPoint(path, nil, 0, height*2) 197 | CGPathCloseSubpath(path) 198 | return path 199 | ``` 200 | 201 | #### • PaintCode 202 | 203 | __[PaintCode](http://www.paintcodeapp.com)__ is a realy powerful Mac app that can do a lot of things. 204 | You can just draw things, and it will __automagically__ create the code for you 205 | 206 | In this case we can use it to create BezierPaths, and extract from there the CGPath. 207 | 208 | In the case of drawing a star, it is going to give us this code: 209 | 210 | ``` swift 211 | //// Star Drawing 212 | var starPath = UIBezierPath() 213 | starPath.moveToPoint(CGPointMake(180, 25)) 214 | starPath.addLineToPoint(CGPointMake(195.16, 43.53)) 215 | starPath.addLineToPoint(CGPointMake(220.9, 49.88)) 216 | starPath.addLineToPoint(CGPointMake(204.54, 67.67)) 217 | starPath.addLineToPoint(CGPointMake(205.27, 90.12)) 218 | starPath.addLineToPoint(CGPointMake(180, 82.6)) 219 | starPath.addLineToPoint(CGPointMake(154.73, 90.12)) 220 | starPath.addLineToPoint(CGPointMake(155.46, 67.67)) 221 | starPath.addLineToPoint(CGPointMake(139.1, 49.88)) 222 | starPath.addLineToPoint(CGPointMake(164.84, 43.53)) 223 | starPath.closePath() 224 | UIColor.grayColor().setFill() 225 | starPath.fill() 226 | 227 | ``` 228 | 229 | The only thing we have to do here is extract the CGPath from the UIBezierPath like so: 230 | 231 | ``` swift 232 | let myPath = starPath.CGPath 233 | var myLoader = WavesLoader.showProgressBasedLoaderWithPath(myPath) 234 | ``` 235 | 236 | #### • SVG + PaintCode 237 | 238 | A feature that I `LOVE` from PaintCode is that you can import an .svg file, and it is going to create the code to create its BezierPath. Completely awesome. 239 | 240 | That's how I did the Github and Twitter logos, for example. 241 | 242 | ### Technical details: 243 | - Swift 3.0 244 | - Animations using CAKeyFrameAnimation 245 | 246 | ### Licenses 247 | All source code is licensed under the MIT License. 248 | 249 | If you use it, i'll be happy to know about it. 250 | 251 | ### Pol Quintana - [@poolqf](https://twitter.com/poolqf) 252 | -------------------------------------------------------------------------------- /Source/FillableLoader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FillableLoader.swift 3 | // PQFFillableLoaders 4 | // 5 | // Created by Pol Quintana on 25/7/15. 6 | // Copyright (c) 2015 Pol Quintana. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | open class FillableLoader: UIView, CAAnimationDelegate { 12 | internal var shapeLayer = CAShapeLayer() 13 | internal var strokeLayer = CAShapeLayer() 14 | internal var path: CGPath! 15 | internal var loaderView = UIView() 16 | internal var animate: Bool = false 17 | internal var extraHeight: CGFloat = 0 18 | internal var oldYPoint: CGFloat = 0 19 | internal weak var loaderSuperview: UIView? 20 | 21 | // MARK: Public Variables 22 | 23 | /// Duration of the animation (Default: 10.0) 24 | open var duration: TimeInterval = 10.0 25 | 26 | /// Loader background height (Default: ScreenHeight/6 + 30) 27 | open var rectSize: CGFloat = UIScreen.main.bounds.height/6 + 30 28 | 29 | /// A Boolean value that determines whether the loader should have a swing effect while going up (Default: true) 30 | open var swing: Bool = true 31 | 32 | /// A Boolean value that determines whether the loader movement is progress based or not (Default: false) 33 | open var progressBased: Bool = false 34 | 35 | 36 | // MARK: Custom Getters and Setters 37 | 38 | internal var _backgroundColor: UIColor? 39 | internal var _loaderColor: UIColor? 40 | internal var _loaderBackgroundColor: UIColor? 41 | internal var _loaderStrokeColor: UIColor? 42 | internal var _loaderStrokeWidth: CGFloat = 0.5 43 | internal var _loaderAlpha: CGFloat = 1.0 44 | internal var _cornerRadius: CGFloat = 0.0 45 | internal var _progress: CGFloat = 0.0 46 | internal var _mainBgColor: UIColor = UIColor(white: 0.2, alpha: 0.6) 47 | 48 | /// Background color of the view holding the loader 49 | open var mainBgColor: UIColor { 50 | get { return _mainBgColor } 51 | set { 52 | _mainBgColor = newValue 53 | super.backgroundColor = _mainBgColor 54 | } 55 | } 56 | 57 | /// Loader view background color (Default: Clear) 58 | override open var backgroundColor: UIColor? { 59 | get { return _backgroundColor } 60 | set { 61 | super.backgroundColor = mainBgColor 62 | _backgroundColor = newValue 63 | loaderView.backgroundColor = newValue 64 | loaderView.layer.backgroundColor = newValue?.cgColor 65 | } 66 | } 67 | 68 | /// Filled loader color (Default: Blue) 69 | open var loaderColor: UIColor? { 70 | get { return _loaderColor } 71 | set { 72 | _loaderColor = newValue 73 | shapeLayer.fillColor = newValue?.cgColor 74 | } 75 | } 76 | 77 | /// Unfilled loader color (Default: White) 78 | open var loaderBackgroundColor: UIColor? { 79 | get { return _loaderBackgroundColor } 80 | set { 81 | _loaderBackgroundColor = newValue 82 | strokeLayer.fillColor = newValue?.cgColor 83 | } 84 | } 85 | 86 | /// Loader outline line color (Default: Black) 87 | open var loaderStrokeColor: UIColor? { 88 | get { return _loaderStrokeColor } 89 | set { 90 | _loaderStrokeColor = newValue 91 | strokeLayer.strokeColor = newValue?.cgColor 92 | } 93 | } 94 | 95 | /// Loader outline line width (Default: 0.5) 96 | open var loaderStrokeWidth: CGFloat { 97 | get { return _loaderStrokeWidth } 98 | set { 99 | _loaderStrokeWidth = newValue 100 | strokeLayer.lineWidth = newValue 101 | } 102 | } 103 | 104 | /// Loader view alpha (Default: 1.0) 105 | open var loaderAlpha: CGFloat { 106 | get { return _loaderAlpha } 107 | set { 108 | _loaderAlpha = newValue 109 | loaderView.alpha = newValue 110 | } 111 | } 112 | 113 | /// Loader view corner radius (Default: 0.0) 114 | open var cornerRadius: CGFloat { 115 | get { return _cornerRadius } 116 | set { 117 | _cornerRadius = newValue 118 | loaderView.layer.cornerRadius = newValue 119 | } 120 | } 121 | 122 | /// Loader fill progress from 0.0 to 1.0 . It will automatically fire an animation to update the loader fill progress (Default: 0.0) 123 | open var progress: CGFloat { 124 | get { return _progress } 125 | set { 126 | if (!progressBased || newValue > 1.0 || newValue < 0.0) { return } 127 | _progress = newValue 128 | applyProgress() 129 | } 130 | } 131 | 132 | 133 | // MARK: Initializers Methods 134 | 135 | /** 136 | Creates and SHOWS a loader with the given path 137 | 138 | :param: path Loader CGPath 139 | 140 | :returns: The loader that's already being showed 141 | */ 142 | open static func showLoader(with path: CGPath, on view: UIView? = nil) -> Self { 143 | let loader = createLoader(with: path, on: view) 144 | loader.showLoader() 145 | return loader 146 | } 147 | /** 148 | Creates and SHOWS a progress based loader with the given path 149 | 150 | :param: path Loader CGPath 151 | 152 | :returns: The loader that's already being showed 153 | */ 154 | open static func showProgressBasedLoader(with path: CGPath, on view: UIView? = nil) -> Self { 155 | let loader = createProgressBasedLoader(with: path, on: view) 156 | loader.showLoader() 157 | return loader 158 | } 159 | 160 | /** 161 | Creates a loader with the given path 162 | 163 | :param: path Loader CGPath 164 | 165 | :returns: The created loader 166 | */ 167 | open static func createLoader(with path: CGPath, on view: UIView? = nil) -> Self { 168 | let loader = self.init() 169 | loader.initialSetup(on: view) 170 | loader.add(path) 171 | return loader 172 | } 173 | 174 | /** 175 | Creates a progress based loader with the given path 176 | 177 | :param: path Loader CGPath 178 | 179 | :returns: The created loader 180 | */ 181 | open static func createProgressBasedLoader(with path: CGPath, on view: UIView? = nil) -> Self { 182 | let loader = self.init() 183 | loader.progressBased = true 184 | loader.initialSetup(on: view) 185 | loader.add(path) 186 | return loader 187 | } 188 | 189 | internal func initialSetup(on view: UIView? = nil) { 190 | //Setting up frame 191 | var window = view 192 | if view == nil, let mainWindow = UIApplication.shared.delegate?.window { 193 | window = mainWindow 194 | } 195 | guard let w = window else { return } 196 | self.frame = w.frame 197 | self.center = CGPoint(x: w.bounds.midX, y: w.bounds.midY) 198 | w.addSubview(self) 199 | loaderSuperview = w 200 | 201 | //Initial Values 202 | defaultValues() 203 | 204 | //Setting up loaderView 205 | loaderView.frame = CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.width, height: rectSize) 206 | loaderView.center = CGPoint(x: frame.width/2, y: frame.height/2) 207 | loaderView.layer.cornerRadius = cornerRadius 208 | 209 | //Add loader to its superview 210 | 211 | self.addSubview(loaderView) 212 | 213 | //Initially hidden 214 | isHidden = true 215 | } 216 | 217 | internal func add(_ path: CGPath) { 218 | let bounds = path.boundingBox 219 | let center = bounds.origin 220 | let height = bounds.height 221 | let width = bounds.width 222 | assert(height <= loaderView.frame.height, "The height(\(height)) of the path has to fit the dimensions (Height: \(loaderView.frame.height) Width: \(frame.width))") 223 | assert(width <= loaderView.frame.width, "The width(\(width)) of the path has to fit the dimensions (Height: \(loaderView.frame.width) Width: \(frame.width))") 224 | var transformation = CGAffineTransform(translationX: -center.x - width/2 + loaderView.frame.width/2, y: -center.y - height/2 + loaderView.frame.height/2) 225 | self.path = path.copy(using: &transformation)! 226 | } 227 | 228 | 229 | // MARK: Prepare Loader 230 | 231 | /** 232 | Shows the loader. 233 | 234 | Atention: do not use this method after creating a loader with `showLoaderWithPath(path:)` 235 | */ 236 | open func showLoader() { 237 | alpha = 1.0 238 | isHidden = false 239 | animate = true 240 | generateLoader() 241 | startAnimating() 242 | if superview == nil { 243 | loaderSuperview?.addSubview(self) 244 | } 245 | } 246 | 247 | /** 248 | Stops loader animations and removes it from its superview 249 | */ 250 | open func removeLoader(_ animated: Bool = true) { 251 | let completion: () -> () = { 252 | self.isHidden = false 253 | self.animate = false 254 | self.removeFromSuperview() 255 | self.layer.removeAllAnimations() 256 | self.shapeLayer.removeAllAnimations() 257 | } 258 | 259 | guard animated else { 260 | completion() 261 | return 262 | } 263 | 264 | UIView.animateKeyframes(withDuration: 0.2, 265 | delay: 0, 266 | options: .beginFromCurrentState, 267 | animations: { 268 | self.alpha = 0.0 269 | }) { _ in 270 | completion() 271 | } 272 | } 273 | 274 | internal func layoutPath() { 275 | let maskingLayer = CAShapeLayer() 276 | maskingLayer.frame = loaderView.bounds 277 | maskingLayer.path = path 278 | 279 | strokeLayer = CAShapeLayer() 280 | strokeLayer.frame = loaderView.bounds 281 | strokeLayer.path = path 282 | strokeLayer.strokeColor = loaderStrokeColor?.cgColor 283 | strokeLayer.lineWidth = loaderStrokeWidth 284 | strokeLayer.fillColor = loaderBackgroundColor?.cgColor 285 | loaderView.layer.addSublayer(strokeLayer) 286 | 287 | let baseLayer = CAShapeLayer() 288 | baseLayer.frame = loaderView.bounds 289 | baseLayer.mask = maskingLayer 290 | 291 | shapeLayer.fillColor = loaderColor?.cgColor 292 | shapeLayer.lineWidth = 0.2 293 | shapeLayer.strokeColor = UIColor.black.cgColor 294 | shapeLayer.frame = loaderView.bounds 295 | oldYPoint = rectSize + extraHeight 296 | shapeLayer.position = CGPoint(x: shapeLayer.position.x, y: oldYPoint) 297 | 298 | loaderView.layer.addSublayer(baseLayer) 299 | baseLayer.addSublayer(shapeLayer) 300 | } 301 | 302 | internal func defaultValues() { 303 | duration = 10.0 304 | backgroundColor = UIColor.clear 305 | loaderColor = UIColor(red: 0.41, green: 0.728, blue: 0.892, alpha: 1.0) 306 | loaderBackgroundColor = UIColor.white 307 | loaderStrokeColor = UIColor.black 308 | loaderStrokeWidth = 0.5 309 | loaderAlpha = 1.0 310 | cornerRadius = 0.0 311 | } 312 | 313 | 314 | //MARK: Animations 315 | 316 | internal func startMoving(up: Bool) { 317 | if (progressBased) { return } 318 | let key = up ? "up" : "down" 319 | let moveAnimation: CAKeyframeAnimation = CAKeyframeAnimation(keyPath: "position.y") 320 | moveAnimation.values = up ? [loaderView.frame.height/2 + rectSize/2, loaderView.frame.height/2 - rectSize/2 - extraHeight] : [loaderView.frame.height/2 - rectSize/2 - extraHeight, loaderView.frame.height/2 + rectSize/2] 321 | moveAnimation.duration = duration 322 | moveAnimation.isRemovedOnCompletion = false 323 | moveAnimation.fillMode = kCAFillModeForwards 324 | moveAnimation.delegate = self 325 | moveAnimation.setValue(key, forKey: "animation") 326 | shapeLayer.add(moveAnimation, forKey: key) 327 | } 328 | 329 | internal func applyProgress() { 330 | let yPoint = (rectSize + extraHeight)*(1-progress) 331 | let progressAnimation: CAKeyframeAnimation = CAKeyframeAnimation(keyPath: "position.y") 332 | progressAnimation.values = [oldYPoint, yPoint] 333 | progressAnimation.duration = 0.2 334 | progressAnimation.isRemovedOnCompletion = false 335 | progressAnimation.fillMode = kCAFillModeForwards 336 | shapeLayer.add(progressAnimation, forKey: "progress") 337 | oldYPoint = yPoint 338 | } 339 | 340 | internal func startswinging() { 341 | let swingAnimation: CAKeyframeAnimation = CAKeyframeAnimation(keyPath: "transform.rotation.z") 342 | swingAnimation.values = [0, randomAngle(), -randomAngle(), randomAngle(), -randomAngle(), randomAngle(), 0] 343 | swingAnimation.duration = 12.0 344 | swingAnimation.isRemovedOnCompletion = false 345 | swingAnimation.fillMode = kCAFillModeForwards 346 | swingAnimation.delegate = self 347 | swingAnimation.setValue("rotation", forKey: "animation") 348 | shapeLayer.add(swingAnimation, forKey: "rotation") 349 | } 350 | 351 | internal func randomAngle() -> Double { 352 | return M_PI_4/(Double(arc4random_uniform(16)) + 8) 353 | } 354 | 355 | 356 | //MARK: Abstract methods 357 | 358 | internal func generateLoader() { 359 | preconditionFailure("Call this method from the desired FillableLoader type class") 360 | } 361 | 362 | internal func startAnimating() { 363 | preconditionFailure("Call this method from the desired FillableLoader type class") 364 | } 365 | } 366 | -------------------------------------------------------------------------------- /Source/PlainLoader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlainLoader.swift 3 | // PQFFillableLoaders 4 | // 5 | // Created by Pol Quintana on 2/8/15. 6 | // Copyright (c) 2015 Pol Quintana. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | open class PlainLoader: FillableLoader { 13 | 14 | internal override func generateLoader() { 15 | layoutPath() 16 | } 17 | 18 | internal override func layoutPath() { 19 | super.layoutPath() 20 | shapeLayer.path = shapePath() 21 | } 22 | 23 | 24 | // MARK: Animate 25 | 26 | internal override func startAnimating() { 27 | guard animate else { return } 28 | startMoving(up: true) 29 | } 30 | 31 | internal func shapePath() -> CGMutablePath { 32 | let width = loaderView.frame.width 33 | let height = loaderView.frame.height 34 | let path = CGMutablePath() 35 | path.move(to: CGPoint(x: 0, y: height/2)) 36 | path.addLine(to: CGPoint(x: width + 100, y: height/2)) 37 | path.addLine(to: CGPoint(x: width + 100, y: height*2)) 38 | path.addLine(to: CGPoint(x: 0, y: height*2)) 39 | path.closeSubpath() 40 | return path 41 | } 42 | 43 | } 44 | 45 | extension PlainLoader { 46 | 47 | open func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { 48 | guard animate, let key = anim.value(forKey: "animation") as? String else { return } 49 | if key == "up" { 50 | startMoving(up: false) 51 | } 52 | else if key == "down" { 53 | startMoving(up: true) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Source/RoundedLoader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RoundedLoader.swift 3 | // PQFFillableLoaders 4 | // 5 | // Created by Pol Quintana on 2/8/15. 6 | // Copyright (c) 2015 Pol Quintana. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | open class RoundedLoader: FillableLoader { 13 | 14 | /// Height of the rounded edge of the spike 15 | open var spikeHeight: CGFloat = 5.0 16 | 17 | internal override func generateLoader() { 18 | extraHeight = spikeHeight 19 | layoutPath() 20 | } 21 | 22 | internal override func layoutPath() { 23 | super.layoutPath() 24 | shapeLayer.path = shapePath() 25 | } 26 | 27 | 28 | // MARK: Animate 29 | 30 | internal override func startAnimating() { 31 | guard animate else { return } 32 | if swing { startswinging() } 33 | startMoving(up: true) 34 | } 35 | 36 | 37 | //MARK: Spikes 38 | 39 | internal func shapePath() -> CGMutablePath { 40 | let width = loaderView.frame.width 41 | let height = loaderView.frame.height 42 | let path = CGMutablePath() 43 | let waves = 32 44 | 45 | path.move(to: CGPoint(x: 0, y: height/2)) 46 | 47 | let widthDiff = width/CGFloat(waves*2) 48 | var nextCPX = widthDiff 49 | var nextCPY = height/2 + spikeHeight 50 | var nextX = nextCPX + widthDiff 51 | let nextY = height/2 52 | 53 | for i: Int in 1...waves { 54 | path.addQuadCurve(to: CGPoint(x: nextX, y: nextY) , control: CGPoint(x: nextCPX, y: nextCPY)) 55 | nextCPX = nextX + widthDiff 56 | nextCPY = (i%2 == 0) ? height/2 + spikeHeight : height/2 - spikeHeight 57 | nextX = nextCPX + widthDiff 58 | } 59 | path.addLine(to: CGPoint(x: width + 100, y: height/2)) 60 | path.addLine(to: CGPoint(x: width + 100, y: height*2)) 61 | path.addLine(to: CGPoint(x: 0, y: height*2)) 62 | path.closeSubpath() 63 | return path 64 | } 65 | } 66 | 67 | extension RoundedLoader { 68 | 69 | open func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { 70 | guard animate, let key = anim.value(forKey: "animation") as? String else { return } 71 | if key == "up" { 72 | startMoving(up: false) 73 | } 74 | else if key == "down" { 75 | startMoving(up: true) 76 | } 77 | if key == "rotation" { 78 | startswinging() 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Source/SpikeLoader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SpikeLoader.swift 3 | // PQFFillableLoaders 4 | // 5 | // Created by Pol Quintana on 2/8/15. 6 | // Copyright (c) 2015 Pol Quintana. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | open class SpikeLoader: FillableLoader { 13 | 14 | /// Height of the spike 15 | open var spikeHeight: CGFloat = 10.0 16 | 17 | internal override func generateLoader() { 18 | extraHeight = spikeHeight 19 | layoutPath() 20 | } 21 | 22 | internal override func layoutPath() { 23 | super.layoutPath() 24 | shapeLayer.path = shapePath() 25 | } 26 | 27 | 28 | // MARK: Animate 29 | 30 | internal override func startAnimating() { 31 | guard animate else { return } 32 | if swing { startswinging() } 33 | startMoving(up: true) 34 | } 35 | 36 | //MARK: Spikes 37 | internal func shapePath() -> CGMutablePath { 38 | let width = loaderView.frame.width 39 | let height = loaderView.frame.height 40 | let path = CGMutablePath() 41 | path.move(to: CGPoint(x: 0, y: height/2)) 42 | 43 | let widthDiff = width/32 44 | var nextX = widthDiff 45 | var nextY = height/2 + spikeHeight 46 | 47 | for i: Int in 1...32 { 48 | path.addLine(to: CGPoint(x: nextX, y: nextY)) 49 | nextX += widthDiff 50 | nextY += (i%2 == 0) ? spikeHeight : -spikeHeight 51 | } 52 | path.addLine(to: CGPoint(x: width + 100, y: height/2)) 53 | path.addLine(to: CGPoint(x: width + 100, y: height*2)) 54 | path.addLine(to: CGPoint(x: 0, y: height*2)) 55 | path.closeSubpath() 56 | return path 57 | } 58 | } 59 | 60 | extension SpikeLoader { 61 | 62 | open func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { 63 | guard animate, let key = anim.value(forKey: "animation") as? String else { return } 64 | if key == "up" { 65 | startMoving(up: false) 66 | } 67 | else if key == "down" { 68 | startMoving(up: true) 69 | } 70 | if key == "rotation" { 71 | startswinging() 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Source/WavesLoader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WavesLoader.swift 3 | // PQFFillableLoaders 4 | // 5 | // Created by Pol Quintana on 2/8/15. 6 | // Copyright (c) 2015 Pol Quintana. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | open class WavesLoader: FillableLoader { 13 | 14 | internal override func generateLoader() { 15 | layoutPath() 16 | } 17 | 18 | internal override func layoutPath() { 19 | super.layoutPath() 20 | shapeLayer.path = shapePath(at: 0, count: 7) 21 | } 22 | 23 | 24 | // MARK: Animate 25 | 26 | internal override func startAnimating() { 27 | guard animate else { return } 28 | if swing { startswinging() } 29 | startWaving() 30 | startMoving(up: true) 31 | } 32 | 33 | internal func startWaving() { 34 | let waveAnimation: CAKeyframeAnimation = CAKeyframeAnimation(keyPath: "path") 35 | waveAnimation.values = shapesArray(7) 36 | waveAnimation.duration = 2.0 37 | waveAnimation.isRemovedOnCompletion = false 38 | waveAnimation.fillMode = kCAFillModeForwards 39 | waveAnimation.delegate = self 40 | waveAnimation.setValue("shape", forKey: "animation") 41 | shapeLayer.add(waveAnimation, forKey: "shape") 42 | } 43 | 44 | 45 | //MARK: Waves 46 | 47 | internal func shapesArray(_ count: Int) -> [AnyObject] { 48 | let shapesArray: NSMutableArray = [] 49 | for i in 0.. CGMutablePath { 56 | let width = loaderView.frame.width 57 | let height = loaderView.frame.height 58 | let xMovement: CGFloat = (width/CGFloat(count))*CGFloat(index) 59 | let initialOrLast: Bool = index == 1 || index == count 60 | let divisions: CGFloat = 8 61 | var variation: CGFloat = 10 62 | 63 | let path = CGMutablePath() 64 | path.move(to: CGPoint(x: -width, y: height/2)) 65 | 66 | // First wave 67 | if !initialOrLast { variation = randomFloat() } 68 | 69 | path.addQuadCurve(to: CGPoint(x: -width + width/4 + xMovement, y: height/2), control: CGPoint(x: -width + width/divisions + xMovement, y: height/2 + variation)) 70 | if !initialOrLast { variation = randomFloat() } 71 | path.addQuadCurve(to: CGPoint(x: -width + width/2 + xMovement, y: height/2), control: CGPoint(x: -width + width/divisions*3 + xMovement, y: height/2 - variation)) 72 | 73 | // Second wave 74 | if !initialOrLast { variation = randomFloat() } 75 | path.addQuadCurve(to: CGPoint(x: -width + width/4*3 + xMovement, y: height/2), control: CGPoint(x: -width + width/divisions*5 + xMovement, y: height/2 + variation)) 76 | if !initialOrLast { variation = randomFloat() } 77 | path.addQuadCurve(to: CGPoint(x: -width + width + xMovement, y: height/2), control: CGPoint(x: -width + width/divisions*7 + xMovement, y: height/2 - variation)) 78 | 79 | // Third wave 80 | if !initialOrLast { variation = randomFloat() } 81 | path.addQuadCurve(to: CGPoint(x: width/4 + xMovement, y: height/2), control: CGPoint(x: width/divisions + xMovement, y: height/2 + variation)) 82 | if !initialOrLast { variation = randomFloat() } 83 | path.addQuadCurve(to: CGPoint(x: width/2 + xMovement, y: height/2), control: CGPoint(x: width/divisions*3 + xMovement, y: height/2 - variation)) 84 | 85 | // Forth wave 86 | if !initialOrLast { variation = randomFloat() } 87 | path.addQuadCurve(to: CGPoint(x: width/4*3 + xMovement, y: height/2), control: CGPoint(x: width/divisions*5 + xMovement, y: height/2 + variation)) 88 | if !initialOrLast { variation = randomFloat() } 89 | path.addQuadCurve(to: CGPoint(x: width + xMovement, y: height/2), control: CGPoint(x: width/divisions*7 + xMovement, y: height/2 - variation)) 90 | 91 | // Closing path 92 | path.addLine(to: CGPoint(x: width + 100, y: height/2)) 93 | path.addLine(to: CGPoint(x: width + 100, y: height*2)) 94 | path.addLine(to: CGPoint(x: -width, y: height*2)) 95 | path.closeSubpath() 96 | return path 97 | } 98 | 99 | //MARK: Helpers 100 | 101 | internal func randomFloat() -> CGFloat { 102 | return CGFloat(arc4random_uniform(10) + 5) 103 | } 104 | } 105 | 106 | extension WavesLoader { 107 | 108 | open func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { 109 | guard animate, let key = anim.value(forKey: "animation") as? String else { return } 110 | if key == "up" { 111 | startMoving(up: false) 112 | } 113 | else if key == "down" { 114 | startMoving(up: true) 115 | } 116 | if key == "shape" { 117 | startWaving() 118 | } 119 | if key == "rotation" { 120 | startswinging() 121 | } 122 | } 123 | } 124 | --------------------------------------------------------------------------------