├── .gitignore ├── LICENSE ├── Package.swift ├── README.md ├── SpringIndicator.podspec ├── SpringIndicator.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── SpringIndicator.xcscheme ├── SpringIndicator ├── AnimationProcess.swift ├── CALayer+Animation.swift ├── CAPropertyAnimation+Key.swift ├── Double+Math.swift ├── Info.plist ├── RefreshIndicator.swift ├── SpringIndicator.h └── SpringIndicator.swift ├── SpringIndicatorExample ├── SpringIndicatorExample.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── SpringIndicatorExample │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ ├── TableViewController.swift │ ├── ViewController.swift │ └── WebViewController.swift └── SpringIndicatorExampleTests │ ├── Info.plist │ └── SpringIndicatorExampleTests.swift └── SpringIndicatorTests ├── Info.plist └── SpringIndicatorTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # mac 2 | .DS_Store 3 | 4 | # Xcode 5 | # 6 | build/ 7 | *.pbxuser 8 | !default.pbxuser 9 | *.mode1v3 10 | !default.mode1v3 11 | *.mode2v3 12 | !default.mode2v3 13 | *.perspectivev3 14 | !default.perspectivev3 15 | xcuserdata 16 | *.xccheckout 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | *.xcuserstate 22 | 23 | # CocoaPods 24 | # 25 | # We recommend against adding the Pods directory to your .gitignore. However 26 | # you should judge for yourself, the pros and cons are mentioned at: 27 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 28 | # 29 | # Pods/ 30 | 31 | # SPM 32 | .swiftpm 33 | .build/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Kyohei Ito 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "SpringIndicator", 8 | platforms: [ .iOS("9"), .tvOS("9") ], 9 | products: [ 10 | // Products define the executables and libraries a package produces, and make them visible to other packages. 11 | .library( 12 | name: "SpringIndicator", 13 | targets: ["SpringIndicator"] 14 | ), 15 | ], 16 | dependencies: [ 17 | // Dependencies declare other packages that this package depends on. 18 | // .package(url: /* package url */, from: "1.0.0"), 19 | ], 20 | targets: [ 21 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 22 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 23 | .target( 24 | name: "SpringIndicator", 25 | dependencies: [], 26 | path: "SpringIndicator", 27 | exclude: [ 28 | "Info.plist" 29 | ] 30 | ) 31 | ] 32 | ) 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SpringIndicator 2 | 3 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 4 | [![Version](https://img.shields.io/cocoapods/v/SpringIndicator.svg?style=flat)](http://cocoadocs.org/docsets/SpringIndicator) 5 | [![License](https://img.shields.io/cocoapods/l/SpringIndicator.svg?style=flat)](http://cocoadocs.org/docsets/SpringIndicator) 6 | [![Platform](https://img.shields.io/cocoapods/p/SpringIndicator.svg?style=flat)](http://cocoadocs.org/docsets/SpringIndicator) 7 | 8 | #### [Appetize's Demo](https://appetize.io/app/taw1k1486yhxqy35gv7jrver7g) 9 | 10 | * Refresher is a simple as UIRefreshControl. 11 | * Don't need to add a UIScrollView delegate. 12 | 13 | ![Indicator](https://user-images.githubusercontent.com/5707132/55774385-e4828580-5acf-11e9-8a4e-075e52660566.gif) 14 | ![Refresher](https://user-images.githubusercontent.com/5707132/55774407-f49a6500-5acf-11e9-9e36-9248e89d7ff1.gif) 15 | 16 | ![Image](https://user-images.githubusercontent.com/5707132/55774429-0a0f8f00-5ad0-11e9-91d6-7ab0ae36e409.png) 17 | 18 | 19 | ## Requirements 20 | 21 | - Swift 5 22 | - iOS 8.0 or later 23 | - tvOS 9.0 or later 24 | 25 | ## How to Install SpringIndicator 26 | 27 | #### Cocoapods 28 | 29 | Add the following to your `Podfile`: 30 | 31 | ```Ruby 32 | pod "SpringIndicator" 33 | ``` 34 | 35 | #### Carthage 36 | 37 | Add the following to your `Cartfile`: 38 | 39 | ```Ruby 40 | github "KyoheiG3/SpringIndicator" 41 | ``` 42 | 43 | ### Swift Package Manager 44 | 45 | To install BulletinBoard using the [Swift Package Manager](https://swift.org/package-manager/), add this dependency to your Package.swift file: 46 | 47 | ```swift 48 | .package(url: "https://github.com/KyoheiG3/SpringIndicator.git", from: "5.1.0") 49 | ``` 50 | 51 | ## Usage 52 | 53 | ### Example 54 | 55 | Add Code 56 | 57 | ```swift 58 | let indicator = SpringIndicator(frame: CGRect(x: 100, y: 100, width: 60, height: 60)) 59 | view.addSubview(indicator) 60 | indicator.start() 61 | ``` 62 | 63 | RefreshIndicator 64 | 65 | ```swift 66 | let refreshControl = RefreshIndicator() 67 | refreshControl.addTarget(self, action: "onRefresh", forControlEvents: .ValueChanged) 68 | scrollView.addSubview(refreshControl) 69 | ``` 70 | 71 | Exit refresh 72 | 73 | ```swift 74 | refreshControl.endRefreshing() 75 | ``` 76 | 77 | Can use Interface Builder 78 | 79 | ![Interface Builder](https://user-images.githubusercontent.com/5707132/55774482-3d521e00-5ad0-11e9-9020-924d50bd8eb1.png) 80 | 81 | 82 | ### Variable 83 | 84 | #### Indicator 85 | 86 | ```swift 87 | @IBInspectable var animating: Bool 88 | ``` 89 | * Start the animation automatically in `drawRect`. 90 | 91 | ```swift 92 | @IBInspectable var lineWidth: CGFloat 93 | ``` 94 | * Line thickness. 95 | 96 | ```swift 97 | @IBInspectable var lineColor: UIColor 98 | ``` 99 | * Line Color. 100 | * Default is `gray`. 101 | 102 | ```swift 103 | var lineColors: [UIColor] 104 | ``` 105 | * Line Colors. 106 | * Can change some colors during rotation. 107 | * If set, `lineColor` is not used. 108 | 109 | ```swift 110 | @IBInspectable var lineCap: Bool 111 | ``` 112 | * Cap style. 113 | * Options are `round` or `square`. true is `round`. 114 | * Default is `false`. 115 | 116 | ```swift 117 | @IBInspectable var rotateDuration: Double 118 | ``` 119 | * Rotation duration. 120 | * Default is `1.5`. 121 | 122 | #### RefreshIndicator 123 | 124 | ```swift 125 | let indicator: SpringIndicator 126 | ``` 127 | * Indicator for refresh control. 128 | 129 | ```swift 130 | var isRefreshing: Bool 131 | ``` 132 | * Refreshing status. 133 | 134 | 135 | ### Function 136 | 137 | #### Indicator 138 | 139 | ```swift 140 | var isSpinning: Bool 141 | ``` 142 | * During stroke animation is `true`. 143 | 144 | ```swift 145 | func start() 146 | ``` 147 | * Start animating. 148 | 149 | ```swift 150 | func stop(with: Bool = default, completion: ((SpringIndicator) -> Swift.Void)? = default) 151 | ``` 152 | * Stop animating. 153 | * If true, waiting for stroke animation. 154 | 155 | ```swift 156 | func strokeRatio(_ ratio: CGFloat) 157 | ``` 158 | * between `0.0` and `1.0`. 159 | 160 | #### Refresher 161 | 162 | ```swift 163 | func endRefreshing() 164 | ``` 165 | * Must be explicitly called when the refreshing has completed. 166 | 167 | ## Author 168 | 169 | #### Kyohei Ito 170 | 171 | - [GitHub](https://github.com/kyoheig3) 172 | - [Twitter](https://twitter.com/kyoheig3) 173 | 174 | Follow me 🎉 175 | 176 | ## LICENSE 177 | 178 | Under the MIT license. See LICENSE file for details. 179 | -------------------------------------------------------------------------------- /SpringIndicator.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "SpringIndicator" 3 | s.version = "5.0.0" 4 | s.summary = "SpringIndicator is a indicator such as a spring and PullToRefresh." 5 | s.homepage = "https://github.com/KyoheiG3/SpringIndicator" 6 | s.license = { :type => "MIT", :file => "LICENSE" } 7 | s.author = { "Kyohei Ito" => "je.suis.kyohei@gmail.com" } 8 | s.swift_version = '5.0' 9 | s.ios.deployment_target = '8.0' 10 | s.tvos.deployment_target = '9.0' 11 | s.source = { :git => "https://github.com/KyoheiG3/SpringIndicator.git", :tag => s.version.to_s } 12 | s.source_files = "SpringIndicator/**/*.{h,swift}" 13 | s.requires_arc = true 14 | s.frameworks = "UIKit" 15 | end 16 | -------------------------------------------------------------------------------- /SpringIndicator.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C75202161F74F67E005E1197 /* Double+Math.swift in Sources */ = {isa = PBXBuildFile; fileRef = C75202151F74F67E005E1197 /* Double+Math.swift */; }; 11 | C75202181F74F77A005E1197 /* RefreshIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C75202171F74F77A005E1197 /* RefreshIndicator.swift */; }; 12 | C75E9E3B1AA8B500000304B8 /* SpringIndicator.h in Headers */ = {isa = PBXBuildFile; fileRef = C75E9E3A1AA8B500000304B8 /* SpringIndicator.h */; settings = {ATTRIBUTES = (Public, ); }; }; 13 | C75E9E411AA8B500000304B8 /* SpringIndicator.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C75E9E351AA8B500000304B8 /* SpringIndicator.framework */; }; 14 | C75E9E481AA8B500000304B8 /* SpringIndicatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C75E9E471AA8B500000304B8 /* SpringIndicatorTests.swift */; }; 15 | C75E9E521AA8B529000304B8 /* SpringIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C75E9E511AA8B529000304B8 /* SpringIndicator.swift */; }; 16 | C77771011F789C2B002D585C /* CALayer+Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77771001F789C2B002D585C /* CALayer+Animation.swift */; }; 17 | C77771031F789CA0002D585C /* CAPropertyAnimation+Key.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77771021F789CA0002D585C /* CAPropertyAnimation+Key.swift */; }; 18 | C77771051F789D82002D585C /* AnimationProcess.swift in Sources */ = {isa = PBXBuildFile; fileRef = C77771041F789D82002D585C /* AnimationProcess.swift */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXContainerItemProxy section */ 22 | C75E9E421AA8B500000304B8 /* PBXContainerItemProxy */ = { 23 | isa = PBXContainerItemProxy; 24 | containerPortal = C75E9E2C1AA8B500000304B8 /* Project object */; 25 | proxyType = 1; 26 | remoteGlobalIDString = C75E9E341AA8B500000304B8; 27 | remoteInfo = SpringIndicator; 28 | }; 29 | /* End PBXContainerItemProxy section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | C75202151F74F67E005E1197 /* Double+Math.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double+Math.swift"; sourceTree = ""; }; 33 | C75202171F74F77A005E1197 /* RefreshIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshIndicator.swift; sourceTree = ""; }; 34 | C75E9E351AA8B500000304B8 /* SpringIndicator.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SpringIndicator.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | C75E9E391AA8B500000304B8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 36 | C75E9E3A1AA8B500000304B8 /* SpringIndicator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SpringIndicator.h; sourceTree = ""; }; 37 | C75E9E401AA8B500000304B8 /* SpringIndicatorTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SpringIndicatorTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 38 | C75E9E461AA8B500000304B8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 39 | C75E9E471AA8B500000304B8 /* SpringIndicatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpringIndicatorTests.swift; sourceTree = ""; }; 40 | C75E9E511AA8B529000304B8 /* SpringIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpringIndicator.swift; sourceTree = ""; }; 41 | C77771001F789C2B002D585C /* CALayer+Animation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CALayer+Animation.swift"; sourceTree = ""; }; 42 | C77771021F789CA0002D585C /* CAPropertyAnimation+Key.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CAPropertyAnimation+Key.swift"; sourceTree = ""; }; 43 | C77771041F789D82002D585C /* AnimationProcess.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimationProcess.swift; sourceTree = ""; }; 44 | /* End PBXFileReference section */ 45 | 46 | /* Begin PBXFrameworksBuildPhase section */ 47 | C75E9E311AA8B500000304B8 /* Frameworks */ = { 48 | isa = PBXFrameworksBuildPhase; 49 | buildActionMask = 2147483647; 50 | files = ( 51 | ); 52 | runOnlyForDeploymentPostprocessing = 0; 53 | }; 54 | C75E9E3D1AA8B500000304B8 /* Frameworks */ = { 55 | isa = PBXFrameworksBuildPhase; 56 | buildActionMask = 2147483647; 57 | files = ( 58 | C75E9E411AA8B500000304B8 /* SpringIndicator.framework in Frameworks */, 59 | ); 60 | runOnlyForDeploymentPostprocessing = 0; 61 | }; 62 | /* End PBXFrameworksBuildPhase section */ 63 | 64 | /* Begin PBXGroup section */ 65 | C75E9E2B1AA8B500000304B8 = { 66 | isa = PBXGroup; 67 | children = ( 68 | C75E9E371AA8B500000304B8 /* SpringIndicator */, 69 | C75E9E441AA8B500000304B8 /* SpringIndicatorTests */, 70 | C75E9E361AA8B500000304B8 /* Products */, 71 | ); 72 | sourceTree = ""; 73 | }; 74 | C75E9E361AA8B500000304B8 /* Products */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | C75E9E351AA8B500000304B8 /* SpringIndicator.framework */, 78 | C75E9E401AA8B500000304B8 /* SpringIndicatorTests.xctest */, 79 | ); 80 | name = Products; 81 | sourceTree = ""; 82 | }; 83 | C75E9E371AA8B500000304B8 /* SpringIndicator */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | C77771041F789D82002D585C /* AnimationProcess.swift */, 87 | C77771001F789C2B002D585C /* CALayer+Animation.swift */, 88 | C77771021F789CA0002D585C /* CAPropertyAnimation+Key.swift */, 89 | C75202151F74F67E005E1197 /* Double+Math.swift */, 90 | C75202171F74F77A005E1197 /* RefreshIndicator.swift */, 91 | C75E9E3A1AA8B500000304B8 /* SpringIndicator.h */, 92 | C75E9E511AA8B529000304B8 /* SpringIndicator.swift */, 93 | C75E9E381AA8B500000304B8 /* Supporting Files */, 94 | ); 95 | path = SpringIndicator; 96 | sourceTree = ""; 97 | }; 98 | C75E9E381AA8B500000304B8 /* Supporting Files */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | C75E9E391AA8B500000304B8 /* Info.plist */, 102 | ); 103 | name = "Supporting Files"; 104 | sourceTree = ""; 105 | }; 106 | C75E9E441AA8B500000304B8 /* SpringIndicatorTests */ = { 107 | isa = PBXGroup; 108 | children = ( 109 | C75E9E471AA8B500000304B8 /* SpringIndicatorTests.swift */, 110 | C75E9E451AA8B500000304B8 /* Supporting Files */, 111 | ); 112 | path = SpringIndicatorTests; 113 | sourceTree = ""; 114 | }; 115 | C75E9E451AA8B500000304B8 /* Supporting Files */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | C75E9E461AA8B500000304B8 /* Info.plist */, 119 | ); 120 | name = "Supporting Files"; 121 | sourceTree = ""; 122 | }; 123 | /* End PBXGroup section */ 124 | 125 | /* Begin PBXHeadersBuildPhase section */ 126 | C75E9E321AA8B500000304B8 /* Headers */ = { 127 | isa = PBXHeadersBuildPhase; 128 | buildActionMask = 2147483647; 129 | files = ( 130 | C75E9E3B1AA8B500000304B8 /* SpringIndicator.h in Headers */, 131 | ); 132 | runOnlyForDeploymentPostprocessing = 0; 133 | }; 134 | /* End PBXHeadersBuildPhase section */ 135 | 136 | /* Begin PBXNativeTarget section */ 137 | C75E9E341AA8B500000304B8 /* SpringIndicator */ = { 138 | isa = PBXNativeTarget; 139 | buildConfigurationList = C75E9E4B1AA8B500000304B8 /* Build configuration list for PBXNativeTarget "SpringIndicator" */; 140 | buildPhases = ( 141 | C75E9E301AA8B500000304B8 /* Sources */, 142 | C75E9E311AA8B500000304B8 /* Frameworks */, 143 | C75E9E321AA8B500000304B8 /* Headers */, 144 | C75E9E331AA8B500000304B8 /* Resources */, 145 | ); 146 | buildRules = ( 147 | ); 148 | dependencies = ( 149 | ); 150 | name = SpringIndicator; 151 | productName = SpringIndicator; 152 | productReference = C75E9E351AA8B500000304B8 /* SpringIndicator.framework */; 153 | productType = "com.apple.product-type.framework"; 154 | }; 155 | C75E9E3F1AA8B500000304B8 /* SpringIndicatorTests */ = { 156 | isa = PBXNativeTarget; 157 | buildConfigurationList = C75E9E4E1AA8B500000304B8 /* Build configuration list for PBXNativeTarget "SpringIndicatorTests" */; 158 | buildPhases = ( 159 | C75E9E3C1AA8B500000304B8 /* Sources */, 160 | C75E9E3D1AA8B500000304B8 /* Frameworks */, 161 | C75E9E3E1AA8B500000304B8 /* Resources */, 162 | ); 163 | buildRules = ( 164 | ); 165 | dependencies = ( 166 | C75E9E431AA8B500000304B8 /* PBXTargetDependency */, 167 | ); 168 | name = SpringIndicatorTests; 169 | productName = SpringIndicatorTests; 170 | productReference = C75E9E401AA8B500000304B8 /* SpringIndicatorTests.xctest */; 171 | productType = "com.apple.product-type.bundle.unit-test"; 172 | }; 173 | /* End PBXNativeTarget section */ 174 | 175 | /* Begin PBXProject section */ 176 | C75E9E2C1AA8B500000304B8 /* Project object */ = { 177 | isa = PBXProject; 178 | attributes = { 179 | LastSwiftMigration = 0700; 180 | LastSwiftUpdateCheck = 0700; 181 | LastUpgradeCheck = 1020; 182 | ORGANIZATIONNAME = kyohei_ito; 183 | TargetAttributes = { 184 | C75E9E341AA8B500000304B8 = { 185 | CreatedOnToolsVersion = 6.2; 186 | LastSwiftMigration = 1020; 187 | }; 188 | C75E9E3F1AA8B500000304B8 = { 189 | CreatedOnToolsVersion = 6.2; 190 | LastSwiftMigration = 1020; 191 | }; 192 | }; 193 | }; 194 | buildConfigurationList = C75E9E2F1AA8B500000304B8 /* Build configuration list for PBXProject "SpringIndicator" */; 195 | compatibilityVersion = "Xcode 3.2"; 196 | developmentRegion = en; 197 | hasScannedForEncodings = 0; 198 | knownRegions = ( 199 | en, 200 | Base, 201 | ); 202 | mainGroup = C75E9E2B1AA8B500000304B8; 203 | productRefGroup = C75E9E361AA8B500000304B8 /* Products */; 204 | projectDirPath = ""; 205 | projectRoot = ""; 206 | targets = ( 207 | C75E9E341AA8B500000304B8 /* SpringIndicator */, 208 | C75E9E3F1AA8B500000304B8 /* SpringIndicatorTests */, 209 | ); 210 | }; 211 | /* End PBXProject section */ 212 | 213 | /* Begin PBXResourcesBuildPhase section */ 214 | C75E9E331AA8B500000304B8 /* Resources */ = { 215 | isa = PBXResourcesBuildPhase; 216 | buildActionMask = 2147483647; 217 | files = ( 218 | ); 219 | runOnlyForDeploymentPostprocessing = 0; 220 | }; 221 | C75E9E3E1AA8B500000304B8 /* Resources */ = { 222 | isa = PBXResourcesBuildPhase; 223 | buildActionMask = 2147483647; 224 | files = ( 225 | ); 226 | runOnlyForDeploymentPostprocessing = 0; 227 | }; 228 | /* End PBXResourcesBuildPhase section */ 229 | 230 | /* Begin PBXSourcesBuildPhase section */ 231 | C75E9E301AA8B500000304B8 /* Sources */ = { 232 | isa = PBXSourcesBuildPhase; 233 | buildActionMask = 2147483647; 234 | files = ( 235 | C77771051F789D82002D585C /* AnimationProcess.swift in Sources */, 236 | C75202161F74F67E005E1197 /* Double+Math.swift in Sources */, 237 | C77771031F789CA0002D585C /* CAPropertyAnimation+Key.swift in Sources */, 238 | C75E9E521AA8B529000304B8 /* SpringIndicator.swift in Sources */, 239 | C77771011F789C2B002D585C /* CALayer+Animation.swift in Sources */, 240 | C75202181F74F77A005E1197 /* RefreshIndicator.swift in Sources */, 241 | ); 242 | runOnlyForDeploymentPostprocessing = 0; 243 | }; 244 | C75E9E3C1AA8B500000304B8 /* Sources */ = { 245 | isa = PBXSourcesBuildPhase; 246 | buildActionMask = 2147483647; 247 | files = ( 248 | C75E9E481AA8B500000304B8 /* SpringIndicatorTests.swift in Sources */, 249 | ); 250 | runOnlyForDeploymentPostprocessing = 0; 251 | }; 252 | /* End PBXSourcesBuildPhase section */ 253 | 254 | /* Begin PBXTargetDependency section */ 255 | C75E9E431AA8B500000304B8 /* PBXTargetDependency */ = { 256 | isa = PBXTargetDependency; 257 | target = C75E9E341AA8B500000304B8 /* SpringIndicator */; 258 | targetProxy = C75E9E421AA8B500000304B8 /* PBXContainerItemProxy */; 259 | }; 260 | /* End PBXTargetDependency section */ 261 | 262 | /* Begin XCBuildConfiguration section */ 263 | C75E9E491AA8B500000304B8 /* Debug */ = { 264 | isa = XCBuildConfiguration; 265 | buildSettings = { 266 | ALWAYS_SEARCH_USER_PATHS = NO; 267 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 268 | CLANG_CXX_LIBRARY = "libc++"; 269 | CLANG_ENABLE_MODULES = YES; 270 | CLANG_ENABLE_OBJC_ARC = YES; 271 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 272 | CLANG_WARN_BOOL_CONVERSION = YES; 273 | CLANG_WARN_COMMA = YES; 274 | CLANG_WARN_CONSTANT_CONVERSION = YES; 275 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 276 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 277 | CLANG_WARN_EMPTY_BODY = YES; 278 | CLANG_WARN_ENUM_CONVERSION = YES; 279 | CLANG_WARN_INFINITE_RECURSION = YES; 280 | CLANG_WARN_INT_CONVERSION = YES; 281 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 282 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 283 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 284 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 285 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 286 | CLANG_WARN_STRICT_PROTOTYPES = YES; 287 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 288 | CLANG_WARN_UNREACHABLE_CODE = YES; 289 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 290 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 291 | COPY_PHASE_STRIP = NO; 292 | CURRENT_PROJECT_VERSION = 1; 293 | ENABLE_STRICT_OBJC_MSGSEND = YES; 294 | ENABLE_TESTABILITY = YES; 295 | GCC_C_LANGUAGE_STANDARD = gnu99; 296 | GCC_DYNAMIC_NO_PIC = NO; 297 | GCC_NO_COMMON_BLOCKS = YES; 298 | GCC_OPTIMIZATION_LEVEL = 0; 299 | GCC_PREPROCESSOR_DEFINITIONS = ( 300 | "DEBUG=1", 301 | "$(inherited)", 302 | ); 303 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 304 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 305 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 306 | GCC_WARN_UNDECLARED_SELECTOR = YES; 307 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 308 | GCC_WARN_UNUSED_FUNCTION = YES; 309 | GCC_WARN_UNUSED_VARIABLE = YES; 310 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 311 | MTL_ENABLE_DEBUG_INFO = YES; 312 | ONLY_ACTIVE_ARCH = YES; 313 | SDKROOT = iphoneos; 314 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 315 | TARGETED_DEVICE_FAMILY = "1,2"; 316 | VERSIONING_SYSTEM = "apple-generic"; 317 | VERSION_INFO_PREFIX = ""; 318 | }; 319 | name = Debug; 320 | }; 321 | C75E9E4A1AA8B500000304B8 /* Release */ = { 322 | isa = XCBuildConfiguration; 323 | buildSettings = { 324 | ALWAYS_SEARCH_USER_PATHS = NO; 325 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 326 | CLANG_CXX_LIBRARY = "libc++"; 327 | CLANG_ENABLE_MODULES = YES; 328 | CLANG_ENABLE_OBJC_ARC = YES; 329 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 330 | CLANG_WARN_BOOL_CONVERSION = YES; 331 | CLANG_WARN_COMMA = YES; 332 | CLANG_WARN_CONSTANT_CONVERSION = YES; 333 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 334 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 335 | CLANG_WARN_EMPTY_BODY = YES; 336 | CLANG_WARN_ENUM_CONVERSION = YES; 337 | CLANG_WARN_INFINITE_RECURSION = YES; 338 | CLANG_WARN_INT_CONVERSION = YES; 339 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 340 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 341 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 342 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 343 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 344 | CLANG_WARN_STRICT_PROTOTYPES = YES; 345 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 346 | CLANG_WARN_UNREACHABLE_CODE = YES; 347 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 348 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 349 | COPY_PHASE_STRIP = NO; 350 | CURRENT_PROJECT_VERSION = 1; 351 | ENABLE_NS_ASSERTIONS = NO; 352 | ENABLE_STRICT_OBJC_MSGSEND = YES; 353 | GCC_C_LANGUAGE_STANDARD = gnu99; 354 | GCC_NO_COMMON_BLOCKS = YES; 355 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 356 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 357 | GCC_WARN_UNDECLARED_SELECTOR = YES; 358 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 359 | GCC_WARN_UNUSED_FUNCTION = YES; 360 | GCC_WARN_UNUSED_VARIABLE = YES; 361 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 362 | MTL_ENABLE_DEBUG_INFO = NO; 363 | SDKROOT = iphoneos; 364 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 365 | TARGETED_DEVICE_FAMILY = "1,2"; 366 | VALIDATE_PRODUCT = YES; 367 | VERSIONING_SYSTEM = "apple-generic"; 368 | VERSION_INFO_PREFIX = ""; 369 | }; 370 | name = Release; 371 | }; 372 | C75E9E4C1AA8B500000304B8 /* Debug */ = { 373 | isa = XCBuildConfiguration; 374 | buildSettings = { 375 | CLANG_ENABLE_MODULES = YES; 376 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 377 | DEFINES_MODULE = YES; 378 | DYLIB_COMPATIBILITY_VERSION = 1; 379 | DYLIB_CURRENT_VERSION = 1; 380 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 381 | INFOPLIST_FILE = SpringIndicator/Info.plist; 382 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 383 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 384 | PRODUCT_BUNDLE_IDENTIFIER = "com.kyoheiito.$(PRODUCT_NAME:rfc1034identifier)"; 385 | PRODUCT_NAME = "$(TARGET_NAME)"; 386 | SKIP_INSTALL = YES; 387 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 388 | SWIFT_VERSION = 5.0; 389 | }; 390 | name = Debug; 391 | }; 392 | C75E9E4D1AA8B500000304B8 /* Release */ = { 393 | isa = XCBuildConfiguration; 394 | buildSettings = { 395 | CLANG_ENABLE_MODULES = YES; 396 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 397 | DEFINES_MODULE = YES; 398 | DYLIB_COMPATIBILITY_VERSION = 1; 399 | DYLIB_CURRENT_VERSION = 1; 400 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 401 | INFOPLIST_FILE = SpringIndicator/Info.plist; 402 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 403 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 404 | PRODUCT_BUNDLE_IDENTIFIER = "com.kyoheiito.$(PRODUCT_NAME:rfc1034identifier)"; 405 | PRODUCT_NAME = "$(TARGET_NAME)"; 406 | SKIP_INSTALL = YES; 407 | SWIFT_VERSION = 5.0; 408 | }; 409 | name = Release; 410 | }; 411 | C75E9E4F1AA8B500000304B8 /* Debug */ = { 412 | isa = XCBuildConfiguration; 413 | buildSettings = { 414 | DEVELOPMENT_TEAM = ""; 415 | GCC_PREPROCESSOR_DEFINITIONS = ( 416 | "DEBUG=1", 417 | "$(inherited)", 418 | ); 419 | INFOPLIST_FILE = SpringIndicatorTests/Info.plist; 420 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 421 | PRODUCT_BUNDLE_IDENTIFIER = "com.kyoheiito.$(PRODUCT_NAME:rfc1034identifier)"; 422 | PRODUCT_NAME = "$(TARGET_NAME)"; 423 | SWIFT_VERSION = 5.0; 424 | }; 425 | name = Debug; 426 | }; 427 | C75E9E501AA8B500000304B8 /* Release */ = { 428 | isa = XCBuildConfiguration; 429 | buildSettings = { 430 | DEVELOPMENT_TEAM = ""; 431 | INFOPLIST_FILE = SpringIndicatorTests/Info.plist; 432 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 433 | PRODUCT_BUNDLE_IDENTIFIER = "com.kyoheiito.$(PRODUCT_NAME:rfc1034identifier)"; 434 | PRODUCT_NAME = "$(TARGET_NAME)"; 435 | SWIFT_VERSION = 5.0; 436 | }; 437 | name = Release; 438 | }; 439 | /* End XCBuildConfiguration section */ 440 | 441 | /* Begin XCConfigurationList section */ 442 | C75E9E2F1AA8B500000304B8 /* Build configuration list for PBXProject "SpringIndicator" */ = { 443 | isa = XCConfigurationList; 444 | buildConfigurations = ( 445 | C75E9E491AA8B500000304B8 /* Debug */, 446 | C75E9E4A1AA8B500000304B8 /* Release */, 447 | ); 448 | defaultConfigurationIsVisible = 0; 449 | defaultConfigurationName = Release; 450 | }; 451 | C75E9E4B1AA8B500000304B8 /* Build configuration list for PBXNativeTarget "SpringIndicator" */ = { 452 | isa = XCConfigurationList; 453 | buildConfigurations = ( 454 | C75E9E4C1AA8B500000304B8 /* Debug */, 455 | C75E9E4D1AA8B500000304B8 /* Release */, 456 | ); 457 | defaultConfigurationIsVisible = 0; 458 | defaultConfigurationName = Release; 459 | }; 460 | C75E9E4E1AA8B500000304B8 /* Build configuration list for PBXNativeTarget "SpringIndicatorTests" */ = { 461 | isa = XCConfigurationList; 462 | buildConfigurations = ( 463 | C75E9E4F1AA8B500000304B8 /* Debug */, 464 | C75E9E501AA8B500000304B8 /* Release */, 465 | ); 466 | defaultConfigurationIsVisible = 0; 467 | defaultConfigurationName = Release; 468 | }; 469 | /* End XCConfigurationList section */ 470 | }; 471 | rootObject = C75E9E2C1AA8B500000304B8 /* Project object */; 472 | } 473 | -------------------------------------------------------------------------------- /SpringIndicator.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SpringIndicator.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SpringIndicator.xcodeproj/xcshareddata/xcschemes/SpringIndicator.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 | 79 | 85 | 86 | 87 | 88 | 89 | 90 | 96 | 97 | 103 | 104 | 105 | 106 | 108 | 109 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /SpringIndicator/AnimationProcess.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnimationProcess.swift 3 | // SpringIndicator 4 | // 5 | // Created by Kyohei Ito on 2017/09/25. 6 | // Copyright © 2017年 kyohei_ito. All rights reserved. 7 | // 8 | 9 | enum AnimationProcess { 10 | case begin, during, skip 11 | 12 | func startAngle() -> Double { 13 | switch self { 14 | case .during: return Double.pi_4 15 | case .begin, .skip: return 0 16 | } 17 | } 18 | 19 | func fromAngle() -> Double { 20 | switch self { 21 | case .during: return -(Double.pi + Double.pi_2) 22 | case .begin, .skip: return -(Double.pi * 2 + Double.pi_2) 23 | } 24 | } 25 | 26 | func toAngle() -> Double { 27 | switch self { 28 | case .during: return Double.pi 29 | case .begin, .skip: return 0 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /SpringIndicator/CALayer+Animation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CALayer+Animation.swift 3 | // SpringIndicator 4 | // 5 | // Created by Kyohei Ito on 2017/09/25. 6 | // Copyright © 2017年 kyohei_ito. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension CALayer { 12 | enum Animation: String { 13 | case rotation = "rotationAnimation" 14 | case expand = "expandAnimation" 15 | case spring = "springAnimation" 16 | case color = "colorAnimation" 17 | case scale = "scaleAnimation" 18 | } 19 | 20 | func add(_ anim: CAAnimation, for key: Animation) { 21 | add(anim, forKey: key.rawValue) 22 | } 23 | 24 | func removeAnimation(for key: Animation) { 25 | removeAnimation(forKey: key.rawValue) 26 | } 27 | 28 | func animation(for key: Animation) -> CAAnimation? { 29 | return animation(forKey: key.rawValue) 30 | } 31 | 32 | func animationExist(for key: Animation) -> Bool { 33 | return animation(forKey: key.rawValue) != nil 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /SpringIndicator/CAPropertyAnimation+Key.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CAPropertyAnimation+Key.swift 3 | // SpringIndicator 4 | // 5 | // Created by Kyohei Ito on 2017/09/25. 6 | // Copyright © 2017年 kyohei_ito. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension CAPropertyAnimation { 12 | enum Key: String { 13 | case strokeStart = "strokeStart" 14 | case strokeEnd = "strokeEnd" 15 | case strokeColor = "strokeColor" 16 | case rotationZ = "transform.rotation.z" 17 | case scale = "transform.scale" 18 | } 19 | 20 | convenience init(key: Key) { 21 | self.init(keyPath: key.rawValue) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /SpringIndicator/Double+Math.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Double+Math.swift 3 | // SpringIndicator 4 | // 5 | // Created by Kyohei Ito on 2017/09/22. 6 | // Copyright © 2017年 kyohei_ito. All rights reserved. 7 | // 8 | 9 | extension Double { 10 | static let pi_2 = pi / 2 11 | static let pi_4 = pi / 4 12 | } 13 | -------------------------------------------------------------------------------- /SpringIndicator/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 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /SpringIndicator/RefreshIndicator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RefreshIndicator.swift 3 | // SpringIndicator 4 | // 5 | // Created by Kyohei Ito on 2017/09/22. 6 | // Copyright © 2017年 kyohei_ito. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | open class RefreshIndicator: UIControl { 12 | deinit { 13 | stopIndicatorAnimation() 14 | } 15 | 16 | private let defaultContentHeight: CGFloat = 60 17 | private var refreshContext = UInt8() 18 | private var initialInsetTop: CGFloat = 0 19 | private weak var target: AnyObject? 20 | private var targetView: UIScrollView? { 21 | willSet { 22 | removeObserver() 23 | } 24 | didSet { 25 | addObserver() 26 | } 27 | } 28 | 29 | public let indicator = SpringIndicator(frame: CGRect(x: 0, y: 0, width: 20, height: 20)) 30 | public private(set) var isRefreshing: Bool = false 31 | 32 | public convenience init() { 33 | self.init(frame: CGRect.zero) 34 | } 35 | 36 | public override init(frame: CGRect) { 37 | super.init(frame: frame) 38 | 39 | setupIndicator() 40 | } 41 | 42 | public required init?(coder aDecoder: NSCoder) { 43 | super.init(coder: aDecoder) 44 | 45 | setupIndicator() 46 | } 47 | 48 | private func setupIndicator() { 49 | indicator.lineWidth = 2 50 | indicator.rotationDuration = 1 51 | indicator.center = center 52 | indicator.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleTopMargin, .flexibleBottomMargin] 53 | addSubview(indicator) 54 | } 55 | 56 | open override func layoutSubviews() { 57 | super.layoutSubviews() 58 | 59 | backgroundColor = UIColor.clear 60 | isUserInteractionEnabled = false 61 | 62 | if let scrollView = superview as? UIScrollView { 63 | autoresizingMask = [.flexibleWidth, .flexibleBottomMargin] 64 | frame.size.height = defaultContentHeight 65 | frame.size.width = scrollView.bounds.width 66 | center.x = scrollView.center.x 67 | } 68 | 69 | if let scrollView = targetView { 70 | initialInsetTop = scrollView.contentInset.top 71 | } 72 | } 73 | 74 | open override func willMove(toSuperview newSuperview: UIView!) { 75 | super.willMove(toSuperview: newSuperview) 76 | 77 | targetView = newSuperview as? UIScrollView 78 | } 79 | 80 | open override func didMoveToSuperview() { 81 | super.didMoveToSuperview() 82 | 83 | layoutIfNeeded() 84 | } 85 | 86 | open override func removeFromSuperview() { 87 | if targetView == superview { 88 | targetView = nil 89 | } 90 | super.removeFromSuperview() 91 | } 92 | 93 | open override func addTarget(_ target: Any?, action: Selector, for controlEvent: UIControl.Event) { 94 | super.addTarget(target, action: action, for: controlEvent) 95 | 96 | self.target = target as AnyObject? 97 | } 98 | 99 | open override func removeTarget(_ target: Any?, action: Selector?, for controlEvent: UIControl.Event) { 100 | super.removeTarget(target, action: action, for: controlEvent) 101 | 102 | self.target = nil 103 | } 104 | 105 | open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 106 | guard let scrollView = object as? UIScrollView, context == &refreshContext else { 107 | return super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) 108 | } 109 | 110 | if target == nil { 111 | targetView = nil 112 | return 113 | } 114 | 115 | if bounds.height <= 0 { 116 | return 117 | } 118 | 119 | if superview == scrollView { 120 | frame.origin.y = scrollOffset(scrollView) 121 | } 122 | 123 | if indicator.isSpinning { 124 | return 125 | } 126 | 127 | if isRefreshing && scrollView.isDragging == false { 128 | beginRefreshing(with: scrollView) 129 | return 130 | } 131 | 132 | let ratio = scrollRatio(scrollView) 133 | isRefreshing = ratio >= 1 134 | indicator.strokeRatio(ratio) 135 | rotationRatio(ratio) 136 | } 137 | 138 | private func addObserver() { 139 | targetView?.addObserver(self, forKeyPath: "contentOffset", options: .new, context: &refreshContext) 140 | } 141 | 142 | private func removeObserver() { 143 | targetView?.removeObserver(self, forKeyPath: "contentOffset", context: &refreshContext) 144 | } 145 | 146 | private func withoutObserve(_ block: (() -> Void)) { 147 | removeObserver() 148 | block() 149 | addObserver() 150 | } 151 | 152 | private func scrollOffset(_ scrollView: UIScrollView) -> CGFloat { 153 | var offsetY = scrollView.contentOffset.y 154 | if #available(iOS 11.0, tvOS 11.0, *) { 155 | offsetY += initialInsetTop + scrollView.safeAreaInsets.top 156 | } else { 157 | offsetY += initialInsetTop 158 | } 159 | 160 | return offsetY 161 | } 162 | 163 | private func scrollRatio(_ scrollView: UIScrollView) -> CGFloat { 164 | var offsetY = scrollOffset(scrollView) 165 | 166 | offsetY += frame.size.height - indicator.frame.size.height 167 | if offsetY > 0 { 168 | offsetY = 0 169 | } 170 | 171 | return abs(offsetY / bounds.height) 172 | } 173 | 174 | private func rotationRatio(_ ratio: CGFloat) { 175 | let value = max(min(ratio, 1), 0) 176 | 177 | CATransaction.begin() 178 | CATransaction.setDisableActions(true) 179 | indicator.indicatorView.layer.transform = CATransform3DMakeRotation(CGFloat(Double.pi - Double.pi_4) * value, 0, 0, 1) 180 | CATransaction.commit() 181 | } 182 | } 183 | 184 | // MARK: - Refresh 185 | extension RefreshIndicator { 186 | // MARK: begin 187 | private func beginRefreshing(with scrollView: UIScrollView) { 188 | sendActions(for: .valueChanged) 189 | indicator.layer.add(beginAnimation(), for: .scale) 190 | startIndicatorAnimation() 191 | 192 | let insetTop = initialInsetTop + bounds.height 193 | 194 | withoutObserve { 195 | scrollView.contentInset.top = insetTop 196 | } 197 | 198 | scrollView.contentOffset.y -= insetTop - initialInsetTop 199 | } 200 | 201 | // MARK: end 202 | /// Must be explicitly called when the refreshing has completed 203 | public func endRefreshing() { 204 | isRefreshing = false 205 | 206 | guard let scrollView = targetView else { 207 | stopIndicatorAnimation() 208 | return 209 | } 210 | 211 | let insetTop: CGFloat 212 | let safeAreaTop: CGFloat 213 | if #available(iOS 11.0, tvOS 11.0, *) { 214 | safeAreaTop = scrollView.safeAreaInsets.top 215 | } else { 216 | safeAreaTop = 0 217 | } 218 | 219 | if scrollView.superview?.superview == nil { 220 | insetTop = 0 221 | } else { 222 | insetTop = initialInsetTop + safeAreaTop 223 | } 224 | 225 | if scrollView.contentInset.top + safeAreaTop > insetTop { 226 | let cachedOffsetY = scrollView.contentOffset.y 227 | scrollView.contentInset.top = insetTop - safeAreaTop 228 | 229 | if cachedOffsetY < -insetTop { 230 | indicator.layer.add(endAnimation(), for: .scale) 231 | scrollView.contentOffset.y = cachedOffsetY 232 | 233 | UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.9, initialSpringVelocity: 0, options: .allowUserInteraction, animations: { 234 | scrollView.contentOffset.y = -insetTop 235 | }) { _ in 236 | self.stopIndicatorAnimation() 237 | } 238 | } else { 239 | CATransaction.begin() 240 | CATransaction.setCompletionBlock(stopIndicatorAnimation) 241 | indicator.layer.add(endAnimation(), for: .scale) 242 | CATransaction.commit() 243 | } 244 | } else { 245 | stopIndicatorAnimation() 246 | } 247 | } 248 | } 249 | 250 | // MARK: - Animation 251 | extension RefreshIndicator { 252 | // MARK: for Begin 253 | private func beginAnimation() -> CAPropertyAnimation { 254 | let anim = CABasicAnimation(key: .scale) 255 | anim.duration = 0.1 256 | anim.repeatCount = 1 257 | anim.autoreverses = true 258 | anim.fromValue = 1 259 | anim.toValue = 1.3 260 | anim.timingFunction = CAMediaTimingFunction(name: .easeIn) 261 | 262 | return anim 263 | } 264 | 265 | // MARK: for End 266 | private func endAnimation() -> CAPropertyAnimation { 267 | let anim = CABasicAnimation(key: .scale) 268 | anim.duration = 0.3 269 | anim.repeatCount = 1 270 | anim.fromValue = 1 271 | anim.toValue = 0 272 | anim.timingFunction = CAMediaTimingFunction(name: .easeIn) 273 | anim.fillMode = .forwards 274 | anim.isRemovedOnCompletion = false 275 | 276 | return anim 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /SpringIndicator/SpringIndicator.h: -------------------------------------------------------------------------------- 1 | // 2 | // SpringIndicator.h 3 | // SpringIndicator 4 | // 5 | // Created by Kyohei Ito on 2015/03/06. 6 | // Copyright (c) 2015年 kyohei_ito. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for SpringIndicator. 12 | FOUNDATION_EXPORT double SpringIndicatorVersionNumber; 13 | 14 | //! Project version string for SpringIndicator. 15 | FOUNDATION_EXPORT const unsigned char SpringIndicatorVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /SpringIndicator/SpringIndicator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SpringIndicator.swift 3 | // SpringIndicator 4 | // 5 | // Created by Kyohei Ito on 2015/03/06. 6 | // Copyright (c) 2015年 kyohei_ito. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @IBDesignable 12 | open class SpringIndicator: UIView { 13 | deinit { 14 | stop() 15 | } 16 | 17 | let indicatorView: UIView 18 | fileprivate var pathLayer: CAShapeLayer? { 19 | didSet { 20 | oldValue?.removeAllAnimations() 21 | oldValue?.removeFromSuperlayer() 22 | 23 | if let layer = pathLayer { 24 | indicatorView.layer.addSublayer(layer) 25 | } 26 | } 27 | } 28 | 29 | /// Start the animation automatically in drawRect. 30 | @IBInspectable open var animating: Bool = false 31 | /// Line thickness. 32 | @IBInspectable open var lineWidth: CGFloat = 3 33 | /// Line Color. Default is gray. 34 | @IBInspectable open var lineColor: UIColor = UIColor.gray 35 | /// Line Colors. If set, lineColor is not used. 36 | open var lineColors: [UIColor] = [] 37 | /// Cap style. Options are `round' and `square'. true is `round`. Default is false 38 | @IBInspectable open var lineCap: Bool = false 39 | /// Rotation duration. Default is 1.5 40 | @IBInspectable open var rotationDuration: Double = 1.5 41 | private var strokeDuration: Double { 42 | return rotationDuration / 2 43 | } 44 | 45 | /// During stroke animation is true. 46 | open var isSpinning: Bool { 47 | return pathLayer?.animationExist(for: .spring) == true 48 | } 49 | 50 | public override init(frame: CGRect) { 51 | indicatorView = UIView(frame: CGRect(origin: .zero, size: frame.size)) 52 | super.init(frame: frame) 53 | indicatorView.autoresizingMask = [.flexibleWidth, .flexibleHeight] 54 | addSubview(indicatorView) 55 | 56 | backgroundColor = UIColor.clear 57 | } 58 | 59 | public required init?(coder aDecoder: NSCoder) { 60 | indicatorView = UIView() 61 | super.init(coder: aDecoder) 62 | indicatorView.frame = bounds 63 | indicatorView.autoresizingMask = [.flexibleWidth, .flexibleHeight] 64 | addSubview(indicatorView) 65 | 66 | backgroundColor = UIColor.clear 67 | } 68 | 69 | open override func draw(_ rect: CGRect) { 70 | super.draw(rect) 71 | 72 | if animating { 73 | start(for: .begin) 74 | } 75 | } 76 | 77 | private func makeRotationPath(for process: AnimationProcess) -> UIBezierPath { 78 | let start = CGFloat(process.startAngle()) 79 | let end = CGFloat(Double.pi + Double.pi_2) + start 80 | let center = CGPoint(x: bounds.width / 2, y: bounds.height / 2) 81 | let radius = max(bounds.width, bounds.height) / 2 82 | 83 | let arc = UIBezierPath(arcCenter: center, radius: radius, startAngle: start, endAngle: end, clockwise: true) 84 | arc.lineWidth = 0 85 | 86 | return arc 87 | } 88 | 89 | private func makeRotationLayer() -> CAShapeLayer { 90 | let shapeLayer = CAShapeLayer() 91 | shapeLayer.strokeColor = (lineColors.first ?? lineColor).cgColor 92 | shapeLayer.fillColor = nil 93 | shapeLayer.lineWidth = lineWidth 94 | shapeLayer.lineCap = lineCap ? .round : .square 95 | 96 | return shapeLayer 97 | } 98 | 99 | public func start() { 100 | start(for: .begin) 101 | } 102 | 103 | fileprivate func start(for process: AnimationProcess) { 104 | if isSpinning { 105 | return 106 | } 107 | 108 | indicatorView.layer.add(rotationAnimation(for: process), for: .rotation) 109 | strokeTransaction(process) 110 | } 111 | 112 | /// If true, waiting for stroke animation. 113 | public func stop(with waitingForAnimation: Bool = false, completion: ((SpringIndicator) -> Void)? = nil) { 114 | if waitingForAnimation, let layer = pathLayer?.presentation() { 115 | let time = Double(2 - layer.strokeStart - layer.strokeEnd) 116 | DispatchQueue.main.asyncAfter(deadline: .now() + time) { 117 | self.stop(completion: completion) 118 | } 119 | } else { 120 | indicatorView.layer.removeAllAnimations() 121 | pathLayer?.strokeEnd = 0 122 | pathLayer = nil 123 | 124 | completion?(self) 125 | } 126 | } 127 | 128 | private func strokeTransaction(_ process: AnimationProcess) { 129 | if pathLayer == nil { 130 | pathLayer = makeRotationLayer() 131 | } 132 | 133 | pathLayer?.path = makeRotationPath(for: process).cgPath 134 | 135 | CATransaction.begin() 136 | if process == .during { 137 | CATransaction.setCompletionBlock() { 138 | self.pathLayer?.removeAllAnimations() 139 | self.start(for: .skip) 140 | } 141 | } else { 142 | if lineColors.count > 1 { 143 | pathLayer?.add(colorAnimation(for: process), for: .color) 144 | } 145 | } 146 | 147 | pathLayer?.add(nextAnimation(for: process), for: .spring) 148 | CATransaction.commit() 149 | } 150 | 151 | private func nextAnimation(for process: AnimationProcess) -> CAAnimation { 152 | return process == .during ? strokeAnimation(key: .strokeStart) : springAnimation() 153 | } 154 | 155 | } 156 | 157 | // MARK: - Animation 158 | extension SpringIndicator { 159 | // MARK: for Rotation 160 | private func rotationAnimation(for process: AnimationProcess) -> CAPropertyAnimation { 161 | let animation = CABasicAnimation(key: .rotationZ) 162 | animation.duration = rotationDuration 163 | animation.repeatCount = HUGE 164 | animation.fromValue = process.fromAngle() 165 | animation.toValue = process.toAngle() 166 | animation.fillMode = .forwards 167 | animation.isRemovedOnCompletion = false 168 | 169 | return animation 170 | } 171 | 172 | // MARK: for Spring 173 | private func springAnimation() -> CAAnimationGroup { 174 | let expand = strokeAnimation(key: .strokeEnd) 175 | expand.beginTime = 0 176 | 177 | let contract = strokeAnimation(key: .strokeStart) 178 | contract.beginTime = expand.duration 179 | 180 | let animation = CAAnimationGroup() 181 | animation.animations = [expand, contract] 182 | animation.duration = expand.duration + contract.duration 183 | animation.repeatCount = HUGE 184 | animation.fillMode = .forwards 185 | animation.isRemovedOnCompletion = false 186 | 187 | return animation 188 | } 189 | 190 | private func strokeAnimation(key: CAPropertyAnimation.Key) -> CAPropertyAnimation { 191 | let animation = CAKeyframeAnimation(key: key) 192 | animation.duration = strokeDuration 193 | animation.keyTimes = [0, 0.3, 0.5, 0.7, 1] 194 | animation.values = [0, 0.1, 0.5, 0.9, 1] 195 | animation.fillMode = .forwards 196 | animation.isRemovedOnCompletion = false 197 | 198 | return animation 199 | } 200 | 201 | // MARK: for Color 202 | private func colorAnimation(for process: AnimationProcess) -> CAPropertyAnimation { 203 | let animation = CAKeyframeAnimation(key: .strokeColor) 204 | animation.duration = rotationDuration * CFTimeInterval(lineColors.count) 205 | animation.repeatCount = HUGE 206 | animation.keyTimes = colorAnimationKeyTimes() 207 | animation.values = colorAnimationValues(for: process) 208 | animation.fillMode = .forwards 209 | animation.isRemovedOnCompletion = false 210 | return animation 211 | } 212 | 213 | private func colorAnimationKeyTimes() -> [NSNumber] { 214 | let c = Float(lineColors.count) 215 | return stride(from: 1, through: c, by: 1).reduce([]) { (r: [NSNumber], f: Float) in 216 | r + [NSNumber(value: f/c-1/c), NSNumber(value: f/c)] 217 | } 218 | } 219 | 220 | private func colorAnimationValues(for process: AnimationProcess) -> [CGColor] { 221 | var colors = ArraySlice(lineColors) 222 | var first: UIColor? 223 | 224 | if process == .skip { 225 | first = colors.first 226 | colors = colors.dropFirst() 227 | } 228 | 229 | var cgColors = colors.reduce([]) { (r: [CGColor], c: UIColor) in 230 | r + [c.cgColor, c.cgColor] 231 | } 232 | 233 | if let first = first { 234 | cgColors.append(contentsOf: [first.cgColor, first.cgColor]) 235 | } 236 | 237 | return cgColors 238 | } 239 | } 240 | 241 | // MARK: - Stroke 242 | extension SpringIndicator { 243 | /// between 0.0 and 1.0. 244 | public func strokeRatio(_ ratio: CGFloat) { 245 | if ratio <= 0 { 246 | pathLayer = nil 247 | } else if ratio >= 1 { 248 | strokeValue(1) 249 | } else { 250 | strokeValue(ratio) 251 | } 252 | } 253 | 254 | private func strokeValue(_ value: CGFloat) { 255 | if pathLayer == nil { 256 | pathLayer = makeRotationLayer() 257 | pathLayer?.path = makeRotationPath(for: .begin).cgPath 258 | } 259 | 260 | CATransaction.begin() 261 | CATransaction.setDisableActions(true) 262 | pathLayer?.strokeStart = 0 263 | pathLayer?.strokeEnd = value 264 | CATransaction.commit() 265 | } 266 | } 267 | 268 | // MARK: - RefreshIndicator extension 269 | extension RefreshIndicator { 270 | func startIndicatorAnimation() { 271 | indicator.start(for: .during) 272 | } 273 | 274 | func stopIndicatorAnimation() { 275 | indicator.stop() { 276 | $0.layer.removeAnimation(for: .scale) 277 | } 278 | } 279 | } 280 | 281 | -------------------------------------------------------------------------------- /SpringIndicatorExample/SpringIndicatorExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C70EBE5D1B09A6AB00D95973 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C70EBE5C1B09A6AB00D95973 /* TableViewController.swift */; }; 11 | C724B8BC1AA8CC31006F0F00 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C724B8BB1AA8CC31006F0F00 /* AppDelegate.swift */; }; 12 | C724B8BE1AA8CC31006F0F00 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C724B8BD1AA8CC31006F0F00 /* ViewController.swift */; }; 13 | C724B8C11AA8CC31006F0F00 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C724B8BF1AA8CC31006F0F00 /* Main.storyboard */; }; 14 | C724B8C31AA8CC31006F0F00 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C724B8C21AA8CC31006F0F00 /* Images.xcassets */; }; 15 | C724B8C61AA8CC31006F0F00 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = C724B8C41AA8CC31006F0F00 /* LaunchScreen.xib */; }; 16 | C724B8D21AA8CC31006F0F00 /* SpringIndicatorExampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C724B8D11AA8CC31006F0F00 /* SpringIndicatorExampleTests.swift */; }; 17 | C724B8E41AA8CDEB006F0F00 /* SpringIndicator.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C724B8E11AA8CD7C006F0F00 /* SpringIndicator.framework */; }; 18 | C724B8E51AA8CDEB006F0F00 /* SpringIndicator.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C724B8E11AA8CD7C006F0F00 /* SpringIndicator.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 19 | C727AAF31AE42E9200E132FB /* WebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C727AAF21AE42E9200E132FB /* WebViewController.swift */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXContainerItemProxy section */ 23 | C724B8CC1AA8CC31006F0F00 /* PBXContainerItemProxy */ = { 24 | isa = PBXContainerItemProxy; 25 | containerPortal = C724B8AE1AA8CC31006F0F00 /* Project object */; 26 | proxyType = 1; 27 | remoteGlobalIDString = C724B8B51AA8CC31006F0F00; 28 | remoteInfo = SpringIndicatorExample; 29 | }; 30 | C724B8E01AA8CD7C006F0F00 /* PBXContainerItemProxy */ = { 31 | isa = PBXContainerItemProxy; 32 | containerPortal = C724B8DB1AA8CD7C006F0F00 /* SpringIndicator.xcodeproj */; 33 | proxyType = 2; 34 | remoteGlobalIDString = C75E9E351AA8B500000304B8; 35 | remoteInfo = SpringIndicator; 36 | }; 37 | C724B8E21AA8CD7C006F0F00 /* PBXContainerItemProxy */ = { 38 | isa = PBXContainerItemProxy; 39 | containerPortal = C724B8DB1AA8CD7C006F0F00 /* SpringIndicator.xcodeproj */; 40 | proxyType = 2; 41 | remoteGlobalIDString = C75E9E401AA8B500000304B8; 42 | remoteInfo = SpringIndicatorTests; 43 | }; 44 | C724B8E61AA8CDEB006F0F00 /* PBXContainerItemProxy */ = { 45 | isa = PBXContainerItemProxy; 46 | containerPortal = C724B8DB1AA8CD7C006F0F00 /* SpringIndicator.xcodeproj */; 47 | proxyType = 1; 48 | remoteGlobalIDString = C75E9E341AA8B500000304B8; 49 | remoteInfo = SpringIndicator; 50 | }; 51 | /* End PBXContainerItemProxy section */ 52 | 53 | /* Begin PBXCopyFilesBuildPhase section */ 54 | C724B8E81AA8CDEB006F0F00 /* Embed Frameworks */ = { 55 | isa = PBXCopyFilesBuildPhase; 56 | buildActionMask = 2147483647; 57 | dstPath = ""; 58 | dstSubfolderSpec = 10; 59 | files = ( 60 | C724B8E51AA8CDEB006F0F00 /* SpringIndicator.framework in Embed Frameworks */, 61 | ); 62 | name = "Embed Frameworks"; 63 | runOnlyForDeploymentPostprocessing = 0; 64 | }; 65 | /* End PBXCopyFilesBuildPhase section */ 66 | 67 | /* Begin PBXFileReference section */ 68 | C70EBE5C1B09A6AB00D95973 /* TableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = ""; }; 69 | C724B8B61AA8CC31006F0F00 /* SpringIndicatorExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SpringIndicatorExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 70 | C724B8BA1AA8CC31006F0F00 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 71 | C724B8BB1AA8CC31006F0F00 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 72 | C724B8BD1AA8CC31006F0F00 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 73 | C724B8C01AA8CC31006F0F00 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 74 | C724B8C21AA8CC31006F0F00 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 75 | C724B8C51AA8CC31006F0F00 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 76 | C724B8CB1AA8CC31006F0F00 /* SpringIndicatorExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SpringIndicatorExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 77 | C724B8D01AA8CC31006F0F00 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 78 | C724B8D11AA8CC31006F0F00 /* SpringIndicatorExampleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpringIndicatorExampleTests.swift; sourceTree = ""; }; 79 | C724B8DB1AA8CD7C006F0F00 /* SpringIndicator.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SpringIndicator.xcodeproj; path = ../SpringIndicator.xcodeproj; sourceTree = ""; }; 80 | C727AAF21AE42E9200E132FB /* WebViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebViewController.swift; sourceTree = ""; }; 81 | /* End PBXFileReference section */ 82 | 83 | /* Begin PBXFrameworksBuildPhase section */ 84 | C724B8B31AA8CC31006F0F00 /* Frameworks */ = { 85 | isa = PBXFrameworksBuildPhase; 86 | buildActionMask = 2147483647; 87 | files = ( 88 | C724B8E41AA8CDEB006F0F00 /* SpringIndicator.framework in Frameworks */, 89 | ); 90 | runOnlyForDeploymentPostprocessing = 0; 91 | }; 92 | C724B8C81AA8CC31006F0F00 /* Frameworks */ = { 93 | isa = PBXFrameworksBuildPhase; 94 | buildActionMask = 2147483647; 95 | files = ( 96 | ); 97 | runOnlyForDeploymentPostprocessing = 0; 98 | }; 99 | /* End PBXFrameworksBuildPhase section */ 100 | 101 | /* Begin PBXGroup section */ 102 | C724B8AD1AA8CC31006F0F00 = { 103 | isa = PBXGroup; 104 | children = ( 105 | C724B8DB1AA8CD7C006F0F00 /* SpringIndicator.xcodeproj */, 106 | C724B8B81AA8CC31006F0F00 /* SpringIndicatorExample */, 107 | C724B8CE1AA8CC31006F0F00 /* SpringIndicatorExampleTests */, 108 | C724B8B71AA8CC31006F0F00 /* Products */, 109 | ); 110 | sourceTree = ""; 111 | }; 112 | C724B8B71AA8CC31006F0F00 /* Products */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | C724B8B61AA8CC31006F0F00 /* SpringIndicatorExample.app */, 116 | C724B8CB1AA8CC31006F0F00 /* SpringIndicatorExampleTests.xctest */, 117 | ); 118 | name = Products; 119 | sourceTree = ""; 120 | }; 121 | C724B8B81AA8CC31006F0F00 /* SpringIndicatorExample */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | C724B8BB1AA8CC31006F0F00 /* AppDelegate.swift */, 125 | C724B8BD1AA8CC31006F0F00 /* ViewController.swift */, 126 | C724B8BF1AA8CC31006F0F00 /* Main.storyboard */, 127 | C724B8C21AA8CC31006F0F00 /* Images.xcassets */, 128 | C724B8C41AA8CC31006F0F00 /* LaunchScreen.xib */, 129 | C724B8B91AA8CC31006F0F00 /* Supporting Files */, 130 | C70EBE5C1B09A6AB00D95973 /* TableViewController.swift */, 131 | C727AAF21AE42E9200E132FB /* WebViewController.swift */, 132 | ); 133 | path = SpringIndicatorExample; 134 | sourceTree = ""; 135 | }; 136 | C724B8B91AA8CC31006F0F00 /* Supporting Files */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | C724B8BA1AA8CC31006F0F00 /* Info.plist */, 140 | ); 141 | name = "Supporting Files"; 142 | sourceTree = ""; 143 | }; 144 | C724B8CE1AA8CC31006F0F00 /* SpringIndicatorExampleTests */ = { 145 | isa = PBXGroup; 146 | children = ( 147 | C724B8D11AA8CC31006F0F00 /* SpringIndicatorExampleTests.swift */, 148 | C724B8CF1AA8CC31006F0F00 /* Supporting Files */, 149 | ); 150 | path = SpringIndicatorExampleTests; 151 | sourceTree = ""; 152 | }; 153 | C724B8CF1AA8CC31006F0F00 /* Supporting Files */ = { 154 | isa = PBXGroup; 155 | children = ( 156 | C724B8D01AA8CC31006F0F00 /* Info.plist */, 157 | ); 158 | name = "Supporting Files"; 159 | sourceTree = ""; 160 | }; 161 | C724B8DC1AA8CD7C006F0F00 /* Products */ = { 162 | isa = PBXGroup; 163 | children = ( 164 | C724B8E11AA8CD7C006F0F00 /* SpringIndicator.framework */, 165 | C724B8E31AA8CD7C006F0F00 /* SpringIndicatorTests.xctest */, 166 | ); 167 | name = Products; 168 | sourceTree = ""; 169 | }; 170 | /* End PBXGroup section */ 171 | 172 | /* Begin PBXNativeTarget section */ 173 | C724B8B51AA8CC31006F0F00 /* SpringIndicatorExample */ = { 174 | isa = PBXNativeTarget; 175 | buildConfigurationList = C724B8D51AA8CC31006F0F00 /* Build configuration list for PBXNativeTarget "SpringIndicatorExample" */; 176 | buildPhases = ( 177 | C724B8B21AA8CC31006F0F00 /* Sources */, 178 | C724B8B31AA8CC31006F0F00 /* Frameworks */, 179 | C724B8B41AA8CC31006F0F00 /* Resources */, 180 | C724B8E81AA8CDEB006F0F00 /* Embed Frameworks */, 181 | ); 182 | buildRules = ( 183 | ); 184 | dependencies = ( 185 | C724B8E71AA8CDEB006F0F00 /* PBXTargetDependency */, 186 | ); 187 | name = SpringIndicatorExample; 188 | productName = SpringIndicatorExample; 189 | productReference = C724B8B61AA8CC31006F0F00 /* SpringIndicatorExample.app */; 190 | productType = "com.apple.product-type.application"; 191 | }; 192 | C724B8CA1AA8CC31006F0F00 /* SpringIndicatorExampleTests */ = { 193 | isa = PBXNativeTarget; 194 | buildConfigurationList = C724B8D81AA8CC31006F0F00 /* Build configuration list for PBXNativeTarget "SpringIndicatorExampleTests" */; 195 | buildPhases = ( 196 | C724B8C71AA8CC31006F0F00 /* Sources */, 197 | C724B8C81AA8CC31006F0F00 /* Frameworks */, 198 | C724B8C91AA8CC31006F0F00 /* Resources */, 199 | ); 200 | buildRules = ( 201 | ); 202 | dependencies = ( 203 | C724B8CD1AA8CC31006F0F00 /* PBXTargetDependency */, 204 | ); 205 | name = SpringIndicatorExampleTests; 206 | productName = SpringIndicatorExampleTests; 207 | productReference = C724B8CB1AA8CC31006F0F00 /* SpringIndicatorExampleTests.xctest */; 208 | productType = "com.apple.product-type.bundle.unit-test"; 209 | }; 210 | /* End PBXNativeTarget section */ 211 | 212 | /* Begin PBXProject section */ 213 | C724B8AE1AA8CC31006F0F00 /* Project object */ = { 214 | isa = PBXProject; 215 | attributes = { 216 | LastSwiftMigration = 0700; 217 | LastSwiftUpdateCheck = 0700; 218 | LastUpgradeCheck = 1020; 219 | ORGANIZATIONNAME = kyohei_ito; 220 | TargetAttributes = { 221 | C724B8B51AA8CC31006F0F00 = { 222 | CreatedOnToolsVersion = 6.2; 223 | LastSwiftMigration = 1020; 224 | }; 225 | C724B8CA1AA8CC31006F0F00 = { 226 | CreatedOnToolsVersion = 6.2; 227 | LastSwiftMigration = 1020; 228 | TestTargetID = C724B8B51AA8CC31006F0F00; 229 | }; 230 | }; 231 | }; 232 | buildConfigurationList = C724B8B11AA8CC31006F0F00 /* Build configuration list for PBXProject "SpringIndicatorExample" */; 233 | compatibilityVersion = "Xcode 3.2"; 234 | developmentRegion = en; 235 | hasScannedForEncodings = 0; 236 | knownRegions = ( 237 | en, 238 | Base, 239 | ); 240 | mainGroup = C724B8AD1AA8CC31006F0F00; 241 | productRefGroup = C724B8B71AA8CC31006F0F00 /* Products */; 242 | projectDirPath = ""; 243 | projectReferences = ( 244 | { 245 | ProductGroup = C724B8DC1AA8CD7C006F0F00 /* Products */; 246 | ProjectRef = C724B8DB1AA8CD7C006F0F00 /* SpringIndicator.xcodeproj */; 247 | }, 248 | ); 249 | projectRoot = ""; 250 | targets = ( 251 | C724B8B51AA8CC31006F0F00 /* SpringIndicatorExample */, 252 | C724B8CA1AA8CC31006F0F00 /* SpringIndicatorExampleTests */, 253 | ); 254 | }; 255 | /* End PBXProject section */ 256 | 257 | /* Begin PBXReferenceProxy section */ 258 | C724B8E11AA8CD7C006F0F00 /* SpringIndicator.framework */ = { 259 | isa = PBXReferenceProxy; 260 | fileType = wrapper.framework; 261 | path = SpringIndicator.framework; 262 | remoteRef = C724B8E01AA8CD7C006F0F00 /* PBXContainerItemProxy */; 263 | sourceTree = BUILT_PRODUCTS_DIR; 264 | }; 265 | C724B8E31AA8CD7C006F0F00 /* SpringIndicatorTests.xctest */ = { 266 | isa = PBXReferenceProxy; 267 | fileType = wrapper.cfbundle; 268 | path = SpringIndicatorTests.xctest; 269 | remoteRef = C724B8E21AA8CD7C006F0F00 /* PBXContainerItemProxy */; 270 | sourceTree = BUILT_PRODUCTS_DIR; 271 | }; 272 | /* End PBXReferenceProxy section */ 273 | 274 | /* Begin PBXResourcesBuildPhase section */ 275 | C724B8B41AA8CC31006F0F00 /* Resources */ = { 276 | isa = PBXResourcesBuildPhase; 277 | buildActionMask = 2147483647; 278 | files = ( 279 | C724B8C11AA8CC31006F0F00 /* Main.storyboard in Resources */, 280 | C724B8C61AA8CC31006F0F00 /* LaunchScreen.xib in Resources */, 281 | C724B8C31AA8CC31006F0F00 /* Images.xcassets in Resources */, 282 | ); 283 | runOnlyForDeploymentPostprocessing = 0; 284 | }; 285 | C724B8C91AA8CC31006F0F00 /* Resources */ = { 286 | isa = PBXResourcesBuildPhase; 287 | buildActionMask = 2147483647; 288 | files = ( 289 | ); 290 | runOnlyForDeploymentPostprocessing = 0; 291 | }; 292 | /* End PBXResourcesBuildPhase section */ 293 | 294 | /* Begin PBXSourcesBuildPhase section */ 295 | C724B8B21AA8CC31006F0F00 /* Sources */ = { 296 | isa = PBXSourcesBuildPhase; 297 | buildActionMask = 2147483647; 298 | files = ( 299 | C724B8BE1AA8CC31006F0F00 /* ViewController.swift in Sources */, 300 | C727AAF31AE42E9200E132FB /* WebViewController.swift in Sources */, 301 | C724B8BC1AA8CC31006F0F00 /* AppDelegate.swift in Sources */, 302 | C70EBE5D1B09A6AB00D95973 /* TableViewController.swift in Sources */, 303 | ); 304 | runOnlyForDeploymentPostprocessing = 0; 305 | }; 306 | C724B8C71AA8CC31006F0F00 /* Sources */ = { 307 | isa = PBXSourcesBuildPhase; 308 | buildActionMask = 2147483647; 309 | files = ( 310 | C724B8D21AA8CC31006F0F00 /* SpringIndicatorExampleTests.swift in Sources */, 311 | ); 312 | runOnlyForDeploymentPostprocessing = 0; 313 | }; 314 | /* End PBXSourcesBuildPhase section */ 315 | 316 | /* Begin PBXTargetDependency section */ 317 | C724B8CD1AA8CC31006F0F00 /* PBXTargetDependency */ = { 318 | isa = PBXTargetDependency; 319 | target = C724B8B51AA8CC31006F0F00 /* SpringIndicatorExample */; 320 | targetProxy = C724B8CC1AA8CC31006F0F00 /* PBXContainerItemProxy */; 321 | }; 322 | C724B8E71AA8CDEB006F0F00 /* PBXTargetDependency */ = { 323 | isa = PBXTargetDependency; 324 | name = SpringIndicator; 325 | targetProxy = C724B8E61AA8CDEB006F0F00 /* PBXContainerItemProxy */; 326 | }; 327 | /* End PBXTargetDependency section */ 328 | 329 | /* Begin PBXVariantGroup section */ 330 | C724B8BF1AA8CC31006F0F00 /* Main.storyboard */ = { 331 | isa = PBXVariantGroup; 332 | children = ( 333 | C724B8C01AA8CC31006F0F00 /* Base */, 334 | ); 335 | name = Main.storyboard; 336 | sourceTree = ""; 337 | }; 338 | C724B8C41AA8CC31006F0F00 /* LaunchScreen.xib */ = { 339 | isa = PBXVariantGroup; 340 | children = ( 341 | C724B8C51AA8CC31006F0F00 /* Base */, 342 | ); 343 | name = LaunchScreen.xib; 344 | sourceTree = ""; 345 | }; 346 | /* End PBXVariantGroup section */ 347 | 348 | /* Begin XCBuildConfiguration section */ 349 | C724B8D31AA8CC31006F0F00 /* Debug */ = { 350 | isa = XCBuildConfiguration; 351 | buildSettings = { 352 | ALWAYS_SEARCH_USER_PATHS = NO; 353 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 354 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 355 | CLANG_CXX_LIBRARY = "libc++"; 356 | CLANG_ENABLE_MODULES = YES; 357 | CLANG_ENABLE_OBJC_ARC = YES; 358 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 359 | CLANG_WARN_BOOL_CONVERSION = YES; 360 | CLANG_WARN_COMMA = YES; 361 | CLANG_WARN_CONSTANT_CONVERSION = YES; 362 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 363 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 364 | CLANG_WARN_EMPTY_BODY = YES; 365 | CLANG_WARN_ENUM_CONVERSION = YES; 366 | CLANG_WARN_INFINITE_RECURSION = YES; 367 | CLANG_WARN_INT_CONVERSION = YES; 368 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 369 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 370 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 371 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 372 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 373 | CLANG_WARN_STRICT_PROTOTYPES = YES; 374 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 375 | CLANG_WARN_UNREACHABLE_CODE = YES; 376 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 377 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 378 | COPY_PHASE_STRIP = NO; 379 | ENABLE_STRICT_OBJC_MSGSEND = YES; 380 | ENABLE_TESTABILITY = YES; 381 | GCC_C_LANGUAGE_STANDARD = gnu99; 382 | GCC_DYNAMIC_NO_PIC = NO; 383 | GCC_NO_COMMON_BLOCKS = YES; 384 | GCC_OPTIMIZATION_LEVEL = 0; 385 | GCC_PREPROCESSOR_DEFINITIONS = ( 386 | "DEBUG=1", 387 | "$(inherited)", 388 | ); 389 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 390 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 391 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 392 | GCC_WARN_UNDECLARED_SELECTOR = YES; 393 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 394 | GCC_WARN_UNUSED_FUNCTION = YES; 395 | GCC_WARN_UNUSED_VARIABLE = YES; 396 | IPHONEOS_DEPLOYMENT_TARGET = 8.2; 397 | MTL_ENABLE_DEBUG_INFO = YES; 398 | ONLY_ACTIVE_ARCH = YES; 399 | SDKROOT = iphoneos; 400 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 401 | }; 402 | name = Debug; 403 | }; 404 | C724B8D41AA8CC31006F0F00 /* Release */ = { 405 | isa = XCBuildConfiguration; 406 | buildSettings = { 407 | ALWAYS_SEARCH_USER_PATHS = NO; 408 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 409 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 410 | CLANG_CXX_LIBRARY = "libc++"; 411 | CLANG_ENABLE_MODULES = YES; 412 | CLANG_ENABLE_OBJC_ARC = YES; 413 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 414 | CLANG_WARN_BOOL_CONVERSION = YES; 415 | CLANG_WARN_COMMA = YES; 416 | CLANG_WARN_CONSTANT_CONVERSION = YES; 417 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 418 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 419 | CLANG_WARN_EMPTY_BODY = YES; 420 | CLANG_WARN_ENUM_CONVERSION = YES; 421 | CLANG_WARN_INFINITE_RECURSION = YES; 422 | CLANG_WARN_INT_CONVERSION = YES; 423 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 424 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 425 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 426 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 427 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 428 | CLANG_WARN_STRICT_PROTOTYPES = YES; 429 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 430 | CLANG_WARN_UNREACHABLE_CODE = YES; 431 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 432 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 433 | COPY_PHASE_STRIP = NO; 434 | ENABLE_NS_ASSERTIONS = NO; 435 | ENABLE_STRICT_OBJC_MSGSEND = YES; 436 | GCC_C_LANGUAGE_STANDARD = gnu99; 437 | GCC_NO_COMMON_BLOCKS = YES; 438 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 439 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 440 | GCC_WARN_UNDECLARED_SELECTOR = YES; 441 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 442 | GCC_WARN_UNUSED_FUNCTION = YES; 443 | GCC_WARN_UNUSED_VARIABLE = YES; 444 | IPHONEOS_DEPLOYMENT_TARGET = 8.2; 445 | MTL_ENABLE_DEBUG_INFO = NO; 446 | SDKROOT = iphoneos; 447 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 448 | VALIDATE_PRODUCT = YES; 449 | }; 450 | name = Release; 451 | }; 452 | C724B8D61AA8CC31006F0F00 /* Debug */ = { 453 | isa = XCBuildConfiguration; 454 | buildSettings = { 455 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 456 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 457 | INFOPLIST_FILE = SpringIndicatorExample/Info.plist; 458 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 459 | PRODUCT_BUNDLE_IDENTIFIER = "com.kyoheiito.$(PRODUCT_NAME:rfc1034identifier)"; 460 | PRODUCT_NAME = "$(TARGET_NAME)"; 461 | SWIFT_VERSION = 5.0; 462 | }; 463 | name = Debug; 464 | }; 465 | C724B8D71AA8CC31006F0F00 /* Release */ = { 466 | isa = XCBuildConfiguration; 467 | buildSettings = { 468 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 469 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 470 | INFOPLIST_FILE = SpringIndicatorExample/Info.plist; 471 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 472 | PRODUCT_BUNDLE_IDENTIFIER = "com.kyoheiito.$(PRODUCT_NAME:rfc1034identifier)"; 473 | PRODUCT_NAME = "$(TARGET_NAME)"; 474 | SWIFT_VERSION = 5.0; 475 | }; 476 | name = Release; 477 | }; 478 | C724B8D91AA8CC31006F0F00 /* Debug */ = { 479 | isa = XCBuildConfiguration; 480 | buildSettings = { 481 | BUNDLE_LOADER = "$(TEST_HOST)"; 482 | GCC_PREPROCESSOR_DEFINITIONS = ( 483 | "DEBUG=1", 484 | "$(inherited)", 485 | ); 486 | INFOPLIST_FILE = SpringIndicatorExampleTests/Info.plist; 487 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 488 | PRODUCT_BUNDLE_IDENTIFIER = "com.kyoheiito.$(PRODUCT_NAME:rfc1034identifier)"; 489 | PRODUCT_NAME = "$(TARGET_NAME)"; 490 | SWIFT_VERSION = 5.0; 491 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SpringIndicatorExample.app/SpringIndicatorExample"; 492 | }; 493 | name = Debug; 494 | }; 495 | C724B8DA1AA8CC31006F0F00 /* Release */ = { 496 | isa = XCBuildConfiguration; 497 | buildSettings = { 498 | BUNDLE_LOADER = "$(TEST_HOST)"; 499 | INFOPLIST_FILE = SpringIndicatorExampleTests/Info.plist; 500 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 501 | PRODUCT_BUNDLE_IDENTIFIER = "com.kyoheiito.$(PRODUCT_NAME:rfc1034identifier)"; 502 | PRODUCT_NAME = "$(TARGET_NAME)"; 503 | SWIFT_VERSION = 5.0; 504 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SpringIndicatorExample.app/SpringIndicatorExample"; 505 | }; 506 | name = Release; 507 | }; 508 | /* End XCBuildConfiguration section */ 509 | 510 | /* Begin XCConfigurationList section */ 511 | C724B8B11AA8CC31006F0F00 /* Build configuration list for PBXProject "SpringIndicatorExample" */ = { 512 | isa = XCConfigurationList; 513 | buildConfigurations = ( 514 | C724B8D31AA8CC31006F0F00 /* Debug */, 515 | C724B8D41AA8CC31006F0F00 /* Release */, 516 | ); 517 | defaultConfigurationIsVisible = 0; 518 | defaultConfigurationName = Release; 519 | }; 520 | C724B8D51AA8CC31006F0F00 /* Build configuration list for PBXNativeTarget "SpringIndicatorExample" */ = { 521 | isa = XCConfigurationList; 522 | buildConfigurations = ( 523 | C724B8D61AA8CC31006F0F00 /* Debug */, 524 | C724B8D71AA8CC31006F0F00 /* Release */, 525 | ); 526 | defaultConfigurationIsVisible = 0; 527 | defaultConfigurationName = Release; 528 | }; 529 | C724B8D81AA8CC31006F0F00 /* Build configuration list for PBXNativeTarget "SpringIndicatorExampleTests" */ = { 530 | isa = XCConfigurationList; 531 | buildConfigurations = ( 532 | C724B8D91AA8CC31006F0F00 /* Debug */, 533 | C724B8DA1AA8CC31006F0F00 /* Release */, 534 | ); 535 | defaultConfigurationIsVisible = 0; 536 | defaultConfigurationName = Release; 537 | }; 538 | /* End XCConfigurationList section */ 539 | }; 540 | rootObject = C724B8AE1AA8CC31006F0F00 /* Project object */; 541 | } 542 | -------------------------------------------------------------------------------- /SpringIndicatorExample/SpringIndicatorExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SpringIndicatorExample/SpringIndicatorExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SpringIndicatorExample/SpringIndicatorExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SpringIndicatorExample 4 | // 5 | // Created by Kyohei Ito on 2015/03/06. 6 | // Copyright (c) 2015年 kyohei_ito. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /SpringIndicatorExample/SpringIndicatorExample/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /SpringIndicatorExample/SpringIndicatorExample/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 65 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /SpringIndicatorExample/SpringIndicatorExample/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /SpringIndicatorExample/SpringIndicatorExample/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 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /SpringIndicatorExample/SpringIndicatorExample/TableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewController.swift 3 | // SpringIndicatorExample 4 | // 5 | // Created by Kyohei Ito on 2015/05/18. 6 | // Copyright (c) 2015年 kyohei_ito. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SpringIndicator 11 | 12 | class TableViewController: UIViewController { 13 | @IBOutlet weak var tableView: UITableView! 14 | let refreshControl = RefreshIndicator() 15 | 16 | let dataSourceList: [[String]] = [[Int](0..<20).map({ "section 0, cell \($0)" })] 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | // Do any additional setup after loading the view, typically from a nib. 21 | 22 | tableView.dataSource = self 23 | tableView.delegate = self 24 | 25 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell") 26 | 27 | refreshControl.addTarget(self, action: #selector(TableViewController.onRefresh), for: .valueChanged) 28 | tableView.addSubview(refreshControl) 29 | } 30 | 31 | @objc func onRefresh() { 32 | DispatchQueue.main.asyncAfter(deadline: .now() + 2) { 33 | self.refreshControl.endRefreshing() 34 | } 35 | } 36 | } 37 | 38 | extension TableViewController: UITableViewDelegate, UITableViewDataSource { 39 | func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { 40 | let view = UIView() 41 | view.backgroundColor = UIColor(red: 1, green: 0, blue: 0, alpha: 0.5) 42 | return view 43 | } 44 | 45 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 46 | return dataSourceList[section].count 47 | } 48 | 49 | func numberOfSections(in tableView: UITableView) -> Int { 50 | return dataSourceList.count 51 | } 52 | 53 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 54 | return 100 55 | } 56 | 57 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 58 | let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as UITableViewCell 59 | cell.textLabel?.text = "\((indexPath as NSIndexPath).section) : \((indexPath as NSIndexPath).row)" 60 | 61 | return cell 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /SpringIndicatorExample/SpringIndicatorExample/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SpringIndicatorExample 4 | // 5 | // Created by Kyohei Ito on 2015/03/06. 6 | // Copyright (c) 2015年 kyohei_ito. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SpringIndicator 11 | 12 | class ViewController: UIViewController { 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | // Do any additional setup after loading the view, typically from a nib. 17 | 18 | let defaultIndicator = SpringIndicator(frame: CGRect(x: 100, y: 100, width: 60, height: 60)) 19 | defaultIndicator.lineColors = [.red, .blue, .orange, .green] 20 | defaultIndicator.rotationDuration = 2 21 | view.addSubview(defaultIndicator) 22 | defaultIndicator.start() 23 | 24 | let colorIndicator = SpringIndicator(frame: CGRect(x: 300, y: 100, width: 20, height: 20)) 25 | colorIndicator.lineColor = UIColor.red 26 | colorIndicator.lineWidth = 2 27 | colorIndicator.rotationDuration = 1 28 | view.addSubview(colorIndicator) 29 | colorIndicator.start() 30 | } 31 | 32 | override func didReceiveMemoryWarning() { 33 | super.didReceiveMemoryWarning() 34 | // Dispose of any resources that can be recreated. 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /SpringIndicatorExample/SpringIndicatorExample/WebViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebViewController.swift 3 | // SpringIndicatorExample 4 | // 5 | // Created by Kyohei Ito on 2015/04/20. 6 | // Copyright (c) 2015年 kyohei_ito. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SpringIndicator 11 | 12 | class WebViewController: UIViewController, UIWebViewDelegate { 13 | @IBOutlet weak var webView: UIWebView! 14 | let refreshControl = RefreshIndicator() 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | 19 | // Do any additional setup after loading the view. 20 | let url = URL(string: "https://www.apple.com") 21 | let request = URLRequest(url: url!) 22 | webView.loadRequest(request) 23 | 24 | refreshControl.indicator.lineColor = UIColor.red 25 | refreshControl.addTarget(self, action: #selector(WebViewController.onRefresh), for: .valueChanged) 26 | webView.scrollView.addSubview(refreshControl) 27 | } 28 | 29 | override func didReceiveMemoryWarning() { 30 | super.didReceiveMemoryWarning() 31 | // Dispose of any resources that can be recreated. 32 | } 33 | 34 | @objc func onRefresh() { 35 | webView.reload() 36 | } 37 | 38 | func webViewDidFinishLoad(_ webView: UIWebView) { 39 | refreshControl.endRefreshing() 40 | } 41 | 42 | func webView(_ webView: UIWebView, didFailLoadWithError error: Error) { 43 | refreshControl.endRefreshing() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /SpringIndicatorExample/SpringIndicatorExampleTests/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 | -------------------------------------------------------------------------------- /SpringIndicatorExample/SpringIndicatorExampleTests/SpringIndicatorExampleTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SpringIndicatorExampleTests.swift 3 | // SpringIndicatorExampleTests 4 | // 5 | // Created by Kyohei Ito on 2015/03/06. 6 | // Copyright (c) 2015年 kyohei_ito. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import XCTest 11 | 12 | class SpringIndicatorExampleTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | XCTAssert(true, "Pass") 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure() { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /SpringIndicatorTests/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 | -------------------------------------------------------------------------------- /SpringIndicatorTests/SpringIndicatorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SpringIndicatorTests.swift 3 | // SpringIndicatorTests 4 | // 5 | // Created by Kyohei Ito on 2015/03/06. 6 | // Copyright (c) 2015年 kyohei_ito. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import XCTest 11 | 12 | class SpringIndicatorTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | XCTAssert(true, "Pass") 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure() { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | --------------------------------------------------------------------------------