├── .gitignore ├── .slather.yml ├── .swift-version ├── .travis.yml ├── CHANGELOG.md ├── Example ├── Default-568h@2x.png ├── Podfile ├── Podfile.lock ├── RPCircularProgressExample.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── RPCircularProgressExample.xcscheme ├── RPCircularProgressExample.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── RPCircularProgressExample │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Info.plist │ └── ViewController.swift ├── LICENSE ├── README.md ├── RPCircularProgress.podspec ├── Source └── RPCircularProgress.swift ├── Tests ├── Info.plist └── RPCircularProgressTests.swift └── ohhai.gif /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | build/ 3 | *.pbxuser 4 | !default.pbxuser 5 | *.mode1v3 6 | !default.mode1v3 7 | *.mode2v3 8 | !default.mode2v3 9 | *.perspectivev3 10 | !default.perspectivev3 11 | xcuserdata 12 | *.xccheckout 13 | *.moved-aside 14 | DerivedData 15 | *.hmap 16 | *.ipa 17 | *.xcuserstate 18 | 19 | # CocoaPods 20 | Pods/ 21 | 22 | # Carthage 23 | Carthage/Checkouts 24 | Carthage/Build 25 | -------------------------------------------------------------------------------- /.slather.yml: -------------------------------------------------------------------------------- 1 | coverage_service: coveralls 2 | xcodeproj: Example/RPCircularProgressExample.xcodeproj 3 | source_directory: Source -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 5.0 -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | osx_image: xcode10.2 2 | rvm: 3 | - 2.6.2 4 | language: objective-c 5 | before_install: 6 | - gem install cocoapods 7 | - pod repo update 8 | - gem install xcpretty slather -N 9 | - xcrun instruments -w "iPhone 6 (12.2)" || echo "(Pre)Launched the simulator." 10 | cache: cocoapods 11 | podfile: Example/Podfile 12 | script: 13 | - set -o pipefail && xcodebuild test -workspace example/RPCircularProgressExample.xcworkspace -scheme "RPCircularProgressExample" -destination "platform=iOS Simulator,name=iPhone XS,OS=12.2" -enableCodeCoverage YES ONLY_ACTIVE_ARCH=YES | xcpretty -c 14 | after_success: 15 | - pod lib lint --allow-warnings 16 | - slather coverage --ignore "../**/*/Xcode*" --scheme "RPCircularProgressExample" --workspace example/RPCircularProgressExample.xcworkspace example/RPCircularProgressExample.xcodeproj -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [v0.5.0](https://github.com/iwasrobbed/RPCircularProgress/tree/v0.5.0) (2019-04-06) 4 | [Full Changelog](https://github.com/iwasrobbed/RPCircularProgress/compare/v0.4.1...v0.5.0) 5 | 6 | **Closed issues:** 7 | 8 | - s.swift\_version = '4.2' missing from podspec [\#34](https://github.com/iwasrobbed/RPCircularProgress/issues/34) 9 | - Xcode 10.2 / Swift 5 support [\#36](https://github.com/iwasrobbed/RPCircularProgress/issues/36) 10 | 11 | **Merged pull requests:** 12 | 13 | - Swift/5.0 [\#37](https://github.com/iwasrobbed/RPCircularProgress/pull/37) ([alex-taffe](https://github.com/alex-taffe)) 14 | - Added swift version to podspec [\#35](https://github.com/iwasrobbed/RPCircularProgress/pull/35) ([kamrankhan07](https://github.com/kamrankhan07)) 15 | 16 | ## [v0.4.1](https://github.com/iwasrobbed/RPCircularProgress/tree/v0.4.1) (2018-10-10) 17 | [Full Changelog](https://github.com/iwasrobbed/RPCircularProgress/compare/v0.4.0...v0.4.1) 18 | 19 | **Closed issues:** 20 | 21 | - libswiftXCTest.dylib Reason: image not found [\#32](https://github.com/iwasrobbed/RPCircularProgress/issues/32) 22 | 23 | **Merged pull requests:** 24 | 25 | - Swift 4.2 Migration [\#33](https://github.com/iwasrobbed/RPCircularProgress/pull/33) ([alex-taffe](https://github.com/alex-taffe)) 26 | - Remove use of casting to implicitly unwrapped optional [\#30](https://github.com/iwasrobbed/RPCircularProgress/pull/30) ([axtonpitt](https://github.com/axtonpitt)) 27 | 28 | ## [v0.4.0](https://github.com/iwasrobbed/RPCircularProgress/tree/v0.4.0) (2017-10-07) 29 | [Full Changelog](https://github.com/iwasrobbed/RPCircularProgress/compare/v0.3.1...v0.4.0) 30 | 31 | **Implemented enhancements:** 32 | 33 | - Swift 4.0 support [\#27](https://github.com/iwasrobbed/RPCircularProgress/pull/27) ([iwasrobbed](https://github.com/iwasrobbed)) 34 | 35 | **Closed issues:** 36 | 37 | - Swift 4 support 🙏 [\#26](https://github.com/iwasrobbed/RPCircularProgress/issues/26) 38 | 39 | ## [v0.3.1](https://github.com/iwasrobbed/RPCircularProgress/tree/v0.3.1) (2017-05-16) 40 | [Full Changelog](https://github.com/iwasrobbed/RPCircularProgress/compare/v0.3.0...v0.3.1) 41 | 42 | **Closed issues:** 43 | 44 | - UpdateProgress not available from Objective C [\#23](https://github.com/iwasrobbed/RPCircularProgress/issues/23) 45 | - Dot Appears every time updateProgress is called. [\#21](https://github.com/iwasrobbed/RPCircularProgress/issues/21) 46 | - just a noob question [\#19](https://github.com/iwasrobbed/RPCircularProgress/issues/19) 47 | - Swift 3 to Objc Generated interface [\#18](https://github.com/iwasrobbed/RPCircularProgress/issues/18) 48 | - CocoaPods support. [\#17](https://github.com/iwasrobbed/RPCircularProgress/issues/17) 49 | - Question: Start Progress [\#16](https://github.com/iwasrobbed/RPCircularProgress/issues/16) 50 | - Swift 3 and iOS 10 Support [\#10](https://github.com/iwasrobbed/RPCircularProgress/issues/10) 51 | 52 | **Merged pull requests:** 53 | 54 | - Fix Swift 3.1 compile warnings [\#24](https://github.com/iwasrobbed/RPCircularProgress/pull/24) ([Magnat12](https://github.com/Magnat12)) 55 | 56 | ## [v0.3.0](https://github.com/iwasrobbed/RPCircularProgress/tree/v0.3.0) (2016-09-19) 57 | [Full Changelog](https://github.com/iwasrobbed/RPCircularProgress/compare/v0.2.3...v0.3.0) 58 | 59 | **Merged pull requests:** 60 | 61 | - Swift 3.0 support [\#15](https://github.com/iwasrobbed/RPCircularProgress/pull/15) ([iwasrobbed](https://github.com/iwasrobbed)) 62 | - Swift 3.0 support [\#13](https://github.com/iwasrobbed/RPCircularProgress/pull/13) ([iwasrobbed](https://github.com/iwasrobbed)) 63 | - Swift 2.3 support [\#12](https://github.com/iwasrobbed/RPCircularProgress/pull/12) ([iwasrobbed](https://github.com/iwasrobbed)) 64 | - Swift 2.3 support [\#11](https://github.com/iwasrobbed/RPCircularProgress/pull/11) ([iwasrobbed](https://github.com/iwasrobbed)) 65 | 66 | ## [v0.2.3](https://github.com/iwasrobbed/RPCircularProgress/tree/v0.2.3) (2016-09-19) 67 | [Full Changelog](https://github.com/iwasrobbed/RPCircularProgress/compare/v0.1.2...v0.2.3) 68 | 69 | **Implemented enhancements:** 70 | 71 | - Objective-C support [\#9](https://github.com/iwasrobbed/RPCircularProgress/issues/9) 72 | 73 | **Merged pull requests:** 74 | 75 | - Update README [\#7](https://github.com/iwasrobbed/RPCircularProgress/pull/7) ([iwasrobbed](https://github.com/iwasrobbed)) 76 | - Code coverage [\#6](https://github.com/iwasrobbed/RPCircularProgress/pull/6) ([iwasrobbed](https://github.com/iwasrobbed)) 77 | - Code coverage [\#5](https://github.com/iwasrobbed/RPCircularProgress/pull/5) ([iwasrobbed](https://github.com/iwasrobbed)) 78 | - Readme backfill [\#4](https://github.com/iwasrobbed/RPCircularProgress/pull/4) ([iwasrobbed](https://github.com/iwasrobbed)) 79 | - Access for pod use & allow custom timing functions [\#3](https://github.com/iwasrobbed/RPCircularProgress/pull/3) ([iwasrobbed](https://github.com/iwasrobbed)) 80 | 81 | ## [v0.1.2](https://github.com/iwasrobbed/RPCircularProgress/tree/v0.1.2) (2016-04-08) 82 | [Full Changelog](https://github.com/iwasrobbed/RPCircularProgress/compare/v0.1.1...v0.1.2) 83 | 84 | ## [v0.1.1](https://github.com/iwasrobbed/RPCircularProgress/tree/v0.1.1) (2016-04-07) 85 | [Full Changelog](https://github.com/iwasrobbed/RPCircularProgress/compare/v0.1...v0.1.1) 86 | 87 | **Merged pull requests:** 88 | 89 | - Update podspec for lint [\#2](https://github.com/iwasrobbed/RPCircularProgress/pull/2) ([iwasrobbed](https://github.com/iwasrobbed)) 90 | 91 | ## [v0.1](https://github.com/iwasrobbed/RPCircularProgress/tree/v0.1) (2016-04-07) 92 | **Merged pull requests:** 93 | 94 | - Adds travis [\#1](https://github.com/iwasrobbed/RPCircularProgress/pull/1) ([iwasrobbed](https://github.com/iwasrobbed)) 95 | 96 | 97 | 98 | \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* 99 | -------------------------------------------------------------------------------- /Example/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwasrobbed/RPCircularProgress/f607a1d6dd5f45006cc08b84eebae69903023814/Example/Default-568h@2x.png -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | 3 | platform :ios, '9.3' 4 | 5 | # ignore all warnings from all pods 6 | inhibit_all_warnings! 7 | 8 | # use frameworks for Swift 9 | use_frameworks! 10 | 11 | project 'RPCircularProgressExample' 12 | 13 | target 'RPCircularProgressExample' do 14 | # Autolayout 15 | pod 'SnapKit', '~> 4.2.0' 16 | 17 | target 'RPCircularProgressTests' do 18 | inherit! :search_paths 19 | 20 | # Tests 21 | pod 'Quick', '~> 2.0.0' 22 | pod 'Nimble', '~> 8.0.1' 23 | end 24 | end 25 | 26 | post_install do |installer| 27 | installer.pods_project.targets.each do |target| 28 | target.build_configurations.each do |config| 29 | config.build_settings['SWIFT_VERSION'] = '5.0' 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Nimble (8.0.1) 3 | - Quick (2.0.0) 4 | - SnapKit (4.2.0) 5 | 6 | DEPENDENCIES: 7 | - Nimble (~> 8.0.1) 8 | - Quick (~> 2.0.0) 9 | - SnapKit (~> 4.2.0) 10 | 11 | SPEC REPOS: 12 | https://github.com/cocoapods/specs.git: 13 | - Nimble 14 | - Quick 15 | - SnapKit 16 | 17 | SPEC CHECKSUMS: 18 | Nimble: 45f786ae66faa9a709624227fae502db55a8bdd0 19 | Quick: ce1276c7c27ba2da3cb2fd0cde053c3648b3b22d 20 | SnapKit: fe8a619752f3f27075cc9a90244d75c6c3f27e2a 21 | 22 | PODFILE CHECKSUM: d7fc967c643d286b7f3c814c731af17f8d85a18e 23 | 24 | COCOAPODS: 1.6.1 25 | -------------------------------------------------------------------------------- /Example/RPCircularProgressExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | A144166C80A61F1A77C31683 /* Pods_RPCircularProgressExample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A17D572C5E87E17DA6AF049D /* Pods_RPCircularProgressExample.framework */; }; 11 | B2FF371848CBDF4A0D397BB1 /* Pods_RPCircularProgressTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9367CDD11609C5B3CCAEB368 /* Pods_RPCircularProgressTests.framework */; }; 12 | D427C2561CB47563004980FF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D427C2551CB47563004980FF /* AppDelegate.swift */; }; 13 | D427C2581CB47563004980FF /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D427C2571CB47563004980FF /* ViewController.swift */; }; 14 | D427C25D1CB47563004980FF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D427C25C1CB47563004980FF /* Assets.xcassets */; }; 15 | D427C2681CB47601004980FF /* RPCircularProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = D427C2671CB47601004980FF /* RPCircularProgress.swift */; }; 16 | D427C26A1CB48707004980FF /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D427C2691CB48707004980FF /* Default-568h@2x.png */; }; 17 | D486AEA61F894EF7004ECB9A /* SnapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D486AEA71F894EF7004ECB9A /* SnapKit.framework */; }; 18 | D4F6630B1CB68A23007D8441 /* RPCircularProgressTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4F663091CB68A23007D8441 /* RPCircularProgressTests.swift */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXContainerItemProxy section */ 22 | D4756A4E1CB5D82E00432482 /* PBXContainerItemProxy */ = { 23 | isa = PBXContainerItemProxy; 24 | containerPortal = D427C24A1CB47562004980FF /* Project object */; 25 | proxyType = 1; 26 | remoteGlobalIDString = D427C2511CB47562004980FF; 27 | remoteInfo = RPCircularProgressExample; 28 | }; 29 | /* End PBXContainerItemProxy section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 3DCDEA18910B58124FB57F4D /* Pods-RPCircularProgressTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RPCircularProgressTests.release.xcconfig"; path = "Target Support Files/Pods-RPCircularProgressTests/Pods-RPCircularProgressTests.release.xcconfig"; sourceTree = ""; }; 33 | 8B5DCF99EF3F50C3D71EE9F1 /* Pods.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | 9367CDD11609C5B3CCAEB368 /* Pods_RPCircularProgressTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RPCircularProgressTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | A17D572C5E87E17DA6AF049D /* Pods_RPCircularProgressExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RPCircularProgressExample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 36 | B5CE00B7031A6E3D01360B71 /* Pods-RPCircularProgressExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RPCircularProgressExample.release.xcconfig"; path = "Target Support Files/Pods-RPCircularProgressExample/Pods-RPCircularProgressExample.release.xcconfig"; sourceTree = ""; }; 37 | BC956E952BBD410ED384E13C /* Pods-RPCircularProgressExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RPCircularProgressExample.debug.xcconfig"; path = "Target Support Files/Pods-RPCircularProgressExample/Pods-RPCircularProgressExample.debug.xcconfig"; sourceTree = ""; }; 38 | CE06F2091D5A122A0A7DAD78 /* Pods-RPCircularProgressTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RPCircularProgressTests.debug.xcconfig"; path = "Target Support Files/Pods-RPCircularProgressTests/Pods-RPCircularProgressTests.debug.xcconfig"; sourceTree = ""; }; 39 | D427C2521CB47562004980FF /* RPCircularProgressExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RPCircularProgressExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 40 | D427C2551CB47563004980FF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 41 | D427C2571CB47563004980FF /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 42 | D427C25C1CB47563004980FF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 43 | D427C2611CB47563004980FF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 44 | D427C2671CB47601004980FF /* RPCircularProgress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RPCircularProgress.swift; path = ../../Source/RPCircularProgress.swift; sourceTree = ""; }; 45 | D427C2691CB48707004980FF /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = ""; }; 46 | D4756A491CB5D82E00432482 /* RPCircularProgressTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RPCircularProgressTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 47 | D486AEA71F894EF7004ECB9A /* SnapKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SnapKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | D4F663081CB68A23007D8441 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 49 | D4F663091CB68A23007D8441 /* RPCircularProgressTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RPCircularProgressTests.swift; sourceTree = ""; }; 50 | /* End PBXFileReference section */ 51 | 52 | /* Begin PBXFrameworksBuildPhase section */ 53 | D427C24F1CB47562004980FF /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | D486AEA61F894EF7004ECB9A /* SnapKit.framework in Frameworks */, 58 | A144166C80A61F1A77C31683 /* Pods_RPCircularProgressExample.framework in Frameworks */, 59 | ); 60 | runOnlyForDeploymentPostprocessing = 0; 61 | }; 62 | D4756A461CB5D82E00432482 /* Frameworks */ = { 63 | isa = PBXFrameworksBuildPhase; 64 | buildActionMask = 2147483647; 65 | files = ( 66 | B2FF371848CBDF4A0D397BB1 /* Pods_RPCircularProgressTests.framework in Frameworks */, 67 | ); 68 | runOnlyForDeploymentPostprocessing = 0; 69 | }; 70 | /* End PBXFrameworksBuildPhase section */ 71 | 72 | /* Begin PBXGroup section */ 73 | C229183773C62ED225F1A883 /* Pods */ = { 74 | isa = PBXGroup; 75 | children = ( 76 | BC956E952BBD410ED384E13C /* Pods-RPCircularProgressExample.debug.xcconfig */, 77 | B5CE00B7031A6E3D01360B71 /* Pods-RPCircularProgressExample.release.xcconfig */, 78 | CE06F2091D5A122A0A7DAD78 /* Pods-RPCircularProgressTests.debug.xcconfig */, 79 | 3DCDEA18910B58124FB57F4D /* Pods-RPCircularProgressTests.release.xcconfig */, 80 | ); 81 | name = Pods; 82 | path = Pods; 83 | sourceTree = ""; 84 | }; 85 | D427C2491CB47562004980FF = { 86 | isa = PBXGroup; 87 | children = ( 88 | D427C2541CB47563004980FF /* RPCircularProgressExample */, 89 | D4F663071CB68A23007D8441 /* Tests */, 90 | D427C2531CB47562004980FF /* Products */, 91 | DE2EB8CC539C32BA2D2CB959 /* Frameworks */, 92 | C229183773C62ED225F1A883 /* Pods */, 93 | ); 94 | sourceTree = ""; 95 | }; 96 | D427C2531CB47562004980FF /* Products */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | D427C2521CB47562004980FF /* RPCircularProgressExample.app */, 100 | D4756A491CB5D82E00432482 /* RPCircularProgressTests.xctest */, 101 | ); 102 | name = Products; 103 | sourceTree = ""; 104 | }; 105 | D427C2541CB47563004980FF /* RPCircularProgressExample */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | D427C2551CB47563004980FF /* AppDelegate.swift */, 109 | D427C2571CB47563004980FF /* ViewController.swift */, 110 | D427C2671CB47601004980FF /* RPCircularProgress.swift */, 111 | D4756A531CB5D87500432482 /* Supporting Files */, 112 | ); 113 | path = RPCircularProgressExample; 114 | sourceTree = ""; 115 | }; 116 | D4756A531CB5D87500432482 /* Supporting Files */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | D427C2691CB48707004980FF /* Default-568h@2x.png */, 120 | D427C25C1CB47563004980FF /* Assets.xcassets */, 121 | D427C2611CB47563004980FF /* Info.plist */, 122 | ); 123 | name = "Supporting Files"; 124 | sourceTree = ""; 125 | }; 126 | D4F663071CB68A23007D8441 /* Tests */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | D4F663081CB68A23007D8441 /* Info.plist */, 130 | D4F663091CB68A23007D8441 /* RPCircularProgressTests.swift */, 131 | ); 132 | name = Tests; 133 | path = ../Tests; 134 | sourceTree = ""; 135 | }; 136 | DE2EB8CC539C32BA2D2CB959 /* Frameworks */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | D486AEA71F894EF7004ECB9A /* SnapKit.framework */, 140 | 8B5DCF99EF3F50C3D71EE9F1 /* Pods.framework */, 141 | A17D572C5E87E17DA6AF049D /* Pods_RPCircularProgressExample.framework */, 142 | 9367CDD11609C5B3CCAEB368 /* Pods_RPCircularProgressTests.framework */, 143 | ); 144 | name = Frameworks; 145 | sourceTree = ""; 146 | }; 147 | /* End PBXGroup section */ 148 | 149 | /* Begin PBXNativeTarget section */ 150 | D427C2511CB47562004980FF /* RPCircularProgressExample */ = { 151 | isa = PBXNativeTarget; 152 | buildConfigurationList = D427C2641CB47563004980FF /* Build configuration list for PBXNativeTarget "RPCircularProgressExample" */; 153 | buildPhases = ( 154 | 7262C079DBA22B0EF7F6E796 /* [CP] Check Pods Manifest.lock */, 155 | D427C24E1CB47562004980FF /* Sources */, 156 | D427C24F1CB47562004980FF /* Frameworks */, 157 | D427C2501CB47562004980FF /* Resources */, 158 | 39171F4B917A12A482667441 /* [CP] Embed Pods Frameworks */, 159 | ); 160 | buildRules = ( 161 | ); 162 | dependencies = ( 163 | ); 164 | name = RPCircularProgressExample; 165 | productName = RPCircularProgressExample; 166 | productReference = D427C2521CB47562004980FF /* RPCircularProgressExample.app */; 167 | productType = "com.apple.product-type.application"; 168 | }; 169 | D4756A481CB5D82E00432482 /* RPCircularProgressTests */ = { 170 | isa = PBXNativeTarget; 171 | buildConfigurationList = D4756A521CB5D82E00432482 /* Build configuration list for PBXNativeTarget "RPCircularProgressTests" */; 172 | buildPhases = ( 173 | 966D13C5AE49090F662A26AB /* [CP] Check Pods Manifest.lock */, 174 | D4756A451CB5D82E00432482 /* Sources */, 175 | D4756A461CB5D82E00432482 /* Frameworks */, 176 | D4756A471CB5D82E00432482 /* Resources */, 177 | 447C435D364DB532ABE59A47 /* [CP] Embed Pods Frameworks */, 178 | ); 179 | buildRules = ( 180 | ); 181 | dependencies = ( 182 | D4756A4F1CB5D82E00432482 /* PBXTargetDependency */, 183 | ); 184 | name = RPCircularProgressTests; 185 | productName = RPCircularProgressTests; 186 | productReference = D4756A491CB5D82E00432482 /* RPCircularProgressTests.xctest */; 187 | productType = "com.apple.product-type.bundle.unit-test"; 188 | }; 189 | /* End PBXNativeTarget section */ 190 | 191 | /* Begin PBXProject section */ 192 | D427C24A1CB47562004980FF /* Project object */ = { 193 | isa = PBXProject; 194 | attributes = { 195 | LastSwiftUpdateCheck = 0730; 196 | LastUpgradeCheck = 1020; 197 | ORGANIZATIONNAME = "Glazed Donut, LLC."; 198 | TargetAttributes = { 199 | D427C2511CB47562004980FF = { 200 | CreatedOnToolsVersion = 7.3; 201 | DevelopmentTeam = S4JEZRL8YY; 202 | LastSwiftMigration = 1020; 203 | }; 204 | D4756A481CB5D82E00432482 = { 205 | CreatedOnToolsVersion = 7.3; 206 | LastSwiftMigration = 1020; 207 | TestTargetID = D427C2511CB47562004980FF; 208 | }; 209 | }; 210 | }; 211 | buildConfigurationList = D427C24D1CB47562004980FF /* Build configuration list for PBXProject "RPCircularProgressExample" */; 212 | compatibilityVersion = "Xcode 3.2"; 213 | developmentRegion = en; 214 | hasScannedForEncodings = 0; 215 | knownRegions = ( 216 | en, 217 | Base, 218 | ); 219 | mainGroup = D427C2491CB47562004980FF; 220 | productRefGroup = D427C2531CB47562004980FF /* Products */; 221 | projectDirPath = ""; 222 | projectRoot = ""; 223 | targets = ( 224 | D427C2511CB47562004980FF /* RPCircularProgressExample */, 225 | D4756A481CB5D82E00432482 /* RPCircularProgressTests */, 226 | ); 227 | }; 228 | /* End PBXProject section */ 229 | 230 | /* Begin PBXResourcesBuildPhase section */ 231 | D427C2501CB47562004980FF /* Resources */ = { 232 | isa = PBXResourcesBuildPhase; 233 | buildActionMask = 2147483647; 234 | files = ( 235 | D427C25D1CB47563004980FF /* Assets.xcassets in Resources */, 236 | D427C26A1CB48707004980FF /* Default-568h@2x.png in Resources */, 237 | ); 238 | runOnlyForDeploymentPostprocessing = 0; 239 | }; 240 | D4756A471CB5D82E00432482 /* Resources */ = { 241 | isa = PBXResourcesBuildPhase; 242 | buildActionMask = 2147483647; 243 | files = ( 244 | ); 245 | runOnlyForDeploymentPostprocessing = 0; 246 | }; 247 | /* End PBXResourcesBuildPhase section */ 248 | 249 | /* Begin PBXShellScriptBuildPhase section */ 250 | 39171F4B917A12A482667441 /* [CP] Embed Pods Frameworks */ = { 251 | isa = PBXShellScriptBuildPhase; 252 | buildActionMask = 2147483647; 253 | files = ( 254 | ); 255 | inputFileListPaths = ( 256 | ); 257 | inputPaths = ( 258 | "${PODS_ROOT}/Target Support Files/Pods-RPCircularProgressExample/Pods-RPCircularProgressExample-frameworks.sh", 259 | "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework", 260 | ); 261 | name = "[CP] Embed Pods Frameworks"; 262 | outputFileListPaths = ( 263 | ); 264 | outputPaths = ( 265 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework", 266 | ); 267 | runOnlyForDeploymentPostprocessing = 0; 268 | shellPath = /bin/sh; 269 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-RPCircularProgressExample/Pods-RPCircularProgressExample-frameworks.sh\"\n"; 270 | showEnvVarsInLog = 0; 271 | }; 272 | 447C435D364DB532ABE59A47 /* [CP] Embed Pods Frameworks */ = { 273 | isa = PBXShellScriptBuildPhase; 274 | buildActionMask = 2147483647; 275 | files = ( 276 | ); 277 | inputFileListPaths = ( 278 | ); 279 | inputPaths = ( 280 | "${PODS_ROOT}/Target Support Files/Pods-RPCircularProgressTests/Pods-RPCircularProgressTests-frameworks.sh", 281 | "${BUILT_PRODUCTS_DIR}/Nimble/Nimble.framework", 282 | "${BUILT_PRODUCTS_DIR}/Quick/Quick.framework", 283 | ); 284 | name = "[CP] Embed Pods Frameworks"; 285 | outputFileListPaths = ( 286 | ); 287 | outputPaths = ( 288 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Nimble.framework", 289 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Quick.framework", 290 | ); 291 | runOnlyForDeploymentPostprocessing = 0; 292 | shellPath = /bin/sh; 293 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-RPCircularProgressTests/Pods-RPCircularProgressTests-frameworks.sh\"\n"; 294 | showEnvVarsInLog = 0; 295 | }; 296 | 7262C079DBA22B0EF7F6E796 /* [CP] Check Pods Manifest.lock */ = { 297 | isa = PBXShellScriptBuildPhase; 298 | buildActionMask = 2147483647; 299 | files = ( 300 | ); 301 | inputFileListPaths = ( 302 | ); 303 | inputPaths = ( 304 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 305 | "${PODS_ROOT}/Manifest.lock", 306 | ); 307 | name = "[CP] Check Pods Manifest.lock"; 308 | outputFileListPaths = ( 309 | ); 310 | outputPaths = ( 311 | "$(DERIVED_FILE_DIR)/Pods-RPCircularProgressExample-checkManifestLockResult.txt", 312 | ); 313 | runOnlyForDeploymentPostprocessing = 0; 314 | shellPath = /bin/sh; 315 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 316 | showEnvVarsInLog = 0; 317 | }; 318 | 966D13C5AE49090F662A26AB /* [CP] Check Pods Manifest.lock */ = { 319 | isa = PBXShellScriptBuildPhase; 320 | buildActionMask = 2147483647; 321 | files = ( 322 | ); 323 | inputFileListPaths = ( 324 | ); 325 | inputPaths = ( 326 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 327 | "${PODS_ROOT}/Manifest.lock", 328 | ); 329 | name = "[CP] Check Pods Manifest.lock"; 330 | outputFileListPaths = ( 331 | ); 332 | outputPaths = ( 333 | "$(DERIVED_FILE_DIR)/Pods-RPCircularProgressTests-checkManifestLockResult.txt", 334 | ); 335 | runOnlyForDeploymentPostprocessing = 0; 336 | shellPath = /bin/sh; 337 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 338 | showEnvVarsInLog = 0; 339 | }; 340 | /* End PBXShellScriptBuildPhase section */ 341 | 342 | /* Begin PBXSourcesBuildPhase section */ 343 | D427C24E1CB47562004980FF /* Sources */ = { 344 | isa = PBXSourcesBuildPhase; 345 | buildActionMask = 2147483647; 346 | files = ( 347 | D427C2581CB47563004980FF /* ViewController.swift in Sources */, 348 | D427C2681CB47601004980FF /* RPCircularProgress.swift in Sources */, 349 | D427C2561CB47563004980FF /* AppDelegate.swift in Sources */, 350 | ); 351 | runOnlyForDeploymentPostprocessing = 0; 352 | }; 353 | D4756A451CB5D82E00432482 /* Sources */ = { 354 | isa = PBXSourcesBuildPhase; 355 | buildActionMask = 2147483647; 356 | files = ( 357 | D4F6630B1CB68A23007D8441 /* RPCircularProgressTests.swift in Sources */, 358 | ); 359 | runOnlyForDeploymentPostprocessing = 0; 360 | }; 361 | /* End PBXSourcesBuildPhase section */ 362 | 363 | /* Begin PBXTargetDependency section */ 364 | D4756A4F1CB5D82E00432482 /* PBXTargetDependency */ = { 365 | isa = PBXTargetDependency; 366 | target = D427C2511CB47562004980FF /* RPCircularProgressExample */; 367 | targetProxy = D4756A4E1CB5D82E00432482 /* PBXContainerItemProxy */; 368 | }; 369 | /* End PBXTargetDependency section */ 370 | 371 | /* Begin XCBuildConfiguration section */ 372 | D427C2621CB47563004980FF /* Debug */ = { 373 | isa = XCBuildConfiguration; 374 | buildSettings = { 375 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; 376 | ALWAYS_SEARCH_USER_PATHS = NO; 377 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 378 | CLANG_ANALYZER_NONNULL = YES; 379 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 380 | CLANG_CXX_LIBRARY = "libc++"; 381 | CLANG_ENABLE_CODE_COVERAGE = YES; 382 | CLANG_ENABLE_MODULES = YES; 383 | CLANG_ENABLE_OBJC_ARC = YES; 384 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 385 | CLANG_WARN_BOOL_CONVERSION = YES; 386 | CLANG_WARN_COMMA = YES; 387 | CLANG_WARN_CONSTANT_CONVERSION = YES; 388 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 389 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 390 | CLANG_WARN_EMPTY_BODY = YES; 391 | CLANG_WARN_ENUM_CONVERSION = YES; 392 | CLANG_WARN_INFINITE_RECURSION = YES; 393 | CLANG_WARN_INT_CONVERSION = YES; 394 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 395 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 396 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 397 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 398 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 399 | CLANG_WARN_STRICT_PROTOTYPES = YES; 400 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 401 | CLANG_WARN_UNREACHABLE_CODE = YES; 402 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 403 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 404 | COPY_PHASE_STRIP = NO; 405 | DEBUG_INFORMATION_FORMAT = dwarf; 406 | ENABLE_STRICT_OBJC_MSGSEND = YES; 407 | ENABLE_TESTABILITY = YES; 408 | GCC_C_LANGUAGE_STANDARD = gnu99; 409 | GCC_DYNAMIC_NO_PIC = NO; 410 | GCC_NO_COMMON_BLOCKS = YES; 411 | GCC_OPTIMIZATION_LEVEL = 0; 412 | GCC_PREPROCESSOR_DEFINITIONS = ( 413 | "DEBUG=1", 414 | "$(inherited)", 415 | ); 416 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 417 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 418 | GCC_WARN_UNDECLARED_SELECTOR = YES; 419 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 420 | GCC_WARN_UNUSED_FUNCTION = YES; 421 | GCC_WARN_UNUSED_VARIABLE = YES; 422 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 423 | MTL_ENABLE_DEBUG_INFO = YES; 424 | ONLY_ACTIVE_ARCH = YES; 425 | SDKROOT = iphoneos; 426 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 427 | SWIFT_VERSION = 4.0; 428 | }; 429 | name = Debug; 430 | }; 431 | D427C2631CB47563004980FF /* Release */ = { 432 | isa = XCBuildConfiguration; 433 | buildSettings = { 434 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; 435 | ALWAYS_SEARCH_USER_PATHS = NO; 436 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 437 | CLANG_ANALYZER_NONNULL = YES; 438 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 439 | CLANG_CXX_LIBRARY = "libc++"; 440 | CLANG_ENABLE_CODE_COVERAGE = YES; 441 | CLANG_ENABLE_MODULES = YES; 442 | CLANG_ENABLE_OBJC_ARC = YES; 443 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 444 | CLANG_WARN_BOOL_CONVERSION = YES; 445 | CLANG_WARN_COMMA = YES; 446 | CLANG_WARN_CONSTANT_CONVERSION = YES; 447 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 448 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 449 | CLANG_WARN_EMPTY_BODY = YES; 450 | CLANG_WARN_ENUM_CONVERSION = YES; 451 | CLANG_WARN_INFINITE_RECURSION = YES; 452 | CLANG_WARN_INT_CONVERSION = YES; 453 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 454 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 455 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 456 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 457 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 458 | CLANG_WARN_STRICT_PROTOTYPES = YES; 459 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 460 | CLANG_WARN_UNREACHABLE_CODE = YES; 461 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 462 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 463 | COPY_PHASE_STRIP = NO; 464 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 465 | ENABLE_NS_ASSERTIONS = NO; 466 | ENABLE_STRICT_OBJC_MSGSEND = YES; 467 | GCC_C_LANGUAGE_STANDARD = gnu99; 468 | GCC_NO_COMMON_BLOCKS = YES; 469 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 470 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 471 | GCC_WARN_UNDECLARED_SELECTOR = YES; 472 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 473 | GCC_WARN_UNUSED_FUNCTION = YES; 474 | GCC_WARN_UNUSED_VARIABLE = YES; 475 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 476 | MTL_ENABLE_DEBUG_INFO = NO; 477 | SDKROOT = iphoneos; 478 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 479 | SWIFT_VERSION = 4.0; 480 | VALIDATE_PRODUCT = YES; 481 | }; 482 | name = Release; 483 | }; 484 | D427C2651CB47563004980FF /* Debug */ = { 485 | isa = XCBuildConfiguration; 486 | baseConfigurationReference = BC956E952BBD410ED384E13C /* Pods-RPCircularProgressExample.debug.xcconfig */; 487 | buildSettings = { 488 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; 489 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 490 | CLANG_ENABLE_MODULES = YES; 491 | DEVELOPMENT_TEAM = S4JEZRL8YY; 492 | INFOPLIST_FILE = RPCircularProgressExample/Info.plist; 493 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 494 | PRODUCT_BUNDLE_IDENTIFIER = com.glazedDonut.RPCircularProgressExample; 495 | PRODUCT_NAME = "$(TARGET_NAME)"; 496 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 497 | SWIFT_VERSION = 5.0; 498 | }; 499 | name = Debug; 500 | }; 501 | D427C2661CB47563004980FF /* Release */ = { 502 | isa = XCBuildConfiguration; 503 | baseConfigurationReference = B5CE00B7031A6E3D01360B71 /* Pods-RPCircularProgressExample.release.xcconfig */; 504 | buildSettings = { 505 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; 506 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 507 | CLANG_ENABLE_MODULES = YES; 508 | DEVELOPMENT_TEAM = S4JEZRL8YY; 509 | INFOPLIST_FILE = RPCircularProgressExample/Info.plist; 510 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 511 | PRODUCT_BUNDLE_IDENTIFIER = com.glazedDonut.RPCircularProgressExample; 512 | PRODUCT_NAME = "$(TARGET_NAME)"; 513 | SWIFT_VERSION = 5.0; 514 | }; 515 | name = Release; 516 | }; 517 | D4756A501CB5D82E00432482 /* Debug */ = { 518 | isa = XCBuildConfiguration; 519 | baseConfigurationReference = CE06F2091D5A122A0A7DAD78 /* Pods-RPCircularProgressTests.debug.xcconfig */; 520 | buildSettings = { 521 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; 522 | BUNDLE_LOADER = "$(TEST_HOST)"; 523 | INFOPLIST_FILE = ../Tests/Info.plist; 524 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 525 | PRODUCT_BUNDLE_IDENTIFIER = com.glazedDonut.RPCircularProgressTests; 526 | PRODUCT_NAME = "$(TARGET_NAME)"; 527 | SWIFT_VERSION = 5.0; 528 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RPCircularProgressExample.app/RPCircularProgressExample"; 529 | }; 530 | name = Debug; 531 | }; 532 | D4756A511CB5D82E00432482 /* Release */ = { 533 | isa = XCBuildConfiguration; 534 | baseConfigurationReference = 3DCDEA18910B58124FB57F4D /* Pods-RPCircularProgressTests.release.xcconfig */; 535 | buildSettings = { 536 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; 537 | BUNDLE_LOADER = "$(TEST_HOST)"; 538 | INFOPLIST_FILE = ../Tests/Info.plist; 539 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 540 | PRODUCT_BUNDLE_IDENTIFIER = com.glazedDonut.RPCircularProgressTests; 541 | PRODUCT_NAME = "$(TARGET_NAME)"; 542 | SWIFT_VERSION = 5.0; 543 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RPCircularProgressExample.app/RPCircularProgressExample"; 544 | }; 545 | name = Release; 546 | }; 547 | /* End XCBuildConfiguration section */ 548 | 549 | /* Begin XCConfigurationList section */ 550 | D427C24D1CB47562004980FF /* Build configuration list for PBXProject "RPCircularProgressExample" */ = { 551 | isa = XCConfigurationList; 552 | buildConfigurations = ( 553 | D427C2621CB47563004980FF /* Debug */, 554 | D427C2631CB47563004980FF /* Release */, 555 | ); 556 | defaultConfigurationIsVisible = 0; 557 | defaultConfigurationName = Release; 558 | }; 559 | D427C2641CB47563004980FF /* Build configuration list for PBXNativeTarget "RPCircularProgressExample" */ = { 560 | isa = XCConfigurationList; 561 | buildConfigurations = ( 562 | D427C2651CB47563004980FF /* Debug */, 563 | D427C2661CB47563004980FF /* Release */, 564 | ); 565 | defaultConfigurationIsVisible = 0; 566 | defaultConfigurationName = Release; 567 | }; 568 | D4756A521CB5D82E00432482 /* Build configuration list for PBXNativeTarget "RPCircularProgressTests" */ = { 569 | isa = XCConfigurationList; 570 | buildConfigurations = ( 571 | D4756A501CB5D82E00432482 /* Debug */, 572 | D4756A511CB5D82E00432482 /* Release */, 573 | ); 574 | defaultConfigurationIsVisible = 0; 575 | defaultConfigurationName = Release; 576 | }; 577 | /* End XCConfigurationList section */ 578 | }; 579 | rootObject = D427C24A1CB47562004980FF /* Project object */; 580 | } 581 | -------------------------------------------------------------------------------- /Example/RPCircularProgressExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/RPCircularProgressExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/RPCircularProgressExample.xcodeproj/xcshareddata/xcschemes/RPCircularProgressExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 65 | 67 | 73 | 74 | 75 | 76 | 77 | 78 | 84 | 86 | 92 | 93 | 94 | 95 | 97 | 98 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /Example/RPCircularProgressExample.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/RPCircularProgressExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/RPCircularProgressExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // RPCircularProgressExample 4 | // 5 | // Created by Rob Phillips on 4/5/16. 6 | // Copyright © 2016 Glazed Donut, LLC. All rights reserved. 7 | // 8 | // See LICENSE for full license agreement. 9 | // 10 | 11 | import UIKit 12 | 13 | @UIApplicationMain 14 | class AppDelegate: UIResponder, UIApplicationDelegate { 15 | 16 | var window: UIWindow? 17 | 18 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 19 | 20 | setupRootView() 21 | 22 | return true 23 | } 24 | 25 | fileprivate func setupRootView() { 26 | window = UIWindow.init(frame: UIScreen.main.bounds) 27 | window?.rootViewController = ViewController() 28 | window?.makeKeyAndVisible() 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /Example/RPCircularProgressExample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /Example/RPCircularProgressExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /Example/RPCircularProgressExample/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // RPCircularProgressExample 4 | // 5 | // Created by Rob Phillips on 4/5/16. 6 | // Copyright © 2016 Glazed Donut, LLC. All rights reserved. 7 | // 8 | // See LICENSE for full license agreement. 9 | // 10 | 11 | import UIKit 12 | import SnapKit 13 | 14 | class ViewController: UIViewController { 15 | 16 | // MARK: - Lifecycle 17 | 18 | let container = UIView() 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | 23 | setupView() 24 | } 25 | 26 | // MARK: - Indeterminate Progress Examples 27 | 28 | lazy fileprivate var thinIndeterminate: RPCircularProgress = { 29 | let progress = RPCircularProgress() 30 | progress.thicknessRatio = 0.1 31 | return progress 32 | }() 33 | 34 | lazy fileprivate var thinFilledIndeterminate: RPCircularProgress = { 35 | let progress = RPCircularProgress() 36 | progress.innerTintColor = UIColor.red 37 | progress.thicknessRatio = 0.2 38 | progress.indeterminateDuration = 0.5 39 | return progress 40 | }() 41 | 42 | lazy fileprivate var unroundedIndeterminate: RPCircularProgress = { 43 | let progress = RPCircularProgress() 44 | progress.roundedCorners = false 45 | progress.thicknessRatio = 0.6 46 | progress.clockwiseProgress = false 47 | return progress 48 | }() 49 | 50 | lazy fileprivate var chartIndeterminate: RPCircularProgress = { 51 | let progress = RPCircularProgress() 52 | progress.roundedCorners = false 53 | progress.thicknessRatio = 1 54 | return progress 55 | }() 56 | 57 | // MARK: - Progress Examples 58 | 59 | lazy fileprivate var thinProgress: RPCircularProgress = { 60 | let progress = RPCircularProgress() 61 | progress.thicknessRatio = 0.2 62 | return progress 63 | }() 64 | 65 | lazy fileprivate var thinFilledProgress: RPCircularProgress = { 66 | let progress = RPCircularProgress() 67 | progress.trackTintColor = UIColor.init(red: 74 / 255, green: 144 / 255, blue: 226 / 255, alpha: 0.3) 68 | progress.progressTintColor = UIColor.init(red: 74 / 255, green: 144 / 255, blue: 226 / 255, alpha: 1) 69 | progress.thicknessRatio = 0.5 70 | return progress 71 | }() 72 | 73 | lazy fileprivate var unroundedProgress: RPCircularProgress = { 74 | let progress = RPCircularProgress() 75 | progress.roundedCorners = false 76 | progress.thicknessRatio = 0.3 77 | return progress 78 | }() 79 | 80 | lazy fileprivate var chartProgress: RPCircularProgress = { 81 | let progress = RPCircularProgress() 82 | progress.roundedCorners = false 83 | progress.thicknessRatio = 1 84 | return progress 85 | }() 86 | 87 | } 88 | 89 | private extension ViewController { 90 | 91 | // MARK: - Constrain Views 92 | 93 | func setupView() { 94 | setupContainer() 95 | 96 | setupThinIndeterminate() 97 | setupThinFilledIndeterminate() 98 | setupUnroundedIndeterminate() 99 | setupChartIndeterminate() 100 | 101 | setupThinProgress() 102 | setupThinFilledProgress() 103 | setupUnroundedProgress() 104 | setupChartProgress() 105 | } 106 | 107 | func setupContainer() { 108 | view.addSubview(container) 109 | container.snp.makeConstraints { (make) in 110 | make.center.equalTo(view) 111 | make.width.equalTo(view).multipliedBy(0.8) 112 | make.height.equalTo(view).multipliedBy(0.4) 113 | } 114 | } 115 | 116 | func setupThinIndeterminate() { 117 | constrain(thinIndeterminate) 118 | 119 | thinIndeterminate.enableIndeterminate() 120 | } 121 | 122 | func setupThinFilledIndeterminate() { 123 | constrain(thinFilledIndeterminate, leftView: thinIndeterminate) 124 | 125 | thinFilledIndeterminate.enableIndeterminate() 126 | } 127 | 128 | func setupUnroundedIndeterminate() { 129 | constrain(unroundedIndeterminate, leftView: thinFilledIndeterminate) 130 | 131 | unroundedIndeterminate.enableIndeterminate() 132 | } 133 | 134 | func setupChartIndeterminate() { 135 | constrain(chartIndeterminate, leftView: unroundedIndeterminate) 136 | 137 | chartIndeterminate.enableIndeterminate() 138 | } 139 | 140 | func setupThinProgress() { 141 | constrain(thinProgress, topView: thinIndeterminate) 142 | 143 | // You can update progress while being indeterminate if you'd like 144 | thinProgress.updateProgress(0.4, duration: 5) 145 | thinProgress.enableIndeterminate() 146 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(2 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)) { 147 | self.thinProgress.updateProgress(1, completion: { 148 | self.thinProgress.enableIndeterminate(false) 149 | }) 150 | } 151 | } 152 | 153 | func setupThinFilledProgress() { 154 | constrain(thinFilledProgress, leftView: thinProgress) 155 | 156 | thinFilledProgress.updateProgress(0.4, initialDelay: 0.4, duration: 3) 157 | } 158 | 159 | func setupUnroundedProgress() { 160 | constrain(unroundedProgress, leftView: thinFilledProgress) 161 | 162 | unroundedProgress.updateProgress(0.4, initialDelay: 0.6, duration: 4) 163 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(2 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)) { 164 | self.unroundedProgress.updateProgress(0.9) 165 | } 166 | } 167 | 168 | func setupChartProgress() { 169 | constrain(chartProgress, leftView: unroundedProgress) 170 | 171 | chartProgress.updateProgress(0.3, animated: false, initialDelay: 1) 172 | } 173 | 174 | // MARK: - Setup Helpers 175 | 176 | func constrain(_ newView: UIView, topView: UIView? = nil) { 177 | container.addSubview(newView) 178 | newView.snp.makeConstraints { (make) in 179 | make.size.equalTo(40) 180 | make.left.equalTo(container).offset(20) 181 | if let topView = topView { 182 | make.top.equalTo(topView.snp.bottom).offset(20) 183 | } else { 184 | make.top.equalTo(container).offset(20) 185 | } 186 | } 187 | } 188 | 189 | func constrain(_ newView: UIView, leftView: UIView) { 190 | container.addSubview(newView) 191 | newView.snp.makeConstraints { (make) in 192 | make.size.equalTo(40) 193 | make.left.equalTo(leftView.snp.right).offset(20) 194 | make.top.equalTo(leftView) 195 | } 196 | } 197 | 198 | } 199 | 200 | 201 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Rob Phillips. 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## RPCircularProgress 2 | 3 | [![Coverage Status](https://coveralls.io/repos/github/iwasrobbed/RPCircularProgress/badge.svg?branch=master)](https://coveralls.io/github/iwasrobbed/RPCircularProgress?branch=master) 4 | [![Build Status](https://travis-ci.org/iwasrobbed/RPCircularProgress.svg?branch=master)](https://travis-ci.org/iwasrobbed/RPCircularProgress) 5 | [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/iwasrobbed/RPCircularProgress/blob/master/LICENSE) 6 | [![CocoaPods](https://img.shields.io/cocoapods/v/RPCircularProgress.svg?maxAge=2592000)]() 7 | [![Swift](https://img.shields.io/badge/language-Swift-blue.svg)](https://swift.org) 8 | 9 | ⚠️ To use with Swift 2.3 please ensure you are using == 0.2.3 ⚠️ 10 | 11 | ⚠️ To use with Swift 3.x please ensure you are using >= 0.3.0 ⚠️ 12 | 13 | ⚠️ To use with Swift 4.x please ensure you are using >= 0.4.0 ⚠️ 14 | 15 | ⚠️ To use with Swift 4.2 please ensure you are using >= 0.4.1 ⚠️ 16 | 17 | ⚠️ To use with Swift 5.0 please ensure you are using >= 0.5.0 ⚠️ 18 | 19 | UIView subclass written in Swift to show circular progress. 20 | 21 | ![Example Usage](ohhai.gif) 22 | 23 | Is your app using it? [Let me know!](mailto:rob@desideratalabs.co) 24 | 25 | Please see the included example app for sample usage. 26 | 27 | ### Styles 28 | 29 | * Indeterminate (spins infinitely) 30 | * Normal (set progress between 0.0 and 1.0) 31 | * Mixed (can make it indeterminate, but also animate progress to 1.0) 32 | 33 | ### Public API 34 | 35 | ```swift 36 | /** 37 | Enables or disables the indeterminate (spinning) animation 38 | 39 | - parameter enabled: Whether or not to enable the animation (defaults to `true`) 40 | - parameter completion: An optional closure to execute after the animation completes 41 | */ 42 | open func enableIndeterminate(_ enabled: Bool = true, completion: CompletionBlock? = nil) {} 43 | ``` 44 | 45 | ```swift 46 | /** 47 | Updates the progress bar to the given value with the optional properties 48 | 49 | - parameter progress: The progress to update to, pinned between `0` and `1` 50 | - parameter animated: Whether or not the update should be animated (defaults to `true`) 51 | - parameter initialDelay: Sets an initial delay before the animation begins 52 | - parameter duration: Sets the overal duration that the animation should complete within 53 | - parameter completion: An optional closure to execute after the animation completes 54 | */ 55 | open func updateProgress(_ progress: CGFloat, animated: Bool = true, initialDelay: CFTimeInterval = 0, duration: CFTimeInterval? = nil, completion: CompletionBlock? = nil) {} 56 | ``` 57 | 58 | ### Properties 59 | 60 | Note: Most properties below are `@IBInspectable`, but I don't use Interface Builder personally so let me know if you see any issues. 61 | 62 | `var trackTintColor: UIColor` 63 | * The color of the empty progress track (gets drawn over) 64 | 65 | `var progressTintColor: UIColor` 66 | * The color of the progress bar 67 | 68 | `var innerTintColor: UIColor?` 69 | * The color the notched out circle within the progress area (if there is one) 70 | 71 | `var roundedCorners: Bool` 72 | * Sets whether or not the corners of the progress bar should be rounded 73 | 74 | `var thicknessRatio: CGFloat` 75 | * Sets how thick the progress bar should be (pinned between `0.01` and `1`) 76 | 77 | `var clockwiseProgress: Bool` 78 | * Sets whether or not the animation should be clockwise 79 | 80 | `var timingFunction: CAMediaTimingFunction` 81 | * A timing function defining the pacing of the animation. Defaults to ease in, ease out. 82 | 83 | `var progress: CGFloat` 84 | * Getter for the current progress (not observed from any active animations) 85 | 86 | `var indeterminateProgress: CGFloat` 87 | * Sets how much of the progress bar should be filled during an indeterminate animation, pinned between `0.05` and `0.9` 88 | * **Note:** This can be overriden / animated from by using updateProgress(...) 89 | 90 | `var indeterminateDuration: CFTimeInterval` 91 | * Controls the speed at which the indeterminate progress bar animates 92 | 93 | ### Supports 94 | Swift, ARC & iOS 8+, Autolayout or springs and struts 95 | 96 | ### A little help from my friends 97 | Please feel free to fork and create a pull request for bug fixes or improvements, being sure to maintain the general coding style, adding tests, and adding comments as necessary. 98 | 99 | ### Credit 100 | This library is effectively a Swift port of [DACircularProgress](https://github.com/danielamitay/DACircularProgress) with some minor changes to the API, so it should be fairly easy to act as a replacement. I really loved that library but unfortunately it wasn't being maintained. 101 | -------------------------------------------------------------------------------- /RPCircularProgress.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = "RPCircularProgress" 3 | spec.summary = "Swift UIView subclass with circular progress properties." 4 | spec.version = "0.5.0" 5 | spec.homepage = "https://github.com/iwasrobbed/RPCircularProgress" 6 | spec.license = { :type => "MIT", :file => "LICENSE" } 7 | spec.authors = { "Rob Phillips" => "rob@robphillips.me" } 8 | spec.source = { :git => "https://github.com/iwasrobbed/RPCircularProgress.git", :tag => "v" + spec.version.to_s } 9 | spec.source_files = "Source" 10 | spec.platform = :ios, "8.0" 11 | spec.swift_version = '5.0' 12 | spec.requires_arc = true 13 | end 14 | -------------------------------------------------------------------------------- /Source/RPCircularProgress.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RPCircularProgress.swift 3 | // RPCircularProgress 4 | // 5 | // Created by Rob Phillips on 4/5/16. 6 | // Copyright © 2016 Glazed Donut, LLC. All rights reserved. 7 | // 8 | // See LICENSE for full license agreement. 9 | // 10 | 11 | import UIKit 12 | 13 | open class RPCircularProgress: UIView { 14 | 15 | // MARK: - Completion 16 | 17 | public typealias CompletionBlock = () -> Void 18 | 19 | // MARK: - Public API 20 | 21 | /** 22 | The color of the empty progress track (gets drawn over) 23 | */ 24 | @IBInspectable open var trackTintColor: UIColor { 25 | get { 26 | return progressLayer.trackTintColor 27 | } 28 | set { 29 | progressLayer.trackTintColor = newValue 30 | progressLayer.setNeedsDisplay() 31 | } 32 | } 33 | 34 | /** 35 | The color of the progress bar 36 | */ 37 | @IBInspectable open var progressTintColor: UIColor { 38 | get { 39 | return progressLayer.progressTintColor 40 | } 41 | set { 42 | progressLayer.progressTintColor = newValue 43 | progressLayer.setNeedsDisplay() 44 | } 45 | } 46 | 47 | /** 48 | The color the notched out circle within the progress area (if there is one) 49 | */ 50 | @IBInspectable open var innerTintColor: UIColor? { 51 | get { 52 | return progressLayer.innerTintColor 53 | } 54 | set { 55 | progressLayer.innerTintColor = newValue 56 | progressLayer.setNeedsDisplay() 57 | } 58 | } 59 | 60 | /** 61 | Sets whether or not the corners of the progress bar should be rounded 62 | */ 63 | @IBInspectable open var roundedCorners: Bool { 64 | get { 65 | return progressLayer.roundedCorners 66 | } 67 | set { 68 | progressLayer.roundedCorners = newValue 69 | progressLayer.setNeedsDisplay() 70 | } 71 | } 72 | 73 | /** 74 | Sets how thick the progress bar should be (pinned between `0.01` and `1`) 75 | */ 76 | @IBInspectable open var thicknessRatio: CGFloat { 77 | get { 78 | return progressLayer.thicknessRatio 79 | } 80 | set { 81 | progressLayer.thicknessRatio = pin(newValue, minValue: 0.01, maxValue: 1) 82 | progressLayer.setNeedsDisplay() 83 | } 84 | } 85 | 86 | /** 87 | Sets whether or not the animation should be clockwise 88 | */ 89 | @IBInspectable open var clockwiseProgress: Bool { 90 | get { 91 | return progressLayer.clockwiseProgress 92 | } 93 | set { 94 | progressLayer.clockwiseProgress = newValue 95 | progressLayer.setNeedsDisplay() 96 | } 97 | } 98 | 99 | /** 100 | A timing function defining the pacing of the animation. Defaults to ease in, ease out. 101 | */ 102 | open var timingFunction: CAMediaTimingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut) 103 | 104 | /** 105 | Getter for the current progress (not observed from any active animations) 106 | */ 107 | @IBInspectable open var progress: CGFloat { 108 | get { 109 | return progressLayer.progress 110 | } 111 | } 112 | 113 | /** 114 | Sets how much of the progress bar should be filled during an indeterminate animation, pinned between `0.05` and `0.9` 115 | 116 | **Note:** This can be overriden / animated from by using updateProgress(...) 117 | */ 118 | @IBInspectable open var indeterminateProgress: CGFloat { 119 | get { 120 | return progressLayer.indeterminateProgress 121 | } 122 | set { 123 | progressLayer.indeterminateProgress = pin(newValue, minValue: 0.05, maxValue: 0.9) 124 | } 125 | } 126 | 127 | /** 128 | Controls the speed at which the indeterminate progress bar animates 129 | */ 130 | @IBInspectable open var indeterminateDuration: CFTimeInterval = Defaults.indeterminateDuration 131 | 132 | // MARK: - Custom Base Layer 133 | 134 | fileprivate var progressLayer: ProgressLayer! { 135 | get { 136 | return (layer as! ProgressLayer) 137 | } 138 | } 139 | 140 | open override class var layerClass : AnyClass { 141 | return ProgressLayer.self 142 | } 143 | 144 | // Lifecycle 145 | 146 | /** 147 | Default initializer for the class 148 | 149 | - returns: A configured instance of self 150 | */ 151 | required public init() { 152 | super.init(frame: CGRect(x: 0, y: 0, width: 40, height: 40)) 153 | 154 | setupDefaults() 155 | } 156 | 157 | required public init?(coder aDecoder: NSCoder) { 158 | super.init(coder: aDecoder) 159 | 160 | setupDefaults() 161 | } 162 | 163 | open override func didMoveToWindow() { 164 | super.didMoveToWindow() 165 | 166 | if let window = window { 167 | progressLayer.contentsScale = window.screen.scale 168 | progressLayer.setNeedsDisplay() 169 | } 170 | } 171 | 172 | // MARK: - Indeterminate 173 | 174 | /** 175 | Enables or disables the indeterminate (spinning) animation 176 | 177 | - parameter enabled: Whether or not to enable the animation (defaults to `true`) 178 | - parameter completion: An optional closure to execute after the animation completes 179 | */ 180 | open func enableIndeterminate(_ enabled: Bool = true, completion: CompletionBlock? = nil) { 181 | if let animation = progressLayer.animation(forKey: AnimationKeys.indeterminate) { 182 | // Check if there are any closures to execute on the existing animation 183 | if let block = animation.value(forKey: AnimationKeys.completionBlock) as? CompletionBlockObject { 184 | block.action() 185 | } 186 | progressLayer.removeAnimation(forKey: AnimationKeys.indeterminate) 187 | 188 | // And notify of disabling completion 189 | completion?() 190 | } 191 | 192 | guard enabled else { return } 193 | 194 | addIndeterminateAnimation(completion) 195 | } 196 | 197 | // MARK: - Progress 198 | 199 | /** 200 | Updates the progress bar to the given value with the optional properties 201 | 202 | - parameter progress: The progress to update to, pinned between `0` and `1` 203 | - parameter animated: Whether or not the update should be animated (defaults to `true`) 204 | - parameter initialDelay: Sets an initial delay before the animation begins 205 | - parameter duration: Sets the overal duration that the animation should complete within 206 | - parameter completion: An optional closure to execute after the animation completes 207 | */ 208 | open func updateProgress(_ progress: CGFloat, animated: Bool = true, initialDelay: CFTimeInterval = 0, duration: CFTimeInterval? = nil, completion: CompletionBlock? = nil) { 209 | let pinnedProgress = pin(progress) 210 | if animated { 211 | 212 | // Get duration 213 | let animationDuration: CFTimeInterval 214 | if let duration = duration, duration != 0 { 215 | animationDuration = duration 216 | } else { 217 | // Same duration as UIProgressView animation 218 | animationDuration = CFTimeInterval(fabsf(Float(self.progress) - Float(pinnedProgress))) 219 | } 220 | 221 | // Get current progress (to avoid jumpy behavior) 222 | // Basic animations have their value reset to the original once the animation is finished 223 | // since only the presentation layer is animating 224 | var currentProgress: CGFloat = 0 225 | if let presentationLayer = progressLayer.presentation() { 226 | currentProgress = presentationLayer.progress 227 | } 228 | progressLayer.progress = currentProgress 229 | 230 | progressLayer.removeAnimation(forKey: AnimationKeys.progress) 231 | animate(progress, currentProgress: currentProgress, initialDelay: initialDelay, duration: animationDuration, completion: completion) 232 | } else { 233 | progressLayer.removeAnimation(forKey: AnimationKeys.progress) 234 | 235 | progressLayer.progress = pinnedProgress 236 | progressLayer.setNeedsDisplay() 237 | 238 | completion?() 239 | } 240 | } 241 | } 242 | 243 | // MARK: - Private API 244 | 245 | private extension RPCircularProgress { 246 | 247 | // MARK: - Defaults 248 | 249 | func setupDefaults() { 250 | progressLayer.trackTintColor = Defaults.trackTintColor 251 | progressLayer.progressTintColor = Defaults.progressTintColor 252 | progressLayer.innerTintColor = nil 253 | backgroundColor = Defaults.backgroundColor 254 | progressLayer.thicknessRatio = Defaults.thicknessRatio 255 | progressLayer.roundedCorners = Defaults.roundedCorners 256 | progressLayer.clockwiseProgress = Defaults.clockwiseProgress 257 | indeterminateDuration = Defaults.indeterminateDuration 258 | progressLayer.indeterminateProgress = Defaults.indeterminateProgress 259 | } 260 | 261 | // MARK: - Progress 262 | 263 | // Pin certain values between 0.0 and 1.0 264 | func pin(_ value: CGFloat, minValue: CGFloat = 0, maxValue: CGFloat = 1) -> CGFloat { 265 | return min(max(value, minValue), maxValue) 266 | } 267 | 268 | func animate(_ pinnedProgress: CGFloat, currentProgress: CGFloat, initialDelay: CFTimeInterval, duration: CFTimeInterval, completion: CompletionBlock?) { 269 | let animation = CABasicAnimation(keyPath: AnimationKeys.progress) 270 | animation.duration = duration 271 | animation.timingFunction = timingFunction 272 | animation.fromValue = currentProgress 273 | animation.fillMode = CAMediaTimingFillMode.forwards 274 | animation.isRemovedOnCompletion = false 275 | animation.toValue = pinnedProgress 276 | animation.beginTime = CACurrentMediaTime() + initialDelay 277 | animation.delegate = self 278 | if let completion = completion { 279 | let completionObject = CompletionBlockObject(action: completion) 280 | animation.setValue(completionObject, forKey: AnimationKeys.completionBlock) 281 | } 282 | progressLayer.add(animation, forKey: AnimationKeys.progress) 283 | } 284 | 285 | // MARK: - Indeterminate 286 | 287 | func addIndeterminateAnimation(_ completion: CompletionBlock?) { 288 | guard progressLayer.animation(forKey: AnimationKeys.indeterminate) == nil else { return } 289 | 290 | let animation = CABasicAnimation(keyPath: AnimationKeys.transformRotation) 291 | animation.byValue = clockwiseProgress ? 2 * Double.pi : -2 * Double.pi 292 | animation.duration = indeterminateDuration 293 | animation.repeatCount = Float.infinity 294 | animation.isRemovedOnCompletion = false 295 | progressLayer.progress = indeterminateProgress 296 | if let completion = completion { 297 | let completionObject = CompletionBlockObject(action: completion) 298 | animation.setValue(completionObject, forKey: AnimationKeys.completionBlock) 299 | } 300 | progressLayer.add(animation, forKey: AnimationKeys.indeterminate) 301 | } 302 | 303 | // Completion 304 | 305 | class CompletionBlockObject: NSObject { 306 | var action: CompletionBlock 307 | 308 | required init(action: @escaping CompletionBlock) { 309 | self.action = action 310 | } 311 | } 312 | 313 | // MARK: - Private Classes / Structs 314 | 315 | class ProgressLayer: CALayer { 316 | @NSManaged var trackTintColor: UIColor 317 | @NSManaged var progressTintColor: UIColor 318 | @NSManaged var innerTintColor: UIColor? 319 | 320 | @NSManaged var roundedCorners: Bool 321 | @NSManaged var clockwiseProgress: Bool 322 | @NSManaged var thicknessRatio: CGFloat 323 | 324 | @NSManaged var indeterminateProgress: CGFloat 325 | // This needs to have a setter/getter for it to work with CoreAnimation 326 | @NSManaged var progress: CGFloat 327 | 328 | override class func needsDisplay(forKey key: String) -> Bool { 329 | return key == AnimationKeys.progress ? true : super.needsDisplay(forKey: key) 330 | } 331 | 332 | override func draw(in ctx: CGContext) { 333 | let rect = bounds 334 | let centerPoint = CGPoint(x: rect.size.width / 2, y: rect.size.height / 2) 335 | let radius = min(rect.size.height, rect.size.width) / 2 336 | 337 | let progress: CGFloat = min(self.progress, CGFloat(1 - Float.ulpOfOne)) 338 | var radians: CGFloat = 0 339 | if clockwiseProgress { 340 | radians = CGFloat((Double(progress) * 2 * Double.pi) - (Double.pi / 2)) 341 | } else { 342 | radians = CGFloat(3 * (Double.pi / 2) - (Double(progress) * 2 * Double.pi)) 343 | } 344 | 345 | func fillTrack() { 346 | ctx.setFillColor(trackTintColor.cgColor) 347 | let trackPath = CGMutablePath() 348 | trackPath.move(to: centerPoint) 349 | trackPath.addArc(center: centerPoint, radius: radius, startAngle: CGFloat(2 * Double.pi), endAngle: 0, clockwise: true) 350 | trackPath.closeSubpath() 351 | ctx.addPath(trackPath) 352 | ctx.fillPath() 353 | } 354 | 355 | func fillProgressIfNecessary() { 356 | if progress == 0.0 { 357 | return 358 | } 359 | 360 | func fillProgress() { 361 | ctx.setFillColor(progressTintColor.cgColor) 362 | let progressPath = CGMutablePath() 363 | progressPath.move(to: centerPoint) 364 | progressPath.addArc(center: centerPoint, radius: radius, startAngle: CGFloat(3 * (Double.pi / 2)), endAngle: radians, clockwise: !clockwiseProgress) 365 | progressPath.closeSubpath() 366 | ctx.addPath(progressPath) 367 | ctx.fillPath() 368 | } 369 | 370 | func roundCornersIfNecessary() { 371 | if !roundedCorners { 372 | return 373 | } 374 | 375 | let pathWidth = radius * thicknessRatio 376 | let xOffset = radius * (1 + ((1 - (thicknessRatio / 2)) * CGFloat(cosf(Float(radians))))) 377 | let yOffset = radius * (1 + ((1 - (thicknessRatio / 2)) * CGFloat(sinf(Float(radians))))) 378 | let endpoint = CGPoint(x: xOffset, y: yOffset) 379 | 380 | let startEllipseRect = CGRect(x: centerPoint.x - pathWidth / 2, y: 0, width: pathWidth, height: pathWidth) 381 | ctx.addEllipse(in: startEllipseRect) 382 | ctx.fillPath() 383 | 384 | let endEllipseRect = CGRect(x: endpoint.x - pathWidth / 2, y: endpoint.y - pathWidth / 2, width: pathWidth, height: pathWidth) 385 | ctx.addEllipse(in: endEllipseRect) 386 | ctx.fillPath() 387 | } 388 | 389 | fillProgress() 390 | roundCornersIfNecessary() 391 | } 392 | 393 | func notchCenterCircle() { 394 | ctx.setBlendMode(.clear) 395 | let innerRadius = radius * (1 - thicknessRatio) 396 | let clearRect = CGRect(x: centerPoint.x - innerRadius, y: centerPoint.y - innerRadius, width: innerRadius * 2, height: innerRadius * 2) 397 | ctx.addEllipse(in: clearRect) 398 | ctx.fillPath() 399 | 400 | func fillInnerTintIfNecessary() { 401 | if let innerTintColor = innerTintColor { 402 | ctx.setBlendMode(.normal) 403 | ctx.setFillColor(innerTintColor.cgColor) 404 | ctx.addEllipse(in: clearRect) 405 | ctx.fillPath() 406 | } 407 | } 408 | 409 | fillInnerTintIfNecessary() 410 | } 411 | 412 | fillTrack() 413 | fillProgressIfNecessary() 414 | notchCenterCircle() 415 | } 416 | } 417 | 418 | struct Defaults { 419 | static let trackTintColor = UIColor(white: 1.0, alpha: 0.3) 420 | static let progressTintColor = UIColor.white 421 | static let backgroundColor = UIColor.clear 422 | 423 | static let progress: CGFloat = 0 424 | static let thicknessRatio: CGFloat = 0.3 425 | static let roundedCorners = true 426 | static let clockwiseProgress = true 427 | static let indeterminateDuration: CFTimeInterval = 1.0 428 | static let indeterminateProgress: CGFloat = 0.3 429 | } 430 | 431 | struct AnimationKeys { 432 | static let indeterminate = "indeterminateAnimation" 433 | static let progress = "progress" 434 | static let transformRotation = "transform.rotation" 435 | static let completionBlock = "completionBlock" 436 | static let toValue = "toValue" 437 | } 438 | 439 | } 440 | 441 | // MARK: - Animation Delegate 442 | 443 | extension RPCircularProgress: CAAnimationDelegate { 444 | 445 | public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { 446 | let completedValue = anim.value(forKey: AnimationKeys.toValue) 447 | if let completedValue = completedValue as? CGFloat { 448 | progressLayer.progress = completedValue 449 | } 450 | 451 | if let block = anim.value(forKey: AnimationKeys.completionBlock) as? CompletionBlockObject { 452 | block.action() 453 | } 454 | } 455 | 456 | } 457 | 458 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Tests/RPCircularProgressTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RPCircularProgressTests.swift 3 | // RPCircularProgressTests 4 | // 5 | // Created by Rob Phillips on 4/6/16. 6 | // Copyright © 2016 Glazed Donut, LLC. All rights reserved. 7 | // 8 | // See LICENSE for full license agreement. 9 | // 10 | 11 | import Quick 12 | import Nimble 13 | 14 | @testable import RPCircularProgressExample 15 | 16 | class RPCircularProgressTests: QuickSpec { 17 | 18 | override func spec() { 19 | 20 | let progress = RPCircularProgress() 21 | 22 | describe("properties") { 23 | 24 | context("colors") { 25 | 26 | // Non-default color 27 | let color = UIColor.purple 28 | 29 | it("should update track color") { 30 | progress.trackTintColor = color 31 | expect(progress.trackTintColor) == color 32 | } 33 | 34 | it("should update progress color") { 35 | progress.progressTintColor = color 36 | expect(progress.progressTintColor) == color 37 | } 38 | 39 | it("should have an optional inner area color") { 40 | expect(progress.innerTintColor).to(beNil()) 41 | } 42 | 43 | it("should update inner area color") { 44 | progress.innerTintColor = color 45 | expect(progress.innerTintColor) == color 46 | } 47 | 48 | } 49 | 50 | context("corners") { 51 | 52 | it("should update rounded") { 53 | progress.roundedCorners = false 54 | expect(progress.roundedCorners).to(beFalse()) 55 | 56 | progress.roundedCorners = true 57 | expect(progress.roundedCorners).to(beTrue()) 58 | } 59 | 60 | } 61 | 62 | context("progress bar") { 63 | 64 | it("should update thickness ratio") { 65 | progress.thicknessRatio = 0.42 66 | expect(progress.thicknessRatio) == 0.42 67 | } 68 | 69 | it("should pin thickness ratio") { 70 | progress.thicknessRatio = -1 71 | expect(progress.thicknessRatio) == 0.01 72 | 73 | progress.thicknessRatio = 2 74 | expect(progress.thicknessRatio) == 1 75 | } 76 | 77 | it("should update clockwise progress") { 78 | progress.clockwiseProgress = false 79 | expect(progress.clockwiseProgress).to(beFalse()) 80 | 81 | progress.clockwiseProgress = true 82 | expect(progress.clockwiseProgress).to(beTrue()) 83 | } 84 | 85 | it("should have the correct timing function") { 86 | let timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) 87 | progress.timingFunction = timingFunction 88 | progress.updateProgress(1, duration: 2) 89 | 90 | let animation = progress.layer.animation(forKey: "progress") 91 | expect(animation).toEventuallyNot(beNil()) 92 | 93 | let currentTimingFunction = animation!.value(forKey: "timingFunction") as! CAMediaTimingFunction 94 | expect(currentTimingFunction).to(equal(timingFunction)) 95 | } 96 | 97 | it("should return current progress") { 98 | progress.updateProgress(0.42, animated: false) 99 | expect(progress.progress) == 0.42 100 | } 101 | 102 | } 103 | 104 | context("indeterminate progress bar") { 105 | 106 | it("should update indeterminate progress") { 107 | progress.indeterminateProgress = 0.42 108 | expect(progress.indeterminateProgress) == 0.42 109 | } 110 | 111 | it("should pin indeterminate progress") { 112 | progress.indeterminateProgress = -1 113 | expect(progress.indeterminateProgress) == 0.05 114 | 115 | progress.indeterminateProgress = 2 116 | expect(progress.indeterminateProgress) == 0.9 117 | } 118 | 119 | it("should update indeterminate duration") { 120 | progress.indeterminateDuration = 42 121 | expect(progress.indeterminateDuration) == 42 122 | } 123 | 124 | } 125 | 126 | } 127 | 128 | describe("indeterminate progress") { 129 | 130 | context("enabling or disabling") { 131 | 132 | let key = "indeterminateAnimation" 133 | 134 | it("should enable the animation") { 135 | progress.enableIndeterminate() 136 | 137 | let animation = progress.layer.animation(forKey: key) 138 | expect(animation).toEventuallyNot(beNil()) 139 | } 140 | 141 | it("should disable the animation") { 142 | progress.enableIndeterminate() 143 | var animation = progress.layer.animation(forKey: key) 144 | expect(animation).toEventuallyNot(beNil()) 145 | 146 | progress.enableIndeterminate(false) 147 | animation = progress.layer.animation(forKey: key) 148 | expect(animation).to(beNil()) 149 | } 150 | 151 | } 152 | 153 | context("completion") { 154 | 155 | it("should call the enabling closure upon completion") { 156 | waitUntil { done in 157 | progress.enableIndeterminate(completion: { 158 | expect(true).to(beTrue()) 159 | done() 160 | }) 161 | progress.enableIndeterminate(false) 162 | } 163 | } 164 | 165 | it("should call the disabling closure upon completion") { 166 | progress.enableIndeterminate() 167 | waitUntil { done in 168 | progress.enableIndeterminate(false, completion: { 169 | expect(true).to(beTrue()) 170 | done() 171 | }) 172 | } 173 | } 174 | 175 | } 176 | 177 | } 178 | 179 | describe("updating progress") { 180 | 181 | let key = "progress" 182 | 183 | context("progress") { 184 | 185 | it("should pin progress") { 186 | progress.updateProgress(-1, animated: false) 187 | expect(progress.progress) == 0 188 | 189 | progress.updateProgress(2, animated: false) 190 | expect(progress.progress) == 1 191 | } 192 | 193 | it("should update to the proper amount") { 194 | progress.updateProgress(0.42, animated: false) 195 | expect(progress.progress).to(beCloseTo(0.42)) 196 | } 197 | 198 | } 199 | 200 | context("animated") { 201 | 202 | it("should add animation if passed as true") { 203 | progress.updateProgress(0.42, animated: true) 204 | 205 | let animation = progress.layer.animation(forKey: key) 206 | 207 | expect(animation).toEventuallyNot(beNil()) 208 | expect(progress.progress).toEventually(equal(0.42)) 209 | } 210 | 211 | it("should not add animation if passed as false") { 212 | progress.updateProgress(0.42, animated: false) 213 | 214 | let animation = progress.layer.animation(forKey: key) 215 | 216 | expect(animation).to(beNil()) 217 | expect(progress.progress) == 0.42 218 | } 219 | 220 | } 221 | 222 | context("initialDelay") { 223 | 224 | it("should set the proper value") { 225 | let beginTime = NSNumber(floatLiteral: CACurrentMediaTime() + 42) 226 | progress.updateProgress(0.42, initialDelay: 42) 227 | 228 | let animation = progress.layer.animation(forKey: key) 229 | expect(animation).toEventuallyNot(beNil()) 230 | 231 | let initialDelay = animation?.value(forKey: "beginTime") as? NSNumber 232 | expect(initialDelay?.intValue) == beginTime.intValue 233 | } 234 | 235 | } 236 | 237 | context("duration") { 238 | 239 | it("should set the proper value") { 240 | progress.updateProgress(0.42, duration: 42) 241 | 242 | let animation = progress.layer.animation(forKey: key) 243 | expect(animation).toEventuallyNot(beNil()) 244 | 245 | let duration = animation?.value(forKey: "duration") as? Int 246 | expect(duration) == 42 247 | } 248 | 249 | } 250 | 251 | context("completion") { 252 | 253 | it("should call the closure upon completion") { 254 | waitUntil { done in 255 | progress.updateProgress(0.42, duration: 42, completion: { 256 | expect(true).to(beTrue()) 257 | done() 258 | }) 259 | } 260 | } 261 | 262 | } 263 | 264 | } 265 | 266 | } 267 | 268 | } 269 | -------------------------------------------------------------------------------- /ohhai.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwasrobbed/RPCircularProgress/f607a1d6dd5f45006cc08b84eebae69903023814/ohhai.gif --------------------------------------------------------------------------------