├── .github └── FUNDING.yml ├── .gitignore ├── CocoaButton.podspec ├── CocoaButton.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── edgaraszigis.xcuserdatad │ │ └── UserInterfaceState.xcuserstate ├── xcshareddata │ └── xcschemes │ │ └── CocoaButton.xcscheme └── xcuserdata │ └── edgaraszigis.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── CocoaButton ├── CocoaButton.h ├── CocoaButton.swift ├── Common │ ├── AnimationHelper.swift │ ├── Pose.swift │ └── SpinnerView.swift └── Info.plist ├── CocoaButtonTest ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── AttributedLabel.swift ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── ViewController.swift └── ViewControllerWithoutStoryboard.swift ├── LICENSE ├── README.md └── sample.gif /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: edgar-zigis 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /CocoaButton.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = "CocoaButton" 3 | spec.version = "1.0.0" 4 | spec.summary = "Interactive UIButton with loading capability." 5 | 6 | spec.homepage = "https://github.com/edgar-zigis/CocoaButton" 7 | spec.screenshots = "https://raw.githubusercontent.com/edgar-zigis/CocoaButton/master/sample.gif" 8 | 9 | spec.license = { :type => 'MIT', :file => './LICENSE' } 10 | 11 | spec.author = "Edgar Žigis" 12 | 13 | spec.platform = :ios 14 | spec.ios.deployment_target = '11.0' 15 | spec.swift_version = '5.0' 16 | 17 | spec.source = { :git => "https://github.com/edgar-zigis/CocoaButton.git", :tag => "#{spec.version}" } 18 | 19 | spec.source_files = "CocoaButton/**/*.{swift}" 20 | end 21 | -------------------------------------------------------------------------------- /CocoaButton.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C0AF4B142358FC16008C0683 /* ViewControllerWithoutStoryboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0AF4B132358FC16008C0683 /* ViewControllerWithoutStoryboard.swift */; }; 11 | C0AF4B1623590441008C0683 /* AttributedLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0AF4B1523590441008C0683 /* AttributedLabel.swift */; }; 12 | C0D153722358F899002BED5C /* CocoaButton.h in Headers */ = {isa = PBXBuildFile; fileRef = C0D153702358F899002BED5C /* CocoaButton.h */; settings = {ATTRIBUTES = (Public, ); }; }; 13 | C0D1537A2358F8D0002BED5C /* Pose.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0D153792358F8D0002BED5C /* Pose.swift */; }; 14 | C0D1537C2358F8F3002BED5C /* AnimationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0D1537B2358F8F3002BED5C /* AnimationHelper.swift */; }; 15 | C0D1537E2358F90F002BED5C /* SpinnerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0D1537D2358F90F002BED5C /* SpinnerView.swift */; }; 16 | C0D153802358F92E002BED5C /* CocoaButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0D1537F2358F92E002BED5C /* CocoaButton.swift */; }; 17 | C0D153882358F97D002BED5C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0D153872358F97D002BED5C /* AppDelegate.swift */; }; 18 | C0D1538C2358F97D002BED5C /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0D1538B2358F97D002BED5C /* ViewController.swift */; }; 19 | C0D1538F2358F97D002BED5C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C0D1538D2358F97D002BED5C /* Main.storyboard */; }; 20 | C0D153912358F97F002BED5C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C0D153902358F97F002BED5C /* Assets.xcassets */; }; 21 | C0D153942358F97F002BED5C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C0D153922358F97F002BED5C /* LaunchScreen.storyboard */; }; 22 | C0D1539A2358F9AC002BED5C /* CocoaButton.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0D1536D2358F899002BED5C /* CocoaButton.framework */; }; 23 | C0D1539B2358F9AC002BED5C /* CocoaButton.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C0D1536D2358F899002BED5C /* CocoaButton.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXContainerItemProxy section */ 27 | C0D1539C2358F9AC002BED5C /* PBXContainerItemProxy */ = { 28 | isa = PBXContainerItemProxy; 29 | containerPortal = C0D153642358F899002BED5C /* Project object */; 30 | proxyType = 1; 31 | remoteGlobalIDString = C0D1536C2358F899002BED5C; 32 | remoteInfo = CocoaButton; 33 | }; 34 | /* End PBXContainerItemProxy section */ 35 | 36 | /* Begin PBXCopyFilesBuildPhase section */ 37 | C0D1539E2358F9AC002BED5C /* Embed Frameworks */ = { 38 | isa = PBXCopyFilesBuildPhase; 39 | buildActionMask = 2147483647; 40 | dstPath = ""; 41 | dstSubfolderSpec = 10; 42 | files = ( 43 | C0D1539B2358F9AC002BED5C /* CocoaButton.framework in Embed Frameworks */, 44 | ); 45 | name = "Embed Frameworks"; 46 | runOnlyForDeploymentPostprocessing = 0; 47 | }; 48 | /* End PBXCopyFilesBuildPhase section */ 49 | 50 | /* Begin PBXFileReference section */ 51 | C0AF4B132358FC16008C0683 /* ViewControllerWithoutStoryboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewControllerWithoutStoryboard.swift; sourceTree = ""; }; 52 | C0AF4B1523590441008C0683 /* AttributedLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedLabel.swift; sourceTree = ""; }; 53 | C0D1536D2358F899002BED5C /* CocoaButton.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CocoaButton.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | C0D153702358F899002BED5C /* CocoaButton.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CocoaButton.h; sourceTree = ""; }; 55 | C0D153712358F899002BED5C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 56 | C0D153792358F8D0002BED5C /* Pose.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pose.swift; sourceTree = ""; }; 57 | C0D1537B2358F8F3002BED5C /* AnimationHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimationHelper.swift; sourceTree = ""; }; 58 | C0D1537D2358F90F002BED5C /* SpinnerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpinnerView.swift; sourceTree = ""; }; 59 | C0D1537F2358F92E002BED5C /* CocoaButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CocoaButton.swift; sourceTree = ""; }; 60 | C0D153852358F97D002BED5C /* CocoaButtonTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CocoaButtonTest.app; sourceTree = BUILT_PRODUCTS_DIR; }; 61 | C0D153872358F97D002BED5C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 62 | C0D1538B2358F97D002BED5C /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 63 | C0D1538E2358F97D002BED5C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 64 | C0D153902358F97F002BED5C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 65 | C0D153932358F97F002BED5C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 66 | C0D153952358F97F002BED5C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 67 | /* End PBXFileReference section */ 68 | 69 | /* Begin PBXFrameworksBuildPhase section */ 70 | C0D1536A2358F899002BED5C /* Frameworks */ = { 71 | isa = PBXFrameworksBuildPhase; 72 | buildActionMask = 2147483647; 73 | files = ( 74 | ); 75 | runOnlyForDeploymentPostprocessing = 0; 76 | }; 77 | C0D153822358F97D002BED5C /* Frameworks */ = { 78 | isa = PBXFrameworksBuildPhase; 79 | buildActionMask = 2147483647; 80 | files = ( 81 | C0D1539A2358F9AC002BED5C /* CocoaButton.framework in Frameworks */, 82 | ); 83 | runOnlyForDeploymentPostprocessing = 0; 84 | }; 85 | /* End PBXFrameworksBuildPhase section */ 86 | 87 | /* Begin PBXGroup section */ 88 | C0D153632358F899002BED5C = { 89 | isa = PBXGroup; 90 | children = ( 91 | C0D1536F2358F899002BED5C /* CocoaButton */, 92 | C0D153862358F97D002BED5C /* CocoaButtonTest */, 93 | C0D1536E2358F899002BED5C /* Products */, 94 | C0D153992358F9AC002BED5C /* Frameworks */, 95 | ); 96 | sourceTree = ""; 97 | }; 98 | C0D1536E2358F899002BED5C /* Products */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | C0D1536D2358F899002BED5C /* CocoaButton.framework */, 102 | C0D153852358F97D002BED5C /* CocoaButtonTest.app */, 103 | ); 104 | name = Products; 105 | sourceTree = ""; 106 | }; 107 | C0D1536F2358F899002BED5C /* CocoaButton */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | C0D153782358F8BD002BED5C /* Common */, 111 | C0D153702358F899002BED5C /* CocoaButton.h */, 112 | C0D153712358F899002BED5C /* Info.plist */, 113 | C0D1537F2358F92E002BED5C /* CocoaButton.swift */, 114 | ); 115 | path = CocoaButton; 116 | sourceTree = ""; 117 | }; 118 | C0D153782358F8BD002BED5C /* Common */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | C0D1537B2358F8F3002BED5C /* AnimationHelper.swift */, 122 | C0D153792358F8D0002BED5C /* Pose.swift */, 123 | C0D1537D2358F90F002BED5C /* SpinnerView.swift */, 124 | ); 125 | path = Common; 126 | sourceTree = ""; 127 | }; 128 | C0D153862358F97D002BED5C /* CocoaButtonTest */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | C0D153872358F97D002BED5C /* AppDelegate.swift */, 132 | C0D1538B2358F97D002BED5C /* ViewController.swift */, 133 | C0AF4B132358FC16008C0683 /* ViewControllerWithoutStoryboard.swift */, 134 | C0D1538D2358F97D002BED5C /* Main.storyboard */, 135 | C0D153902358F97F002BED5C /* Assets.xcassets */, 136 | C0D153922358F97F002BED5C /* LaunchScreen.storyboard */, 137 | C0D153952358F97F002BED5C /* Info.plist */, 138 | C0AF4B1523590441008C0683 /* AttributedLabel.swift */, 139 | ); 140 | path = CocoaButtonTest; 141 | sourceTree = ""; 142 | }; 143 | C0D153992358F9AC002BED5C /* Frameworks */ = { 144 | isa = PBXGroup; 145 | children = ( 146 | ); 147 | name = Frameworks; 148 | sourceTree = ""; 149 | }; 150 | /* End PBXGroup section */ 151 | 152 | /* Begin PBXHeadersBuildPhase section */ 153 | C0D153682358F899002BED5C /* Headers */ = { 154 | isa = PBXHeadersBuildPhase; 155 | buildActionMask = 2147483647; 156 | files = ( 157 | C0D153722358F899002BED5C /* CocoaButton.h in Headers */, 158 | ); 159 | runOnlyForDeploymentPostprocessing = 0; 160 | }; 161 | /* End PBXHeadersBuildPhase section */ 162 | 163 | /* Begin PBXNativeTarget section */ 164 | C0D1536C2358F899002BED5C /* CocoaButton */ = { 165 | isa = PBXNativeTarget; 166 | buildConfigurationList = C0D153752358F899002BED5C /* Build configuration list for PBXNativeTarget "CocoaButton" */; 167 | buildPhases = ( 168 | C0D153682358F899002BED5C /* Headers */, 169 | C0D153692358F899002BED5C /* Sources */, 170 | C0D1536A2358F899002BED5C /* Frameworks */, 171 | C0D1536B2358F899002BED5C /* Resources */, 172 | ); 173 | buildRules = ( 174 | ); 175 | dependencies = ( 176 | ); 177 | name = CocoaButton; 178 | productName = CocoaButton; 179 | productReference = C0D1536D2358F899002BED5C /* CocoaButton.framework */; 180 | productType = "com.apple.product-type.framework"; 181 | }; 182 | C0D153842358F97D002BED5C /* CocoaButtonTest */ = { 183 | isa = PBXNativeTarget; 184 | buildConfigurationList = C0D153962358F97F002BED5C /* Build configuration list for PBXNativeTarget "CocoaButtonTest" */; 185 | buildPhases = ( 186 | C0D153812358F97D002BED5C /* Sources */, 187 | C0D153822358F97D002BED5C /* Frameworks */, 188 | C0D153832358F97D002BED5C /* Resources */, 189 | C0D1539E2358F9AC002BED5C /* Embed Frameworks */, 190 | ); 191 | buildRules = ( 192 | ); 193 | dependencies = ( 194 | C0D1539D2358F9AC002BED5C /* PBXTargetDependency */, 195 | ); 196 | name = CocoaButtonTest; 197 | productName = CocoaButtonTest; 198 | productReference = C0D153852358F97D002BED5C /* CocoaButtonTest.app */; 199 | productType = "com.apple.product-type.application"; 200 | }; 201 | /* End PBXNativeTarget section */ 202 | 203 | /* Begin PBXProject section */ 204 | C0D153642358F899002BED5C /* Project object */ = { 205 | isa = PBXProject; 206 | attributes = { 207 | LastSwiftUpdateCheck = 1110; 208 | LastUpgradeCheck = 1110; 209 | ORGANIZATIONNAME = "Edgar Žigis"; 210 | TargetAttributes = { 211 | C0D1536C2358F899002BED5C = { 212 | CreatedOnToolsVersion = 11.1; 213 | LastSwiftMigration = 1110; 214 | }; 215 | C0D153842358F97D002BED5C = { 216 | CreatedOnToolsVersion = 11.1; 217 | }; 218 | }; 219 | }; 220 | buildConfigurationList = C0D153672358F899002BED5C /* Build configuration list for PBXProject "CocoaButton" */; 221 | compatibilityVersion = "Xcode 9.3"; 222 | developmentRegion = en; 223 | hasScannedForEncodings = 0; 224 | knownRegions = ( 225 | en, 226 | Base, 227 | ); 228 | mainGroup = C0D153632358F899002BED5C; 229 | productRefGroup = C0D1536E2358F899002BED5C /* Products */; 230 | projectDirPath = ""; 231 | projectRoot = ""; 232 | targets = ( 233 | C0D1536C2358F899002BED5C /* CocoaButton */, 234 | C0D153842358F97D002BED5C /* CocoaButtonTest */, 235 | ); 236 | }; 237 | /* End PBXProject section */ 238 | 239 | /* Begin PBXResourcesBuildPhase section */ 240 | C0D1536B2358F899002BED5C /* Resources */ = { 241 | isa = PBXResourcesBuildPhase; 242 | buildActionMask = 2147483647; 243 | files = ( 244 | ); 245 | runOnlyForDeploymentPostprocessing = 0; 246 | }; 247 | C0D153832358F97D002BED5C /* Resources */ = { 248 | isa = PBXResourcesBuildPhase; 249 | buildActionMask = 2147483647; 250 | files = ( 251 | C0D153942358F97F002BED5C /* LaunchScreen.storyboard in Resources */, 252 | C0D153912358F97F002BED5C /* Assets.xcassets in Resources */, 253 | C0D1538F2358F97D002BED5C /* Main.storyboard in Resources */, 254 | ); 255 | runOnlyForDeploymentPostprocessing = 0; 256 | }; 257 | /* End PBXResourcesBuildPhase section */ 258 | 259 | /* Begin PBXSourcesBuildPhase section */ 260 | C0D153692358F899002BED5C /* Sources */ = { 261 | isa = PBXSourcesBuildPhase; 262 | buildActionMask = 2147483647; 263 | files = ( 264 | C0D1537C2358F8F3002BED5C /* AnimationHelper.swift in Sources */, 265 | C0D153802358F92E002BED5C /* CocoaButton.swift in Sources */, 266 | C0D1537E2358F90F002BED5C /* SpinnerView.swift in Sources */, 267 | C0D1537A2358F8D0002BED5C /* Pose.swift in Sources */, 268 | ); 269 | runOnlyForDeploymentPostprocessing = 0; 270 | }; 271 | C0D153812358F97D002BED5C /* Sources */ = { 272 | isa = PBXSourcesBuildPhase; 273 | buildActionMask = 2147483647; 274 | files = ( 275 | C0AF4B1623590441008C0683 /* AttributedLabel.swift in Sources */, 276 | C0D1538C2358F97D002BED5C /* ViewController.swift in Sources */, 277 | C0AF4B142358FC16008C0683 /* ViewControllerWithoutStoryboard.swift in Sources */, 278 | C0D153882358F97D002BED5C /* AppDelegate.swift in Sources */, 279 | ); 280 | runOnlyForDeploymentPostprocessing = 0; 281 | }; 282 | /* End PBXSourcesBuildPhase section */ 283 | 284 | /* Begin PBXTargetDependency section */ 285 | C0D1539D2358F9AC002BED5C /* PBXTargetDependency */ = { 286 | isa = PBXTargetDependency; 287 | target = C0D1536C2358F899002BED5C /* CocoaButton */; 288 | targetProxy = C0D1539C2358F9AC002BED5C /* PBXContainerItemProxy */; 289 | }; 290 | /* End PBXTargetDependency section */ 291 | 292 | /* Begin PBXVariantGroup section */ 293 | C0D1538D2358F97D002BED5C /* Main.storyboard */ = { 294 | isa = PBXVariantGroup; 295 | children = ( 296 | C0D1538E2358F97D002BED5C /* Base */, 297 | ); 298 | name = Main.storyboard; 299 | sourceTree = ""; 300 | }; 301 | C0D153922358F97F002BED5C /* LaunchScreen.storyboard */ = { 302 | isa = PBXVariantGroup; 303 | children = ( 304 | C0D153932358F97F002BED5C /* Base */, 305 | ); 306 | name = LaunchScreen.storyboard; 307 | sourceTree = ""; 308 | }; 309 | /* End PBXVariantGroup section */ 310 | 311 | /* Begin XCBuildConfiguration section */ 312 | C0D153732358F899002BED5C /* Debug */ = { 313 | isa = XCBuildConfiguration; 314 | buildSettings = { 315 | ALWAYS_SEARCH_USER_PATHS = NO; 316 | CLANG_ANALYZER_NONNULL = YES; 317 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 318 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 319 | CLANG_CXX_LIBRARY = "libc++"; 320 | CLANG_ENABLE_MODULES = YES; 321 | CLANG_ENABLE_OBJC_ARC = YES; 322 | CLANG_ENABLE_OBJC_WEAK = 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_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 342 | CLANG_WARN_UNREACHABLE_CODE = YES; 343 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 344 | COPY_PHASE_STRIP = NO; 345 | CURRENT_PROJECT_VERSION = 1; 346 | DEBUG_INFORMATION_FORMAT = dwarf; 347 | ENABLE_STRICT_OBJC_MSGSEND = YES; 348 | ENABLE_TESTABILITY = YES; 349 | GCC_C_LANGUAGE_STANDARD = gnu11; 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 = 13.1; 364 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 365 | MTL_FAST_MATH = YES; 366 | ONLY_ACTIVE_ARCH = YES; 367 | SDKROOT = iphoneos; 368 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 369 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 370 | VERSIONING_SYSTEM = "apple-generic"; 371 | VERSION_INFO_PREFIX = ""; 372 | }; 373 | name = Debug; 374 | }; 375 | C0D153742358F899002BED5C /* Release */ = { 376 | isa = XCBuildConfiguration; 377 | buildSettings = { 378 | ALWAYS_SEARCH_USER_PATHS = NO; 379 | CLANG_ANALYZER_NONNULL = YES; 380 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 381 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 382 | CLANG_CXX_LIBRARY = "libc++"; 383 | CLANG_ENABLE_MODULES = YES; 384 | CLANG_ENABLE_OBJC_ARC = YES; 385 | CLANG_ENABLE_OBJC_WEAK = YES; 386 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 387 | CLANG_WARN_BOOL_CONVERSION = YES; 388 | CLANG_WARN_COMMA = YES; 389 | CLANG_WARN_CONSTANT_CONVERSION = YES; 390 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 391 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 392 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 393 | CLANG_WARN_EMPTY_BODY = YES; 394 | CLANG_WARN_ENUM_CONVERSION = YES; 395 | CLANG_WARN_INFINITE_RECURSION = YES; 396 | CLANG_WARN_INT_CONVERSION = YES; 397 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 398 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 399 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 400 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 401 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 402 | CLANG_WARN_STRICT_PROTOTYPES = YES; 403 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 404 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 405 | CLANG_WARN_UNREACHABLE_CODE = YES; 406 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 407 | COPY_PHASE_STRIP = NO; 408 | CURRENT_PROJECT_VERSION = 1; 409 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 410 | ENABLE_NS_ASSERTIONS = NO; 411 | ENABLE_STRICT_OBJC_MSGSEND = YES; 412 | GCC_C_LANGUAGE_STANDARD = gnu11; 413 | GCC_NO_COMMON_BLOCKS = YES; 414 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 415 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 416 | GCC_WARN_UNDECLARED_SELECTOR = YES; 417 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 418 | GCC_WARN_UNUSED_FUNCTION = YES; 419 | GCC_WARN_UNUSED_VARIABLE = YES; 420 | IPHONEOS_DEPLOYMENT_TARGET = 13.1; 421 | MTL_ENABLE_DEBUG_INFO = NO; 422 | MTL_FAST_MATH = YES; 423 | SDKROOT = iphoneos; 424 | SWIFT_COMPILATION_MODE = wholemodule; 425 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 426 | VALIDATE_PRODUCT = YES; 427 | VERSIONING_SYSTEM = "apple-generic"; 428 | VERSION_INFO_PREFIX = ""; 429 | }; 430 | name = Release; 431 | }; 432 | C0D153762358F899002BED5C /* Debug */ = { 433 | isa = XCBuildConfiguration; 434 | buildSettings = { 435 | CLANG_ENABLE_MODULES = YES; 436 | CODE_SIGN_STYLE = Automatic; 437 | DEFINES_MODULE = YES; 438 | DEVELOPMENT_TEAM = 22JT5762MG; 439 | DYLIB_COMPATIBILITY_VERSION = 1; 440 | DYLIB_CURRENT_VERSION = 1; 441 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 442 | INFOPLIST_FILE = CocoaButton/Info.plist; 443 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 444 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 445 | LD_RUNPATH_SEARCH_PATHS = ( 446 | "$(inherited)", 447 | "@executable_path/Frameworks", 448 | "@loader_path/Frameworks", 449 | ); 450 | PRODUCT_BUNDLE_IDENTIFIER = com.zigis.CocoaButton; 451 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 452 | SKIP_INSTALL = YES; 453 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 454 | SWIFT_VERSION = 5.0; 455 | TARGETED_DEVICE_FAMILY = "1,2"; 456 | }; 457 | name = Debug; 458 | }; 459 | C0D153772358F899002BED5C /* Release */ = { 460 | isa = XCBuildConfiguration; 461 | buildSettings = { 462 | CLANG_ENABLE_MODULES = YES; 463 | CODE_SIGN_STYLE = Automatic; 464 | DEFINES_MODULE = YES; 465 | DEVELOPMENT_TEAM = 22JT5762MG; 466 | DYLIB_COMPATIBILITY_VERSION = 1; 467 | DYLIB_CURRENT_VERSION = 1; 468 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 469 | INFOPLIST_FILE = CocoaButton/Info.plist; 470 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 471 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 472 | LD_RUNPATH_SEARCH_PATHS = ( 473 | "$(inherited)", 474 | "@executable_path/Frameworks", 475 | "@loader_path/Frameworks", 476 | ); 477 | PRODUCT_BUNDLE_IDENTIFIER = com.zigis.CocoaButton; 478 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 479 | SKIP_INSTALL = YES; 480 | SWIFT_VERSION = 5.0; 481 | TARGETED_DEVICE_FAMILY = "1,2"; 482 | }; 483 | name = Release; 484 | }; 485 | C0D153972358F97F002BED5C /* Debug */ = { 486 | isa = XCBuildConfiguration; 487 | buildSettings = { 488 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 489 | CODE_SIGN_STYLE = Automatic; 490 | DEVELOPMENT_TEAM = 22JT5762MG; 491 | INFOPLIST_FILE = CocoaButtonTest/Info.plist; 492 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 493 | LD_RUNPATH_SEARCH_PATHS = ( 494 | "$(inherited)", 495 | "@executable_path/Frameworks", 496 | ); 497 | PRODUCT_BUNDLE_IDENTIFIER = com.zigis.CocoaButtonTest; 498 | PRODUCT_NAME = "$(TARGET_NAME)"; 499 | SWIFT_VERSION = 5.0; 500 | TARGETED_DEVICE_FAMILY = "1,2"; 501 | }; 502 | name = Debug; 503 | }; 504 | C0D153982358F97F002BED5C /* Release */ = { 505 | isa = XCBuildConfiguration; 506 | buildSettings = { 507 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 508 | CODE_SIGN_STYLE = Automatic; 509 | DEVELOPMENT_TEAM = 22JT5762MG; 510 | INFOPLIST_FILE = CocoaButtonTest/Info.plist; 511 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 512 | LD_RUNPATH_SEARCH_PATHS = ( 513 | "$(inherited)", 514 | "@executable_path/Frameworks", 515 | ); 516 | PRODUCT_BUNDLE_IDENTIFIER = com.zigis.CocoaButtonTest; 517 | PRODUCT_NAME = "$(TARGET_NAME)"; 518 | SWIFT_VERSION = 5.0; 519 | TARGETED_DEVICE_FAMILY = "1,2"; 520 | }; 521 | name = Release; 522 | }; 523 | /* End XCBuildConfiguration section */ 524 | 525 | /* Begin XCConfigurationList section */ 526 | C0D153672358F899002BED5C /* Build configuration list for PBXProject "CocoaButton" */ = { 527 | isa = XCConfigurationList; 528 | buildConfigurations = ( 529 | C0D153732358F899002BED5C /* Debug */, 530 | C0D153742358F899002BED5C /* Release */, 531 | ); 532 | defaultConfigurationIsVisible = 0; 533 | defaultConfigurationName = Release; 534 | }; 535 | C0D153752358F899002BED5C /* Build configuration list for PBXNativeTarget "CocoaButton" */ = { 536 | isa = XCConfigurationList; 537 | buildConfigurations = ( 538 | C0D153762358F899002BED5C /* Debug */, 539 | C0D153772358F899002BED5C /* Release */, 540 | ); 541 | defaultConfigurationIsVisible = 0; 542 | defaultConfigurationName = Release; 543 | }; 544 | C0D153962358F97F002BED5C /* Build configuration list for PBXNativeTarget "CocoaButtonTest" */ = { 545 | isa = XCConfigurationList; 546 | buildConfigurations = ( 547 | C0D153972358F97F002BED5C /* Debug */, 548 | C0D153982358F97F002BED5C /* Release */, 549 | ); 550 | defaultConfigurationIsVisible = 0; 551 | defaultConfigurationName = Release; 552 | }; 553 | /* End XCConfigurationList section */ 554 | }; 555 | rootObject = C0D153642358F899002BED5C /* Project object */; 556 | } 557 | -------------------------------------------------------------------------------- /CocoaButton.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CocoaButton.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CocoaButton.xcodeproj/project.xcworkspace/xcuserdata/edgaraszigis.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edgar-zigis/CocoaButton/6b2fcd9dd9bdfe3fecc9d9cb87d55b0e4f8415cf/CocoaButton.xcodeproj/project.xcworkspace/xcuserdata/edgaraszigis.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /CocoaButton.xcodeproj/xcshareddata/xcschemes/CocoaButton.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /CocoaButton.xcodeproj/xcuserdata/edgaraszigis.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | CocoaButton.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | CocoaButtonTest.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 1 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /CocoaButton/CocoaButton.h: -------------------------------------------------------------------------------- 1 | // 2 | // CocoaButton.h 3 | // CocoaButton 4 | // 5 | // Created by Edgar Žigis on 17/10/2019. 6 | // Copyright © 2019 Edgar Žigis. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for CocoaButton. 12 | FOUNDATION_EXPORT double CocoaButtonVersionNumber; 13 | 14 | //! Project version string for CocoaButton. 15 | FOUNDATION_EXPORT const unsigned char CocoaButtonVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /CocoaButton/CocoaButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CocoaButton.swift 3 | // CocoaButton 4 | // 5 | // Created by Edgar Žigis on 17/10/2019. 6 | // Copyright © 2019 Edgar Žigis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @IBDesignable 12 | public class CocoaButton: UIButton { 13 | 14 | override open var backgroundColor: UIColor? { 15 | didSet { configureButton() } 16 | } 17 | 18 | /** 19 | * Sets button shadow 20 | */ 21 | @IBInspectable 22 | open var shadowColor: UIColor? { 23 | didSet { configureButton() } 24 | } 25 | 26 | private var originalFrame = CGRect.zero 27 | private var activeFrame: CGRect? 28 | 29 | private var isAnimating = false 30 | private var isLoading = false 31 | 32 | private let spinner = SpinnerView() 33 | 34 | // MARK: - Public functions - 35 | 36 | public func startLoading() { 37 | guard !isLoading else { return } 38 | originalFrame = frame 39 | UIView.animate(withDuration: 0.4, animations: { 40 | self.frame.origin.x = 41 | self.originalFrame.origin.x + 42 | (self.originalFrame.size.width - self.frame.size.height) / 2 43 | self.frame.size.width = self.frame.size.height 44 | self.layer.cornerRadius = self.frame.height / 2 45 | self.titleLabel?.alpha = 0 46 | }) { _ in 47 | self.addSubview(self.spinner) 48 | self.activeFrame = self.frame 49 | self.spinner.animate() 50 | self.spinner.alpha = 0 51 | UIView.animate(withDuration: 0.1) { 52 | self.spinner.alpha = 1 53 | } 54 | } 55 | isLoading = true 56 | } 57 | 58 | public func stopLoading() { 59 | guard isLoading else { return } 60 | self.activeFrame = nil 61 | UIView.animate(withDuration: 0.1, animations: { 62 | self.spinner.alpha = 0 63 | }) { _ in 64 | self.spinner.removeFromSuperview() 65 | } 66 | UIView.animate(withDuration: 0.4, animations: { 67 | self.frame = self.originalFrame 68 | self.layer.cornerRadius = 11 69 | self.titleLabel?.alpha = 1 70 | }) { _ in 71 | self.isLoading = false 72 | } 73 | } 74 | 75 | // MARK: - Internal functions - 76 | 77 | private func configureButton() { 78 | layer.shadowColor = shadowColor?.cgColor 79 | layer.shadowOffset = CGSize(width: 0, height: 4) 80 | layer.shadowOpacity = 1 81 | layer.shadowRadius = 8 82 | layer.cornerRadius = 11 83 | titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .medium) 84 | } 85 | 86 | public override func layoutSubviews() { 87 | super.layoutSubviews() 88 | if let activeFrame = activeFrame { 89 | frame = activeFrame 90 | } 91 | spinner.frame = CGRect(x: 10, y: 10, width: frame.height - 20, height: frame.height - 20) 92 | } 93 | 94 | public override func touchesBegan(_ touches: Set, with event: UIEvent?) { 95 | defer { super.touchesBegan(touches, with: event) } 96 | guard !isAnimating, !isLoading else { return } 97 | 98 | AnimationHelper.animateShadowOpacity(forLayer: layer, fromValue: 1, toValue: 0, duration: 0.2) 99 | UIView.animate(withDuration: 0.2) { 100 | self.transform = self.transform.scaledBy(x: 0.98, y: 0.98) 101 | self.titleLabel?.alpha = 0.38 102 | } 103 | isAnimating = true 104 | } 105 | 106 | public override func touchesCancelled(_ touches: Set, with event: UIEvent?) { 107 | AnimationHelper.animateShadowOpacity(forLayer: layer, fromValue: 0, toValue: 1, duration: 0.3) 108 | UIView.animate(withDuration: 0.3, animations: { 109 | if !self.isLoading { 110 | self.transform = .identity 111 | self.titleLabel?.alpha = 1 112 | } 113 | }) { _ in 114 | super.touchesCancelled(touches, with: event) 115 | self.isAnimating = false 116 | } 117 | } 118 | 119 | override public func touchesEnded(_ touches: Set, with event: UIEvent?) { 120 | AnimationHelper.animateShadowOpacity(forLayer: layer, fromValue: 0, toValue: 1, duration: 0.3) 121 | UIView.animate(withDuration: 0.3, animations: { 122 | if !self.isLoading { 123 | self.transform = .identity 124 | self.titleLabel?.alpha = 1 125 | } 126 | }) { _ in 127 | self.sendActions(for: .touchUpInside) 128 | self.isAnimating = false 129 | } 130 | } 131 | 132 | // MARK: - Init - 133 | 134 | override init(frame: CGRect) { 135 | super.init(frame: frame) 136 | configureButton() 137 | } 138 | 139 | required init?(coder aDecoder: NSCoder) { 140 | super.init(coder: aDecoder) 141 | configureButton() 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /CocoaButton/Common/AnimationHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnimationHelper.swift 3 | // CocoaButton 4 | // 5 | // Created by Edgar Žigis on 17/10/2019. 6 | // Copyright © 2019 Edgar Žigis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AnimationHelper { 12 | 13 | class func animateShadowOpacity(forLayer: CALayer, fromValue: Float, toValue: Float, duration: CFTimeInterval) { 14 | let animation = CABasicAnimation(keyPath: "shadowOpacity") 15 | animation.fromValue = fromValue 16 | animation.toValue = toValue 17 | animation.duration = duration 18 | forLayer.add(animation, forKey: animation.keyPath) 19 | forLayer.shadowOpacity = toValue 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /CocoaButton/Common/Pose.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Pose.swift 3 | // CocoaButton 4 | // 5 | // Created by Edgar Žigis on 17/10/2019. 6 | // Copyright © 2019 Edgar Žigis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct Pose { 12 | 13 | let secondsSincePriorPose: CFTimeInterval 14 | let start: CGFloat 15 | let length: CGFloat 16 | 17 | init(_ secondsSincePriorPose: CFTimeInterval, _ start: CGFloat, _ length: CGFloat) { 18 | self.secondsSincePriorPose = secondsSincePriorPose 19 | self.start = start 20 | self.length = length 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /CocoaButton/Common/SpinnerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SpinnerView.swift 3 | // CocoaButton 4 | // 5 | // Created by Edgar Žigis on 17/10/2019. 6 | // Copyright © 2019 Edgar Žigis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SpinnerView : UIView { 12 | 13 | class var poses: [Pose] { 14 | get { 15 | return [ 16 | Pose(0.0, 0.000, 0.7), 17 | Pose(0.6, 0.500, 0.5), 18 | Pose(0.6, 1.000, 0.3), 19 | Pose(0.6, 1.500, 0.1), 20 | Pose(0.2, 1.875, 0.1), 21 | Pose(0.2, 2.250, 0.3), 22 | Pose(0.2, 2.625, 0.5), 23 | Pose(0.2, 3.000, 0.7), 24 | ] 25 | } 26 | } 27 | 28 | public func animate() { 29 | var time: CFTimeInterval = 0 30 | var times = [CFTimeInterval]() 31 | var start: CGFloat = 0 32 | var rotations = [CGFloat]() 33 | var strokeEnds = [CGFloat]() 34 | 35 | let poses = type(of: self).poses 36 | let totalSeconds = poses.reduce(0) { $0 + $1.secondsSincePriorPose } 37 | 38 | poses.forEach { 39 | time += $0.secondsSincePriorPose 40 | times.append(time / totalSeconds) 41 | start = $0.start 42 | rotations.append(start * 2 * .pi) 43 | strokeEnds.append($0.length) 44 | } 45 | 46 | times.append(times.last!) 47 | rotations.append(rotations[0]) 48 | strokeEnds.append(strokeEnds[0]) 49 | 50 | animateKeyPath(keyPath: "strokeEnd", duration: totalSeconds, times: times, values: strokeEnds) 51 | animateKeyPath(keyPath: "transform.rotation", duration: totalSeconds, times: times, values: rotations) 52 | } 53 | 54 | override var layer: CAShapeLayer { 55 | get { return super.layer as! CAShapeLayer } 56 | } 57 | 58 | override class var layerClass: AnyClass { 59 | return CAShapeLayer.self 60 | } 61 | 62 | override func layoutSubviews() { 63 | super.layoutSubviews() 64 | layer.fillColor = nil 65 | layer.strokeColor = UIColor.white.withAlphaComponent(0.8).cgColor 66 | layer.lineWidth = 3 67 | setPath() 68 | } 69 | 70 | override func didMoveToWindow() { 71 | animate() 72 | } 73 | 74 | private func setPath() { 75 | layer.path = UIBezierPath( 76 | ovalIn: bounds.insetBy(dx: layer.lineWidth / 2, dy: layer.lineWidth / 2) 77 | ).cgPath 78 | } 79 | 80 | private func animateKeyPath(keyPath: String, duration: CFTimeInterval, times: [CFTimeInterval], values: [CGFloat]) { 81 | let animation = CAKeyframeAnimation(keyPath: keyPath) 82 | animation.keyTimes = times as [NSNumber]? 83 | animation.values = values 84 | animation.calculationMode = .linear 85 | animation.duration = duration 86 | animation.repeatCount = Float.infinity 87 | layer.add(animation, forKey: animation.keyPath) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /CocoaButton/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /CocoaButtonTest/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CocoaButtonTest 4 | // 5 | // Created by Edgar Žigis on 17/10/2019. 6 | // Copyright © 2019 Edgar Žigis. 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 | -------------------------------------------------------------------------------- /CocoaButtonTest/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /CocoaButtonTest/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /CocoaButtonTest/AttributedLabel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AttributedLabel.swift 3 | // CocoaButtonTest 4 | // 5 | // Created by Edgar Žigis on 17/10/2019. 6 | // Copyright © 2019 Edgar Žigis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AttributedLabel: UILabel { 12 | 13 | var plainText: String? = nil { 14 | didSet { drawText() } 15 | } 16 | 17 | var highlightedText: String? = nil { 18 | didSet { drawText() } 19 | } 20 | 21 | override var font: UIFont? { 22 | didSet { drawText() } 23 | } 24 | 25 | override var textColor: UIColor? { 26 | didSet { drawText() } 27 | } 28 | 29 | override var highlightedTextColor: UIColor? { 30 | didSet { drawText() } 31 | } 32 | 33 | private func drawText() { 34 | var text = "" 35 | var rangeToHighlight = NSRange(location: 0, length: 0) 36 | 37 | if let highlightedText = highlightedText { 38 | text += highlightedText 39 | rangeToHighlight = (text as NSString).range(of: highlightedText) 40 | } 41 | 42 | if let plainText = plainText { text += plainText } 43 | 44 | let attributedString = NSMutableAttributedString(string: text) 45 | if let highlightedTextColor = highlightedTextColor { 46 | attributedString.addAttribute(.foregroundColor, value: highlightedTextColor, range: rangeToHighlight) 47 | } 48 | if let font = font { 49 | attributedString.addAttribute(.font, value: font, range: NSRange(location: 0, length: text.count)) 50 | } 51 | attributedText = NSTextStorage(attributedString: attributedString) 52 | } 53 | 54 | // MARK: Init 55 | 56 | override init(frame: CGRect) { 57 | super.init(frame: frame) 58 | } 59 | 60 | required init?(coder aDecoder: NSCoder) { 61 | super.init(coder: aDecoder) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /CocoaButtonTest/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 | -------------------------------------------------------------------------------- /CocoaButtonTest/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 33 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /CocoaButtonTest/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 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 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /CocoaButtonTest/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // CocoaButtonTest 4 | // 5 | // Created by Edgar Žigis on 17/10/2019. 6 | // Copyright © 2019 Edgar Žigis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | @IBOutlet weak var versionWithoutStoryBoardButton: UIButton! 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | // Do any additional setup after loading the view. 18 | } 19 | 20 | @IBAction func onVersionWithoutStoryBoard(_ sender: Any) { 21 | present(ViewControllerWithoutStoryBoard(), animated: true, completion: nil) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /CocoaButtonTest/ViewControllerWithoutStoryboard.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewControllerWithoutStoryboard.swift 3 | // CocoaButtonTest 4 | // 5 | // Created by Edgar Žigis on 17/10/2019. 6 | // Copyright © 2019 Edgar Žigis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CocoaButton 11 | 12 | class ViewControllerWithoutStoryBoard: UIViewController { 13 | 14 | let submitButton = CocoaButton() 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | 19 | let width = UIScreen.main.bounds.width 20 | 21 | submitButton.setTitle("Submit", for: .normal) 22 | submitButton.frame = CGRect(x: width * 0.1, y: 300, width: width * 0.8, height: 52) 23 | applyStyle(to: submitButton) 24 | submitButton.addTarget(self, action: #selector(onSubmit), for: .touchUpInside) 25 | view.addSubview(submitButton) 26 | 27 | view.backgroundColor = .white 28 | } 29 | 30 | @objc func onSubmit() { 31 | submitButton.startLoading() 32 | DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { 33 | self.submitButton.stopLoading() 34 | } 35 | } 36 | 37 | private func applyStyle(to v: CocoaButton) { 38 | v.setTitle("Submit", for: .normal) 39 | v.backgroundColor = UIColor(displayP3Red: 49/255, green: 190/255, blue: 169/255, alpha: 1) 40 | v.shadowColor = UIColor(red: 94/255, green: 186/255, blue: 187/255, alpha: 0.6) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Edgar Žigis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CocoaButton 2 | 3 | Stylish UIButton with loading animation capability. 4 | ##### Minimum iOS version 11.0 5 | 6 | ![alt text](https://github.com/edgar-zigis/CocoaButton/blob/master/sample.gif?raw=true) 7 | 8 | ### Carthage 9 | 10 | ``` 11 | github "edgar-zigis/CocoaButton" ~> 1.0.0 12 | ``` 13 | ### Cocoapods 14 | 15 | ``` 16 | pod 'CocoaButton', '~> 1.0.0' 17 | ``` 18 | ### Usage 19 | ```swift 20 | let v = CocoaButton() 21 | v.backgroundColor = UIColor(displayP3Red: 49/255, green: 190/255, blue: 169/255, alpha: 1) 22 | v.shadowColor = UIColor(red: 94/255, green: 186/255, blue: 187/255, alpha: 0.6) 23 | v.startLoading() // -> starts loader 24 | v.stopLoading() // -> stops loader 25 | ``` 26 | ### Remarks 27 | It can be used both programmatically and with storyboards. It has all the features of the UIButton. Samples are available at **CocoaButtonTest** 28 | -------------------------------------------------------------------------------- /sample.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edgar-zigis/CocoaButton/6b2fcd9dd9bdfe3fecc9d9cb87d55b0e4f8415cf/sample.gif --------------------------------------------------------------------------------