├── .gitignore ├── .travis.yml ├── CircularProgressButton.podspec ├── Example ├── CircularProgressButton.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── CircularProgressButton-Example.xcscheme ├── Podfile └── Tests │ ├── Info.plist │ └── Tests.swift ├── LICENSE ├── Pod ├── Assets │ └── .gitkeep └── Classes │ ├── .gitkeep │ └── CircularProgressButton.swift ├── Preview └── preview.gif ├── README.md ├── Readme.md └── _Pods.xcodeproj /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | # Pods/ 27 | 28 | # Carthage 29 | # 30 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 31 | # Carthage/Checkouts 32 | 33 | Carthage/Build -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # references: 2 | # * http://www.objc.io/issue-6/travis-ci.html 3 | # * https://github.com/supermarin/xcpretty#usage 4 | 5 | language: objective-c 6 | # cache: cocoapods 7 | # podfile: Example/Podfile 8 | # before_install: 9 | # - gem install cocoapods # Since Travis is not always on latest version 10 | # - pod install --project-directory=Example 11 | script: 12 | - set -o pipefail && xcodebuild test -workspace Example/CircularProgressButton.xcworkspace -scheme CircularProgressButton-Example -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO | xcpretty 13 | - pod lib lint 14 | -------------------------------------------------------------------------------- /CircularProgressButton.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint CircularProgressButton.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = "CircularProgressButton" 11 | s.version = "0.1.0" 12 | s.summary = "A short description of CircularProgressButton." 13 | 14 | # This description is used to generate tags and improve search results. 15 | # * Think: What does it do? Why did you write it? What is the focus? 16 | # * Try to keep it short, snappy and to the point. 17 | # * Write the description between the DESC delimiters below. 18 | # * Finally, don't worry about the indent, CocoaPods strips it! 19 | s.description = <<-DESC 20 | DESC 21 | 22 | s.homepage = "https://github.com//CircularProgressButton" 23 | # s.screenshots = "www.example.com/screenshots_1", "www.example.com/screenshots_2" 24 | s.license = 'MIT' 25 | s.author = { "Dima Cheverda" => "dima.cheverda@gmail.com" } 26 | s.source = { :git => "https://github.com//CircularProgressButton.git", :tag => s.version.to_s } 27 | # s.social_media_url = 'https://twitter.com/' 28 | 29 | s.platform = :ios, '8.0' 30 | s.requires_arc = true 31 | 32 | s.source_files = 'Pod/Classes/**/*' 33 | s.resource_bundles = { 34 | 'CircularProgressButton' => ['Pod/Assets/*.png'] 35 | } 36 | 37 | # s.public_header_files = 'Pod/Classes/**/*.h' 38 | # s.frameworks = 'UIKit', 'MapKit' 39 | # s.dependency 'AFNetworking', '~> 2.3' 40 | end 41 | -------------------------------------------------------------------------------- /Example/CircularProgressButton.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 607FACEC1AFB9204008FA782 /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACEB1AFB9204008FA782 /* Tests.swift */; }; 11 | /* End PBXBuildFile section */ 12 | 13 | /* Begin PBXFileReference section */ 14 | 404047F4D863D789E8BAB943 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = file; name = README.md; path = ../README.md; sourceTree = ""; }; 15 | 607FACE51AFB9204008FA782 /* CircularProgressButton_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CircularProgressButton_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 16 | 607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 17 | 607FACEB1AFB9204008FA782 /* Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests.swift; sourceTree = ""; }; 18 | AFD1192B439E2D0512FB108C /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; 19 | EED17626A7A13B332239C4D0 /* CircularProgressButton.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = file; name = CircularProgressButton.podspec; path = ../CircularProgressButton.podspec; sourceTree = ""; }; 20 | /* End PBXFileReference section */ 21 | 22 | /* Begin PBXFrameworksBuildPhase section */ 23 | 607FACE21AFB9204008FA782 /* Frameworks */ = { 24 | isa = PBXFrameworksBuildPhase; 25 | buildActionMask = 2147483647; 26 | files = ( 27 | ); 28 | runOnlyForDeploymentPostprocessing = 0; 29 | }; 30 | /* End PBXFrameworksBuildPhase section */ 31 | 32 | /* Begin PBXGroup section */ 33 | 607FACC71AFB9204008FA782 = { 34 | isa = PBXGroup; 35 | children = ( 36 | 607FACF51AFB993E008FA782 /* Podspec Metadata */, 37 | 607FACE81AFB9204008FA782 /* Tests */, 38 | 607FACD11AFB9204008FA782 /* Products */, 39 | ); 40 | sourceTree = ""; 41 | }; 42 | 607FACD11AFB9204008FA782 /* Products */ = { 43 | isa = PBXGroup; 44 | children = ( 45 | 607FACE51AFB9204008FA782 /* CircularProgressButton_Tests.xctest */, 46 | ); 47 | name = Products; 48 | sourceTree = ""; 49 | }; 50 | 607FACE81AFB9204008FA782 /* Tests */ = { 51 | isa = PBXGroup; 52 | children = ( 53 | 607FACEB1AFB9204008FA782 /* Tests.swift */, 54 | 607FACE91AFB9204008FA782 /* Supporting Files */, 55 | ); 56 | path = Tests; 57 | sourceTree = ""; 58 | }; 59 | 607FACE91AFB9204008FA782 /* Supporting Files */ = { 60 | isa = PBXGroup; 61 | children = ( 62 | 607FACEA1AFB9204008FA782 /* Info.plist */, 63 | ); 64 | name = "Supporting Files"; 65 | sourceTree = ""; 66 | }; 67 | 607FACF51AFB993E008FA782 /* Podspec Metadata */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | EED17626A7A13B332239C4D0 /* CircularProgressButton.podspec */, 71 | 404047F4D863D789E8BAB943 /* README.md */, 72 | AFD1192B439E2D0512FB108C /* LICENSE */, 73 | ); 74 | name = "Podspec Metadata"; 75 | sourceTree = ""; 76 | }; 77 | /* End PBXGroup section */ 78 | 79 | /* Begin PBXNativeTarget section */ 80 | 607FACE41AFB9204008FA782 /* CircularProgressButton_Tests */ = { 81 | isa = PBXNativeTarget; 82 | buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "CircularProgressButton_Tests" */; 83 | buildPhases = ( 84 | 607FACE11AFB9204008FA782 /* Sources */, 85 | 607FACE21AFB9204008FA782 /* Frameworks */, 86 | 607FACE31AFB9204008FA782 /* Resources */, 87 | ); 88 | buildRules = ( 89 | ); 90 | dependencies = ( 91 | ); 92 | name = CircularProgressButton_Tests; 93 | productName = Tests; 94 | productReference = 607FACE51AFB9204008FA782 /* CircularProgressButton_Tests.xctest */; 95 | productType = "com.apple.product-type.bundle.unit-test"; 96 | }; 97 | /* End PBXNativeTarget section */ 98 | 99 | /* Begin PBXProject section */ 100 | 607FACC81AFB9204008FA782 /* Project object */ = { 101 | isa = PBXProject; 102 | attributes = { 103 | LastSwiftUpdateCheck = 0720; 104 | LastUpgradeCheck = 0720; 105 | ORGANIZATIONNAME = CocoaPods; 106 | TargetAttributes = { 107 | 607FACE41AFB9204008FA782 = { 108 | CreatedOnToolsVersion = 6.3.1; 109 | TestTargetID = 607FACCF1AFB9204008FA782; 110 | }; 111 | }; 112 | }; 113 | buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "PROJECT" */; 114 | compatibilityVersion = "Xcode 3.2"; 115 | developmentRegion = English; 116 | hasScannedForEncodings = 0; 117 | knownRegions = ( 118 | en, 119 | Base, 120 | ); 121 | mainGroup = 607FACC71AFB9204008FA782; 122 | productRefGroup = 607FACD11AFB9204008FA782 /* Products */; 123 | projectDirPath = ""; 124 | projectRoot = ""; 125 | targets = ( 126 | 607FACE41AFB9204008FA782 /* CircularProgressButton_Tests */, 127 | ); 128 | }; 129 | /* End PBXProject section */ 130 | 131 | /* Begin PBXResourcesBuildPhase section */ 132 | 607FACE31AFB9204008FA782 /* Resources */ = { 133 | isa = PBXResourcesBuildPhase; 134 | buildActionMask = 2147483647; 135 | files = ( 136 | ); 137 | runOnlyForDeploymentPostprocessing = 0; 138 | }; 139 | /* End PBXResourcesBuildPhase section */ 140 | 141 | /* Begin PBXSourcesBuildPhase section */ 142 | 607FACE11AFB9204008FA782 /* Sources */ = { 143 | isa = PBXSourcesBuildPhase; 144 | buildActionMask = 2147483647; 145 | files = ( 146 | 607FACEC1AFB9204008FA782 /* Tests.swift in Sources */, 147 | ); 148 | runOnlyForDeploymentPostprocessing = 0; 149 | }; 150 | /* End PBXSourcesBuildPhase section */ 151 | 152 | /* Begin XCBuildConfiguration section */ 153 | 607FACED1AFB9204008FA782 /* Debug */ = { 154 | isa = XCBuildConfiguration; 155 | buildSettings = { 156 | ALWAYS_SEARCH_USER_PATHS = NO; 157 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 158 | CLANG_CXX_LIBRARY = "libc++"; 159 | CLANG_ENABLE_MODULES = YES; 160 | CLANG_ENABLE_OBJC_ARC = YES; 161 | CLANG_WARN_BOOL_CONVERSION = YES; 162 | CLANG_WARN_CONSTANT_CONVERSION = YES; 163 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 164 | CLANG_WARN_EMPTY_BODY = YES; 165 | CLANG_WARN_ENUM_CONVERSION = YES; 166 | CLANG_WARN_INT_CONVERSION = YES; 167 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 168 | CLANG_WARN_UNREACHABLE_CODE = YES; 169 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 170 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 171 | COPY_PHASE_STRIP = NO; 172 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 173 | ENABLE_STRICT_OBJC_MSGSEND = YES; 174 | ENABLE_TESTABILITY = YES; 175 | GCC_C_LANGUAGE_STANDARD = gnu99; 176 | GCC_DYNAMIC_NO_PIC = NO; 177 | GCC_NO_COMMON_BLOCKS = YES; 178 | GCC_OPTIMIZATION_LEVEL = 0; 179 | GCC_PREPROCESSOR_DEFINITIONS = ( 180 | "DEBUG=1", 181 | "$(inherited)", 182 | ); 183 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 184 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 185 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 186 | GCC_WARN_UNDECLARED_SELECTOR = YES; 187 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 188 | GCC_WARN_UNUSED_FUNCTION = YES; 189 | GCC_WARN_UNUSED_VARIABLE = YES; 190 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 191 | MTL_ENABLE_DEBUG_INFO = YES; 192 | ONLY_ACTIVE_ARCH = YES; 193 | SDKROOT = iphoneos; 194 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 195 | }; 196 | name = Debug; 197 | }; 198 | 607FACEE1AFB9204008FA782 /* Release */ = { 199 | isa = XCBuildConfiguration; 200 | buildSettings = { 201 | ALWAYS_SEARCH_USER_PATHS = NO; 202 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 203 | CLANG_CXX_LIBRARY = "libc++"; 204 | CLANG_ENABLE_MODULES = YES; 205 | CLANG_ENABLE_OBJC_ARC = YES; 206 | CLANG_WARN_BOOL_CONVERSION = YES; 207 | CLANG_WARN_CONSTANT_CONVERSION = YES; 208 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 209 | CLANG_WARN_EMPTY_BODY = YES; 210 | CLANG_WARN_ENUM_CONVERSION = YES; 211 | CLANG_WARN_INT_CONVERSION = YES; 212 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 213 | CLANG_WARN_UNREACHABLE_CODE = YES; 214 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 215 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 216 | COPY_PHASE_STRIP = NO; 217 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 218 | ENABLE_NS_ASSERTIONS = NO; 219 | ENABLE_STRICT_OBJC_MSGSEND = YES; 220 | GCC_C_LANGUAGE_STANDARD = gnu99; 221 | GCC_NO_COMMON_BLOCKS = YES; 222 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 223 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 224 | GCC_WARN_UNDECLARED_SELECTOR = YES; 225 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 226 | GCC_WARN_UNUSED_FUNCTION = YES; 227 | GCC_WARN_UNUSED_VARIABLE = YES; 228 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 229 | MTL_ENABLE_DEBUG_INFO = NO; 230 | SDKROOT = iphoneos; 231 | VALIDATE_PRODUCT = YES; 232 | }; 233 | name = Release; 234 | }; 235 | 607FACF31AFB9204008FA782 /* Debug */ = { 236 | isa = XCBuildConfiguration; 237 | buildSettings = { 238 | FRAMEWORK_SEARCH_PATHS = ( 239 | "$(SDKROOT)/Developer/Library/Frameworks", 240 | "$(inherited)", 241 | ); 242 | GCC_PREPROCESSOR_DEFINITIONS = ( 243 | "DEBUG=1", 244 | "$(inherited)", 245 | ); 246 | INFOPLIST_FILE = Tests/Info.plist; 247 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 248 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; 249 | PRODUCT_NAME = "$(TARGET_NAME)"; 250 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CircularProgressButton_Example.app/CircularProgressButton_Example"; 251 | }; 252 | name = Debug; 253 | }; 254 | 607FACF41AFB9204008FA782 /* Release */ = { 255 | isa = XCBuildConfiguration; 256 | buildSettings = { 257 | FRAMEWORK_SEARCH_PATHS = ( 258 | "$(SDKROOT)/Developer/Library/Frameworks", 259 | "$(inherited)", 260 | ); 261 | INFOPLIST_FILE = Tests/Info.plist; 262 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 263 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; 264 | PRODUCT_NAME = "$(TARGET_NAME)"; 265 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CircularProgressButton_Example.app/CircularProgressButton_Example"; 266 | }; 267 | name = Release; 268 | }; 269 | /* End XCBuildConfiguration section */ 270 | 271 | /* Begin XCConfigurationList section */ 272 | 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "PROJECT" */ = { 273 | isa = XCConfigurationList; 274 | buildConfigurations = ( 275 | 607FACED1AFB9204008FA782 /* Debug */, 276 | 607FACEE1AFB9204008FA782 /* Release */, 277 | ); 278 | defaultConfigurationIsVisible = 0; 279 | defaultConfigurationName = Release; 280 | }; 281 | 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "CircularProgressButton_Tests" */ = { 282 | isa = XCConfigurationList; 283 | buildConfigurations = ( 284 | 607FACF31AFB9204008FA782 /* Debug */, 285 | 607FACF41AFB9204008FA782 /* Release */, 286 | ); 287 | defaultConfigurationIsVisible = 0; 288 | defaultConfigurationName = Release; 289 | }; 290 | /* End XCConfigurationList section */ 291 | }; 292 | rootObject = 607FACC81AFB9204008FA782 /* Project object */; 293 | } 294 | -------------------------------------------------------------------------------- /Example/CircularProgressButton.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/CircularProgressButton.xcodeproj/xcshareddata/xcschemes/CircularProgressButton-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | use_frameworks! 3 | 4 | 5 | target 'CircularProgressButton_Tests', :exclusive => true do 6 | pod 'CircularProgressButton', :path => '../' 7 | 8 | 9 | end 10 | -------------------------------------------------------------------------------- /Example/Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/Tests/Tests.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import XCTest 3 | import CircularProgressButton 4 | 5 | class Tests: XCTestCase { 6 | 7 | override func setUp() { 8 | super.setUp() 9 | // Put setup code here. This method is called before the invocation of each test method in the class. 10 | } 11 | 12 | override func tearDown() { 13 | // Put teardown code here. This method is called after the invocation of each test method in the class. 14 | super.tearDown() 15 | } 16 | 17 | func testExample() { 18 | // This is an example of a functional test case. 19 | XCTAssert(true, "Pass") 20 | } 21 | 22 | func testPerformanceExample() { 23 | // This is an example of a performance test case. 24 | self.measureBlock() { 25 | // Put the code you want to measure the time of here. 26 | } 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Dima Cheverda 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Pod/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimacheverda/CircularProgressButton/1c59b8ba9d0eb79aae10c4d9ba75813e18ed80bf/Pod/Assets/.gitkeep -------------------------------------------------------------------------------- /Pod/Classes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimacheverda/CircularProgressButton/1c59b8ba9d0eb79aae10c4d9ba75813e18ed80bf/Pod/Classes/.gitkeep -------------------------------------------------------------------------------- /Pod/Classes/CircularProgressButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CircularProgressButton.swift 3 | // CircularProgressButton 4 | // 5 | // Created by Dima Cheverda on 2/21/15. 6 | // Copyright (c) 2015 Dima Cheverda. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | enum ButtonState: String { 12 | case Original = "Original" 13 | case Animating = "Animating" 14 | case Small = "Small" 15 | } 16 | 17 | class CircularProgressButton: UIButton { 18 | 19 | // MARK: - Public Properties 20 | 21 | var originalCornerRadius: CGFloat = 0 22 | var originalColor: CGColorRef = UIColor(red:0.14, green:0.6, blue:0.79, alpha:1).CGColor 23 | var originalBorderColor: CGColorRef = UIColor(red:0.14, green:0.6, blue:0.79, alpha:1).CGColor 24 | 25 | var smallCornerRadius: CGFloat = 0 26 | var smallColor: CGColorRef = UIColor.whiteColor().CGColor 27 | var smallBorderColor: CGColorRef = UIColor(white: 0.9, alpha: 1).CGColor 28 | 29 | var progress: CGFloat = 0.0 { 30 | didSet { 31 | if progress < 0.0 { 32 | progress = 0.0 33 | // FIXME: fix bug when 1.0 not >= 1.0 34 | } else if progress >= 0.9999999999999 { 35 | progress = 1.0 36 | } 37 | 38 | circularProgressLayer.strokeEnd = progress 39 | 40 | if progress >= 0.9999999999999 { 41 | buttonState = .Animating 42 | self.makeOriginalWithDelay(0.2) 43 | } 44 | } 45 | } 46 | 47 | 48 | // MARK: - Private Properties 49 | 50 | private var animationDuration: CFTimeInterval = 0.2 51 | private var buttonState: ButtonState = .Original 52 | private var borderWidth: CGFloat = 5.0 53 | 54 | private var originalBounds: CGRect = CGRectZero 55 | private var smallBounds: CGRect = CGRectZero 56 | 57 | // TODO: implement PressedColor effect 58 | private var pressedColor: CGColorRef = UIColor(red:0.14, green:0.6, blue:0.49, alpha:1).CGColor 59 | 60 | private var circularProgressLayer: CAShapeLayer 61 | private var foregroundLayer: CALayer 62 | 63 | 64 | // MARK: - Initializers 65 | 66 | init(frame: CGRect, cornerRadius: CGFloat) { 67 | 68 | foregroundLayer = CALayer() 69 | circularProgressLayer = CAShapeLayer() 70 | 71 | super.init(frame: frame) 72 | 73 | prepare() 74 | } 75 | 76 | required init?(coder aDecoder: NSCoder) { 77 | 78 | foregroundLayer = CALayer() 79 | circularProgressLayer = CAShapeLayer() 80 | 81 | super.init(coder: aDecoder) 82 | 83 | prepare() 84 | } 85 | 86 | private func prepare() { 87 | prepareParameters() 88 | prepareForegroundLayer() 89 | prepareCircularLayer() 90 | 91 | layer.masksToBounds = true 92 | } 93 | 94 | private func prepareParameters() { 95 | originalBounds = layer.bounds 96 | 97 | smallBounds = layer.bounds 98 | smallBounds.size.width = smallBounds.height 99 | smallCornerRadius = smallBounds.height/2 100 | } 101 | 102 | private func prepareForegroundLayer() { 103 | foregroundLayer.frame = layer.frame 104 | foregroundLayer.masksToBounds = true 105 | foregroundLayer.cornerRadius = originalCornerRadius 106 | foregroundLayer.backgroundColor = originalColor 107 | foregroundLayer.bounds = originalBounds 108 | foregroundLayer.borderWidth = borderWidth 109 | foregroundLayer.borderColor = originalBorderColor 110 | 111 | layer.addSublayer(foregroundLayer) 112 | } 113 | 114 | private func prepareCircularLayer() { 115 | circularProgressLayer.frame = CGRectMake(0, 0, layer.bounds.height, layer.bounds.height) 116 | circularProgressLayer.position = layer.position 117 | circularProgressLayer.hidden = false 118 | circularProgressLayer.backgroundColor = UIColor.clearColor().CGColor 119 | circularProgressLayer.path = circlePath().CGPath 120 | circularProgressLayer.strokeStart = 0 121 | circularProgressLayer.strokeEnd = 0 122 | circularProgressLayer.lineWidth = borderWidth 123 | circularProgressLayer.fillColor = UIColor.clearColor().CGColor 124 | circularProgressLayer.strokeColor = originalBorderColor 125 | 126 | layer.addSublayer(circularProgressLayer) 127 | } 128 | 129 | private func resetProgressLayer() { 130 | progress = 0 131 | circularProgressLayer.strokeEnd = 0 132 | } 133 | 134 | 135 | // MARK: - Helpers 136 | 137 | private func circlePath() -> UIBezierPath { 138 | let radius = CGRectGetHeight(circularProgressLayer.bounds)/2 - borderWidth/2 139 | let arcCenterXY = radius + borderWidth/2 140 | let arcCenter = CGPoint(x: arcCenterXY, y: arcCenterXY) 141 | let startAngle = CGFloat(-M_PI_2) 142 | let endAngle = startAngle + CGFloat(M_PI*2) 143 | let path = UIBezierPath(arcCenter: arcCenter, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true) 144 | return path 145 | } 146 | 147 | private func makeSmallWithDelay(delay: CFTimeInterval) { 148 | changeToState(.Small, targetLayer: foregroundLayer, delay: delay) 149 | } 150 | 151 | private func makeOriginalWithDelay(delay: CFTimeInterval) { 152 | changeToState(.Original, targetLayer: foregroundLayer, delay: delay) 153 | } 154 | 155 | private func changeToState(state: ButtonState, targetLayer: CALayer, delay: CFTimeInterval) { 156 | 157 | let group = CAAnimationGroup() 158 | group.duration = animationDuration 159 | group.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) 160 | group.beginTime = CACurrentMediaTime() + delay 161 | group.fillMode = kCAFillModeForwards 162 | group.removedOnCompletion = false 163 | group.delegate = self 164 | 165 | let name = (state == .Small) ? "makeSmall" : "makeOriginal" 166 | group.setValue(name, forKey: "name") 167 | group.setValue(targetLayer, forKey: "layer") 168 | 169 | // bounds 170 | let sizeAnimation = CABasicAnimation(keyPath: "bounds") 171 | let toBounds = (state == .Original) ? originalBounds : smallBounds 172 | sizeAnimation.toValue = NSValue(CGRect: toBounds) 173 | 174 | // cornerRadius 175 | let cornerRadiusAnimation = CABasicAnimation(keyPath: "cornerRadius") 176 | let toCornerRadius = (state == .Original) ? originalCornerRadius : smallCornerRadius 177 | cornerRadiusAnimation.toValue = toCornerRadius 178 | 179 | // backgroundColor 180 | let backgroundColorAnimation = CABasicAnimation(keyPath: "backgroundColor") 181 | let toColor = (state == .Original) ? originalColor : smallColor 182 | backgroundColorAnimation.toValue = toColor 183 | 184 | // borderColor 185 | let borderColorAnimation = CABasicAnimation(keyPath: "borderColor") 186 | let toBorderColor = (state == .Original) ? originalBorderColor : smallBorderColor 187 | borderColorAnimation.toValue = toBorderColor 188 | 189 | group.animations = [sizeAnimation, cornerRadiusAnimation, backgroundColorAnimation, borderColorAnimation] 190 | 191 | targetLayer.addAnimation(group, forKey: "anim") 192 | } 193 | 194 | // FIXME: fix bug when highlight color disappear after first animation 195 | private func changeButtonColorTo(color: CGColorRef) { 196 | foregroundLayer.backgroundColor = color 197 | foregroundLayer.borderColor = color 198 | } 199 | 200 | 201 | // MARK: - Touch Tracking 202 | 203 | override func beginTrackingWithTouch(touch: UITouch, withEvent event: UIEvent?) -> Bool { 204 | super.beginTrackingWithTouch(touch, withEvent: event) 205 | 206 | if event?.type == UIEventType.Touches { 207 | changeButtonColorTo(pressedColor) 208 | } 209 | 210 | return true 211 | } 212 | 213 | override func continueTrackingWithTouch(touch: UITouch, withEvent event: UIEvent?) -> Bool { 214 | super.continueTrackingWithTouch(touch, withEvent: event) 215 | 216 | let touchEnd = touch.locationInView(self) 217 | if !CGRectContainsPoint(bounds, touchEnd) { 218 | changeButtonColorTo(originalColor) 219 | return false 220 | } 221 | 222 | return true 223 | } 224 | 225 | override func endTrackingWithTouch(touch: UITouch?, withEvent event: UIEvent?) { 226 | super.endTrackingWithTouch(touch, withEvent: event) 227 | 228 | if event?.type == UIEventType.Touches { 229 | 230 | CATransaction.begin() 231 | CATransaction.setDisableActions(true) 232 | changeButtonColorTo(originalColor) 233 | CATransaction.commit() 234 | 235 | if let touchEnd = touch?.locationInView(self) { 236 | if CGRectContainsPoint(bounds, touchEnd) && buttonState == .Original { 237 | makeSmallWithDelay(0) 238 | } 239 | } 240 | } 241 | } 242 | 243 | 244 | // MARK: - Animation Delegate 245 | 246 | override func animationDidStart(anim: CAAnimation) { 247 | 248 | self.buttonState = .Animating 249 | 250 | let nameValue = anim.valueForKey("name") as? String 251 | if let name = nameValue { 252 | if name == "makeOriginal" { 253 | CATransaction.begin() 254 | CATransaction.setDisableActions(true) 255 | circularProgressLayer.hidden = true 256 | CATransaction.commit() 257 | } 258 | } 259 | } 260 | 261 | override func animationDidStop(anim: CAAnimation, finished flag: Bool) { 262 | 263 | let nameValue = anim.valueForKey("name") as? String 264 | if let name = nameValue { 265 | 266 | if name == "makeSmall" && flag == true { 267 | let targetLayer: CALayer = anim.valueForKey("layer") as! CALayer 268 | 269 | CATransaction.begin() 270 | CATransaction.setDisableActions(true) 271 | targetLayer.backgroundColor = smallColor 272 | targetLayer.bounds = smallBounds 273 | targetLayer.cornerRadius = smallCornerRadius 274 | targetLayer.borderColor = smallBorderColor 275 | circularProgressLayer.hidden = false 276 | 277 | resetProgressLayer() 278 | CATransaction.commit() 279 | 280 | buttonState = .Small 281 | 282 | // FIXME: remove this method call to disable autoanimation when button pressed 283 | animate() 284 | } 285 | 286 | if name == "makeOriginal" && flag == true { 287 | let targetLayer: CALayer = anim.valueForKey("layer") as! CALayer 288 | 289 | CATransaction.begin() 290 | CATransaction.setDisableActions(true) 291 | targetLayer.backgroundColor = originalColor 292 | targetLayer.bounds = originalBounds 293 | targetLayer.cornerRadius = originalCornerRadius 294 | targetLayer.borderColor = originalBorderColor 295 | CATransaction.commit() 296 | 297 | buttonState = .Original 298 | } 299 | } 300 | } 301 | 302 | func animate() { 303 | delay(seconds: 0.1, completion: { 304 | self.progress += 0.05 305 | if self.progress < 1.0 { 306 | self.animate() 307 | } 308 | }) 309 | } 310 | 311 | } 312 | 313 | // MARK: - Delay function 314 | 315 | func delay(seconds seconds: Double, completion:()->()) { 316 | let popTime = dispatch_time(DISPATCH_TIME_NOW, Int64( Double(NSEC_PER_SEC) * seconds )) 317 | 318 | dispatch_after(popTime, dispatch_get_main_queue()) { 319 | completion() 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /Preview/preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimacheverda/CircularProgressButton/1c59b8ba9d0eb79aae10c4d9ba75813e18ed80bf/Preview/preview.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CircularProgressButton 2 | =================== 3 | 4 | Subclass of **UIButton** which animates into and display circular progress 5 | 6 | Features 7 | --------- 8 | 9 | Customisable properties: 10 | 11 | - Original state properties 12 | - `originalCornerRadius: CGFloat` 13 | - `originalColor: CGColor` 14 | - `originalBorderColor: CGColor` 15 | - Small state properties 16 | - `smallCornerRadius: CGFloat` 17 | - `smallColor: CGColor` 18 | - `smallBorderColor: CGColor` 19 | 20 | **Preview** 21 | 22 | ![](Preview/preview.gif) 23 | 24 | How to use 25 | --------- 26 | 27 | > **Note**: Currently avaliable onle through code (no IB support) 28 | 29 | Code for initializing button 30 | 31 | ``` 32 | let buttonFrame = CGRect(x: 0, y: 0, width: 250, height: 100) 33 | let button = CircularProgressButton(frame: buttonFrame, cornerRadius: 20) 34 | button.setTitle("Upload", forState: .Normal) 35 | ``` 36 | 37 | Installing 38 | --------- 39 | 40 | 2. Copy `CircularProgressButton.swift` into your project 41 | 3. Use it 42 | 43 | 44 | --------- 45 | 46 | > The MIT License (MIT) 47 | > 48 | > Copyright (c) 2015 Dmytro Cheverda 49 | > 50 | > Permission is hereby granted, free of charge, to any person obtaining 51 | > a copy of this software and associated documentation files (the 52 | > "Software"), to deal in the Software without restriction, including 53 | > without limitation the rights to use, copy, modify, merge, publish, 54 | > distribute, sublicense, and/or sell copies of the Software, and to 55 | > permit persons to whom the Software is furnished to do so, subject to 56 | > the following conditions: 57 | > 58 | > The above copyright notice and this permission notice shall be 59 | > included in all copies or substantial portions of the Software. 60 | > 61 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 62 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 63 | > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 64 | > IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 65 | > CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 66 | > TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 67 | > SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 68 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | CircularProgressButton 2 | =================== 3 | 4 | Subclass of **UIButton** which animates into and display circular progress 5 | 6 | Features 7 | --------- 8 | 9 | Customisable properties: 10 | 11 | - Original state properties 12 | - `originalCornerRadius: CGFloat` 13 | - `originalColor: CGColor` 14 | - `originalBorderColor: CGColor` 15 | - Small state properties 16 | - `smallCornerRadius: CGFloat` 17 | - `smallColor: CGColor` 18 | - `smallBorderColor: CGColor` 19 | 20 | **Preview** 21 | 22 | ![](Preview/preview.gif) 23 | 24 | How to use 25 | --------- 26 | 27 | > **Note**: Currently avaliable onle through code (no IB support) 28 | 29 | Code for initializing button 30 | 31 | ``` 32 | let buttonFrame = CGRect(x: 0, y: 0, width: 250, height: 100) 33 | let button = CircularProgressButton(frame: buttonFrame, cornerRadius: 20) 34 | button.setTitle("Upload", forState: .Normal) 35 | ``` 36 | 37 | Installing 38 | --------- 39 | 40 | 2. Copy `CircularProgressButton.swift` into your project 41 | 3. Use it 42 | 43 | 44 | --------- 45 | 46 | > The MIT License (MIT) 47 | > 48 | > Copyright (c) 2015 Dmytro Cheverda 49 | > 50 | > Permission is hereby granted, free of charge, to any person obtaining 51 | > a copy of this software and associated documentation files (the 52 | > "Software"), to deal in the Software without restriction, including 53 | > without limitation the rights to use, copy, modify, merge, publish, 54 | > distribute, sublicense, and/or sell copies of the Software, and to 55 | > permit persons to whom the Software is furnished to do so, subject to 56 | > the following conditions: 57 | > 58 | > The above copyright notice and this permission notice shall be 59 | > included in all copies or substantial portions of the Software. 60 | > 61 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 62 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 63 | > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 64 | > IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 65 | > CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 66 | > TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 67 | > SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 68 | -------------------------------------------------------------------------------- /_Pods.xcodeproj: -------------------------------------------------------------------------------- 1 | Example/Pods/Pods.xcodeproj --------------------------------------------------------------------------------