├── surfingRefreshControlDemo ├── surfingRefreshControlDemo │ ├── surfing.jpg │ ├── SurfingRefreshControl │ │ ├── Plists │ │ │ ├── square.plist │ │ │ ├── polygon.plist │ │ │ ├── storehouserefresh.plist │ │ │ └── surfing.plist │ │ ├── SurfingBarItem.swift │ │ └── SurfingRefreshControl.swift │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ ├── DemoViewController.swift │ └── Base.lproj │ │ └── LaunchScreen.storyboard └── surfingRefreshControlDemo.xcodeproj │ ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ ├── peiwei.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ │ └── antonioalves.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ ├── xcuserdata │ ├── antonioalves.xcuserdatad │ │ ├── xcdebugger │ │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ │ ├── xcschememanagement.plist │ │ │ └── surfingRefreshControlDemo.xcscheme │ └── peiwei.xcuserdatad │ │ └── xcschemes │ │ ├── xcschememanagement.plist │ │ └── surfingRefreshControlDemo.xcscheme │ └── project.pbxproj ├── SurfingRefreshControl ├── Plists │ ├── square.plist │ ├── polygon.plist │ ├── storehouserefresh.plist │ └── surfing.plist ├── SurfingBarItem.swift └── SurfingRefreshControl.swift ├── SurfingRefreshControl.podspec ├── LICENSE └── README.md /surfingRefreshControlDemo/surfingRefreshControlDemo/surfing.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peiweichen/SurfingRefreshControl/HEAD/surfingRefreshControlDemo/surfingRefreshControlDemo/surfing.jpg -------------------------------------------------------------------------------- /surfingRefreshControlDemo/surfingRefreshControlDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /surfingRefreshControlDemo/surfingRefreshControlDemo.xcodeproj/project.xcworkspace/xcuserdata/peiwei.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peiweichen/SurfingRefreshControl/HEAD/surfingRefreshControlDemo/surfingRefreshControlDemo.xcodeproj/project.xcworkspace/xcuserdata/peiwei.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /surfingRefreshControlDemo/surfingRefreshControlDemo.xcodeproj/project.xcworkspace/xcuserdata/antonioalves.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peiweichen/SurfingRefreshControl/HEAD/surfingRefreshControlDemo/surfingRefreshControlDemo.xcodeproj/project.xcworkspace/xcuserdata/antonioalves.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /SurfingRefreshControl/Plists/square.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | startPoints 6 | 7 | {0,0} 8 | {30,0} 9 | {30,30} 10 | {0,30} 11 | 12 | endPoints 13 | 14 | {30,0} 15 | {30,30} 16 | {0,30} 17 | {0,0} 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /surfingRefreshControlDemo/surfingRefreshControlDemo/SurfingRefreshControl/Plists/square.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | startPoints 6 | 7 | {0,0} 8 | {30,0} 9 | {30,30} 10 | {0,30} 11 | 12 | endPoints 13 | 14 | {30,0} 15 | {30,30} 16 | {0,30} 17 | {0,0} 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /surfingRefreshControlDemo/surfingRefreshControlDemo.xcodeproj/xcuserdata/antonioalves.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /SurfingRefreshControl/Plists/polygon.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | startPoints 6 | 7 | {16.5,1} 8 | {32.19,10.67} 9 | {26.2,26.33} 10 | {6.8,26.33} 11 | {0.81,10.67} 12 | 13 | endPoints 14 | 15 | {32.19,10.67} 16 | {26.2,26.33} 17 | {6.8,26.33} 18 | {0.81,10.67} 19 | {16.5,1} 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /surfingRefreshControlDemo/surfingRefreshControlDemo.xcodeproj/xcuserdata/peiwei.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | surfingRefreshControlDemo.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | F4EEEF321D0A3D5C00FA3BB4 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /surfingRefreshControlDemo/surfingRefreshControlDemo.xcodeproj/xcuserdata/antonioalves.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | surfingRefreshControlDemo.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | F4EEEF321D0A3D5C00FA3BB4 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /surfingRefreshControlDemo/surfingRefreshControlDemo/SurfingRefreshControl/Plists/polygon.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | startPoints 6 | 7 | {16.5,1} 8 | {32.19,10.67} 9 | {26.2,26.33} 10 | {6.8,26.33} 11 | {0.81,10.67} 12 | 13 | endPoints 14 | 15 | {32.19,10.67} 16 | {26.2,26.33} 17 | {6.8,26.33} 18 | {0.81,10.67} 19 | {16.5,1} 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /surfingRefreshControlDemo/surfingRefreshControlDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // surfingRefreshControlDemo 4 | // 5 | // Created by chenpeiwei on 6/10/16. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | self.window = UIWindow(frame: UIScreen.main.bounds) 19 | self.window?.rootViewController = DemoViewController() 20 | self.window?.makeKeyAndVisible() 21 | return true 22 | } 23 | 24 | 25 | } 26 | 27 | -------------------------------------------------------------------------------- /surfingRefreshControlDemo/surfingRefreshControlDemo/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 | } -------------------------------------------------------------------------------- /SurfingRefreshControl.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "SurfingRefreshControl" 3 | s.version = "1.0" 4 | s.license = { :type => "MIT", :file => "LICENSE" } 5 | s.summary = "Customizable pull-to-refresh control,written in pure Swift" 6 | s.homepage = "https://github.com/peiweichen/SurfingRefreshControl" 7 | s.author = { "Peiwei" => "peiwei233@gmail.com" } 8 | s.source = { :git => "https://github.com/peiweichen/SurfingRefreshControl.git", :tag => s.version } 9 | s.source_files = 'SurfingRefreshControl' 10 | s.description = <<-DESC 11 | This project is heavily inspired by CBStoreHouseRefreshControl which is Objective-C implemented. SurfingRefreshControl provides you a chance to use pure Swift alternative in your next app. 12 | DESC 13 | s.platform = :ios, '7.0' 14 | s.screenshots = "http://peiweichen.github.io/outofwebsite/gif/surfing.gif", "http://peiweichen.github.io/outofwebsite/gif/storehouse.gif" 15 | s.requires_arc = true 16 | end 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Peiwei 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 | -------------------------------------------------------------------------------- /SurfingRefreshControl/Plists/storehouserefresh.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | startPoints 6 | 7 | {0,35} 8 | {12,42} 9 | {24,35} 10 | {0,35} 11 | {0,21} 12 | {12,28} 13 | {24,35} 14 | {24,21} 15 | {0,21} 16 | {0,21} 17 | {12,14} 18 | {12,14} 19 | {24,7} 20 | {0,7} 21 | 22 | endPoints 23 | 24 | {12,42} 25 | {24,35} 26 | {12,28} 27 | {12,28} 28 | {12,28} 29 | {24,21} 30 | {24,21} 31 | {12,14} 32 | {12,14} 33 | {0,7} 34 | {0,7} 35 | {24,7} 36 | {12,0} 37 | {12,0} 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /surfingRefreshControlDemo/surfingRefreshControlDemo/SurfingRefreshControl/Plists/storehouserefresh.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | startPoints 6 | 7 | {0,35} 8 | {12,42} 9 | {24,35} 10 | {0,35} 11 | {0,21} 12 | {12,28} 13 | {24,35} 14 | {24,21} 15 | {0,21} 16 | {0,21} 17 | {12,14} 18 | {12,14} 19 | {24,7} 20 | {0,7} 21 | 22 | endPoints 23 | 24 | {12,42} 25 | {24,35} 26 | {12,28} 27 | {12,28} 28 | {12,28} 29 | {24,21} 30 | {24,21} 31 | {12,14} 32 | {12,14} 33 | {0,7} 34 | {0,7} 35 | {24,7} 36 | {12,0} 37 | {12,0} 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /surfingRefreshControlDemo/surfingRefreshControlDemo/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 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UIRequiresFullScreen 32 | 33 | UIStatusBarHidden 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /SurfingRefreshControl/SurfingBarItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SurfBarItem.swift 3 | // CBStoreHouseRefreshControl 4 | // 5 | // Created by chenpeiwei on 6/8/16. 6 | // Copyright © 2016 Peiwei Chen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SurfingBarItem: UIView { 12 | var translationX:CGFloat = 0 13 | fileprivate var lineWidth:CGFloat = 0 14 | fileprivate var startPoint:CGPoint = CGPoint.zero 15 | fileprivate var endPoint:CGPoint = CGPoint.zero 16 | fileprivate var middlePoint:CGPoint = CGPoint.zero 17 | fileprivate var color:UIColor = UIColor.white 18 | 19 | convenience init(frame:CGRect,startPoint:CGPoint,endPoint:CGPoint,color:UIColor,lineWidth:CGFloat) { 20 | self.init(frame: frame) 21 | self.startPoint = startPoint 22 | self.endPoint = endPoint 23 | self.color = color 24 | self.lineWidth = lineWidth 25 | self.middlePoint = CGPoint(x: (startPoint.x+endPoint.x)*0.5, y:( startPoint.y+endPoint.y)*0.5) 26 | } 27 | 28 | func setHorizontalRandomness(_ horizontalRandomness:Int,dropHeight:CGFloat) { 29 | let horizontalRandomnessCGFloat = CGFloat(horizontalRandomness) 30 | let ramdomNumberInt = Int(arc4random_uniform(65535)) 31 | let randomRangeNumberCGFloat = CGFloat( ramdomNumberInt%horizontalRandomness*2 ) 32 | self.translationX = -horizontalRandomnessCGFloat + randomRangeNumberCGFloat 33 | } 34 | 35 | override func draw(_ rect: CGRect) { 36 | let bezierPath = UIBezierPath() 37 | bezierPath.move(to: self.startPoint) 38 | bezierPath.addLine(to: self.endPoint) 39 | self.color.setStroke() 40 | bezierPath.lineWidth = self.lineWidth 41 | bezierPath.stroke() 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /surfingRefreshControlDemo/surfingRefreshControlDemo/SurfingRefreshControl/SurfingBarItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SurfBarItem.swift 3 | // CBStoreHouseRefreshControl 4 | // 5 | // Created by chenpeiwei on 6/8/16. 6 | // Copyright © 2016 Peiwei Chen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SurfingBarItem: UIView { 12 | var translationX:CGFloat = 0 13 | fileprivate var lineWidth:CGFloat = 0 14 | fileprivate var startPoint:CGPoint = CGPoint.zero 15 | fileprivate var endPoint:CGPoint = CGPoint.zero 16 | fileprivate var middlePoint:CGPoint = CGPoint.zero 17 | fileprivate var color:UIColor = UIColor.white 18 | 19 | convenience init(frame:CGRect,startPoint:CGPoint,endPoint:CGPoint,color:UIColor,lineWidth:CGFloat) { 20 | self.init(frame: frame) 21 | self.startPoint = startPoint 22 | self.endPoint = endPoint 23 | self.color = color 24 | self.lineWidth = lineWidth 25 | self.middlePoint = CGPoint(x: (startPoint.x+endPoint.x)*0.5, y:( startPoint.y+endPoint.y)*0.5) 26 | } 27 | 28 | func setHorizontalRandomness(_ horizontalRandomness:Int,dropHeight:CGFloat) { 29 | let horizontalRandomnessCGFloat = CGFloat(horizontalRandomness) 30 | let ramdomNumberInt = Int(arc4random_uniform(65535)) 31 | let randomRangeNumberCGFloat = CGFloat( ramdomNumberInt%horizontalRandomness*2 ) 32 | self.translationX = -horizontalRandomnessCGFloat + randomRangeNumberCGFloat 33 | } 34 | 35 | override func draw(_ rect: CGRect) { 36 | let bezierPath = UIBezierPath() 37 | bezierPath.move(to: self.startPoint) 38 | bezierPath.addLine(to: self.endPoint) 39 | self.color.setStroke() 40 | bezierPath.lineWidth = self.lineWidth 41 | bezierPath.stroke() 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /SurfingRefreshControl/Plists/surfing.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | startPoints 6 | 7 | {20.5,11.5} 8 | {8.5,11.5} 9 | {8.5,30.5} 10 | {21.5,29.5} 11 | {21.5,45.5} 12 | {32.5,10.5} 13 | {32.5,51.5} 14 | {47.5,51.5} 15 | {52.5,45.5} 16 | {65.5,10.5} 17 | {65.5,53.5} 18 | {66.5,9.5} 19 | {87.5,9.5} 20 | {87.5,29.5} 21 | {66.5,29.5} 22 | {87.5,50.5} 23 | {101.5,8.5} 24 | {121.5,8.5} 25 | {102.5,8.5} 26 | {105.06,27.73} 27 | {106.5,30.5} 28 | {135.5,7.5} 29 | {149.5,8.5} 30 | {149.5,51.5} 31 | {150.5,8.5} 32 | {171.5,49.5} 33 | {217.5,8.5} 34 | {191.5,8.5} 35 | {191.5,51.5} 36 | {217.5,51.5} 37 | 38 | endPoints 39 | 40 | {8.5,11.5} 41 | {8.5,30.5} 42 | {20.5,30.5} 43 | {21.5,45.5} 44 | {10.5,51.5} 45 | {32.5,51.5} 46 | {47.5,51.5} 47 | {52.5,45.5} 48 | {52.5,10.5} 49 | {65.5,53.5} 50 | {65.5,10.5} 51 | {87.5,9.5} 52 | {87.5,29.5} 53 | {66.5,29.5} 54 | {87.5,50.5} 55 | {95.5,50.5} 56 | {121.5,8.5} 57 | {101.5,8.5} 58 | {105.06,27.73} 59 | {108.5,53.5} 60 | {125.5,30.5} 61 | {135.5,51.5} 62 | {149.5,51.5} 63 | {149.5,8.5} 64 | {171.5,49.5} 65 | {177.5,8.5} 66 | {191.5,8.5} 67 | {191.5,51.5} 68 | {217.5,51.5} 69 | {217.5,29.5} 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /surfingRefreshControlDemo/surfingRefreshControlDemo/SurfingRefreshControl/Plists/surfing.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | startPoints 6 | 7 | {20.5,11.5} 8 | {8.5,11.5} 9 | {8.5,30.5} 10 | {21.5,29.5} 11 | {21.5,45.5} 12 | {32.5,10.5} 13 | {32.5,51.5} 14 | {47.5,51.5} 15 | {52.5,45.5} 16 | {65.5,10.5} 17 | {65.5,53.5} 18 | {66.5,9.5} 19 | {87.5,9.5} 20 | {87.5,29.5} 21 | {66.5,29.5} 22 | {87.5,50.5} 23 | {101.5,8.5} 24 | {121.5,8.5} 25 | {102.5,8.5} 26 | {105.06,27.73} 27 | {106.5,30.5} 28 | {135.5,7.5} 29 | {149.5,8.5} 30 | {149.5,51.5} 31 | {150.5,8.5} 32 | {171.5,49.5} 33 | {217.5,8.5} 34 | {191.5,8.5} 35 | {191.5,51.5} 36 | {217.5,51.5} 37 | 38 | endPoints 39 | 40 | {8.5,11.5} 41 | {8.5,30.5} 42 | {20.5,30.5} 43 | {21.5,45.5} 44 | {10.5,51.5} 45 | {32.5,51.5} 46 | {47.5,51.5} 47 | {52.5,45.5} 48 | {52.5,10.5} 49 | {65.5,53.5} 50 | {65.5,10.5} 51 | {87.5,9.5} 52 | {87.5,29.5} 53 | {66.5,29.5} 54 | {87.5,50.5} 55 | {95.5,50.5} 56 | {121.5,8.5} 57 | {101.5,8.5} 58 | {105.06,27.73} 59 | {108.5,53.5} 60 | {125.5,30.5} 61 | {135.5,51.5} 62 | {149.5,51.5} 63 | {149.5,8.5} 64 | {171.5,49.5} 65 | {177.5,8.5} 66 | {191.5,8.5} 67 | {191.5,51.5} 68 | {217.5,51.5} 69 | {217.5,29.5} 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # What is it 2 | 3 | 4 | This project is heavily inspired by [CBStoreHouseRefreshControl](https://github.com/coolbeet/CBStoreHouseRefreshControl) which is Objective-C implemented. SurfingRefreshControl provides you a chance to use pure Swift alternative in your next app. 5 | 6 | You can customize any desired shape through `plist` file like below two shapes: 7 | 8 | ![alt tag](http://peiweichen.github.io/outofwebsite/gif/surfing.gif) 9 | 10 | ![alt tag](http://peiweichen.github.io/outofwebsite/gif/storehouse.gif) 11 | 12 | 13 | 14 | 15 | # How to use 16 | 17 | ### Warning:Check out demo app directly if you're too lazy to read 18 | 19 | You can attach it to any `UIScrollView` ( like `UITableView` `UICollectionView`) : 20 | 21 | ```swift 22 | class func attachToScrollView( 23 | scrollView:UIScrollView, 24 | target:AnyObject, 25 | refreshAction:Selector, 26 | plist:String, 27 | color:UIColor=UIColor.blackColor(), 28 | lineWidth:CGFloat=2, 29 | dropHeight:CGFloat=80, 30 | scale:CGFloat=1, 31 | horizontalRandomness:Int=150, 32 | reverseLoadingAnimation:Bool=false, 33 | internalAnimationFactor:CGFloat=1.0) -> SurfingRefreshControl 34 | ``` 35 | 36 | For instance: 37 | 38 | ```swift 39 | self.surfingRefreshControl = SurfingRefreshControl.attachToScrollView(tableView, target: self,refreshAction:#selector(DemoViewController.refreshTriggered), plist: "surfing", color: UIColor.whiteColor(),lineWidth: 1.5, dropHeight: 120, scale: 1.0, horizontalRandomness: 100, reverseLoadingAnimation: false, internalAnimationFactor: 0.8) 40 | ``` 41 | 42 | Implement `UIScrollViewDelegate` in your `UIViewController` to notify SurfingRefreshControl your scrollView's movements: 43 | 44 | ```swift 45 | override func scrollViewDidScroll(scrollView: UIScrollView) { 46 | self.surfingRefreshControl.scrollViewDidScroll(scrollView) 47 | } 48 | 49 | override func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) { 50 | self.surfingRefreshControl.scrollViewDidEndDragging(scrollView, willDecelerate: decelerate) 51 | } 52 | ``` 53 | 54 | When you're done with surfing,simply call 55 | 56 | ```swift 57 | self.surfingRefreshControl.finishLoading() 58 | ``` 59 | 60 | # How to customize your refresh: 61 | 62 | SurfingRefreshControl make use of `SurfingBarItem` which actually is a UIView subclass, made of start point and end point to form a line.Imagine a squre need 4 line,thus need 4 SurfingBarItem,which equal to 4 start points and 4 end points.In conclusion,a square surfing refresh control would need a plist file of 4 start points and 4 end points,as shown below: 63 | 64 | ![alt tag](http://peiweichen.github.io/outofwebsite/images/squareplist.png) 65 | 66 | 67 | Square SurfingRefreshControl output is: 68 | 69 | ![alt tag](http://peiweichen.github.io/outofwebsite/images/squarerefresh.png) 70 | 71 | 72 | [PaintCode](http://www.paintcodeapp.com/) to generate your startpoints and endpoints without too much hassle: 73 | 74 | ![alt tag](http://peiweichen.github.io/outofwebsite/images/paintcodedemo.png) 75 | 76 | 77 | Author 78 | ------ 79 | Peiwei Chen 80 | peiwei233@gmail.com 81 | [Blog](http://peiweichen.github.io) 82 | 83 | 84 | License 85 | ------- 86 | Copyright (c) 2016 Peiwei Chen . See the LICENSE file for license rights and limitations (MIT). 87 | 88 | 89 | -------------------------------------------------------------------------------- /surfingRefreshControlDemo/surfingRefreshControlDemo/DemoViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoViewController.swift 3 | // surfingRefreshControlDemo 4 | // 5 | // Created by chenpeiwei on 6/10/16. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | class DemoViewController: UITableViewController { 12 | 13 | var surfingRefreshControl:SurfingRefreshControl! 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | 18 | self.title = "Surfing" 19 | self.navigationController?.navigationBar.tintColor = UIColor.white 20 | self.tableView.backgroundColor = UIColor.black 21 | self.tableView.register(UITableViewCell.classForCoder(), forCellReuseIdentifier: "cellIdentifier") 22 | self.tableView.tableFooterView = UIView() 23 | self.tableView.separatorStyle = .none 24 | 25 | //storehouserefresh,surfing,polygon,square 26 | surfingRefreshControl = SurfingRefreshControl.attachToScrollView(tableView, target: self, refreshAction: #selector(DemoViewController.refreshTriggered), plist: "surfing", color: UIColor.white, lineWidth: 2.0, dropHeight: 120, scale: 1.1, horizontalRandomness: 100, reverseLoadingAnimation: false, internalAnimationFactor: 0.8) 27 | } 28 | func refreshTriggered() { 29 | self.perform(#selector(DemoViewController.endRefresh), with: nil, afterDelay: 3.0, inModes: [RunLoopMode.commonModes]) 30 | } 31 | func endRefresh() { 32 | surfingRefreshControl.finishLoading() 33 | } 34 | override func scrollViewDidScroll(_ scrollView: UIScrollView) { 35 | surfingRefreshControl.scrollViewDidScroll(scrollView) 36 | } 37 | override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { 38 | surfingRefreshControl.scrollViewDidEndDragging(scrollView, willDecelerate: decelerate) 39 | } 40 | 41 | 42 | 43 | 44 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 45 | 46 | return 1 47 | } 48 | 49 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 50 | let cell = tableView.dequeueReusableCell(withIdentifier: "cellIdentifier") 51 | cell?.selectionStyle = .none 52 | cell?.backgroundColor = UIColor.clear 53 | 54 | let imageView = UIImageView(image: UIImage(named: "surfing.jpg")) 55 | imageView.translatesAutoresizingMaskIntoConstraints = false 56 | imageView.contentMode = .scaleAspectFill 57 | imageView.clipsToBounds = true 58 | 59 | cell?.contentView.addSubview(imageView) 60 | 61 | cell?.contentView.addConstraint(NSLayoutConstraint(item: imageView, attribute: .top, relatedBy: .equal, toItem: cell?.contentView, attribute: .top, multiplier: 1, constant: 0)) 62 | cell?.contentView.addConstraint(NSLayoutConstraint(item: imageView, attribute: .leading, relatedBy: .equal, toItem: cell?.contentView, attribute: .leading, multiplier: 1, constant: 0)) 63 | cell?.contentView.addConstraint(NSLayoutConstraint(item: imageView, attribute: .trailing, relatedBy: .equal, toItem: cell?.contentView, attribute: .trailing, multiplier: 1, constant: 0)) 64 | cell?.contentView.addConstraint(NSLayoutConstraint(item: imageView, attribute: .bottom, relatedBy: .equal, toItem: cell?.contentView, attribute: .bottom, multiplier: 1, constant: 0)) 65 | 66 | 67 | return cell! 68 | 69 | } 70 | 71 | override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 72 | return 300 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /surfingRefreshControlDemo/surfingRefreshControlDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /surfingRefreshControlDemo/surfingRefreshControlDemo.xcodeproj/xcuserdata/peiwei.xcuserdatad/xcschemes/surfingRefreshControlDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /surfingRefreshControlDemo/surfingRefreshControlDemo.xcodeproj/xcuserdata/antonioalves.xcuserdatad/xcschemes/surfingRefreshControlDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /SurfingRefreshControl/SurfingRefreshControl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SurfingRefreshControl.swift 3 | // CBStoreHouseRefreshControl 4 | // 5 | // Created by chenpeiwei on 6/8/16. 6 | // Copyright © 2016 Peiwei Chen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | fileprivate func < (lhs: T?, rhs: T?) -> Bool { 11 | switch (lhs, rhs) { 12 | case let (l?, r?): 13 | return l < r 14 | case (nil, _?): 15 | return true 16 | default: 17 | return false 18 | } 19 | } 20 | 21 | fileprivate func >= (lhs: T?, rhs: T?) -> Bool { 22 | switch (lhs, rhs) { 23 | case let (l?, r?): 24 | return l >= r 25 | default: 26 | return !(lhs < rhs) 27 | } 28 | } 29 | 30 | fileprivate func <= (lhs: T?, rhs: T?) -> Bool { 31 | switch (lhs, rhs) { 32 | case let (l?, r?): 33 | return l <= r 34 | default: 35 | return !(rhs < lhs) 36 | } 37 | } 38 | 39 | struct SurfingConfigs { 40 | static let loadingIndividualAnimationTiming = 0.8 41 | static let barDarkAlpha:CGFloat = 0.4 42 | static let loadingTimingOffset = 0.1 43 | static let disappearDuration = 1.2 44 | static let relativeHeightFactor:CGFloat = 0.4 45 | static let startPointKey = "startPoints" 46 | static let endPointKey = "endPoints" 47 | static let xKey = "x" 48 | static let yKey = "y" 49 | } 50 | 51 | enum SurfingRefreshControlState { 52 | case idle 53 | case refreshing 54 | case disappearing 55 | } 56 | 57 | class SurfingRefreshControl: UIView { 58 | weak var scrollView:UIScrollView! 59 | var state:SurfingRefreshControlState = .idle 60 | var barItems:[SurfingBarItem]! 61 | var displayLink:CADisplayLink? 62 | var target:AnyObject! 63 | var action:Selector! 64 | 65 | var dropHeight:CGFloat! 66 | var disappearProgress:CGFloat! 67 | var internalAnimationFactor:CGFloat! 68 | var horizontalRandomness:Int! 69 | var reverseLoadingAnimation:Bool! 70 | 71 | var realContentOffsetY:CGFloat { 72 | get { 73 | return self.scrollView.contentOffset.y + self.scrollView.contentInset.top 74 | } 75 | } 76 | 77 | var surfingBarItemsAnimationProgress:CGFloat { 78 | get { 79 | return min(1.0,max(0,fabs(self.realContentOffsetY/self.dropHeight))) 80 | } 81 | } 82 | 83 | class func attachToScrollView(_ scrollView:UIScrollView,target:AnyObject,refreshAction:Selector,plist:String,color:UIColor=UIColor.black,lineWidth:CGFloat=2,dropHeight:CGFloat=80,scale:CGFloat=1,horizontalRandomness:Int=150,reverseLoadingAnimation:Bool=false,internalAnimationFactor:CGFloat=1.0) -> SurfingRefreshControl{ 84 | 85 | let refreshControl = SurfingRefreshControl() 86 | refreshControl.dropHeight = dropHeight 87 | refreshControl.horizontalRandomness = horizontalRandomness 88 | refreshControl.scrollView = scrollView 89 | refreshControl.target = target 90 | refreshControl.action = refreshAction 91 | refreshControl.reverseLoadingAnimation = reverseLoadingAnimation 92 | refreshControl.internalAnimationFactor = internalAnimationFactor 93 | scrollView.addSubview(refreshControl) 94 | 95 | var width:CGFloat = 0 96 | var height:CGFloat = 0 97 | 98 | let dataDictionary = NSDictionary(contentsOfFile: Bundle.main.path(forResource: plist, ofType: "plist")!) 99 | let startPoints = dataDictionary?.object(forKey: SurfingConfigs.startPointKey) as! NSArray 100 | let endPoints = dataDictionary?.object(forKey: SurfingConfigs.endPointKey) as! NSArray 101 | 102 | for (index,startPointObject) in startPoints.enumerated() { 103 | let startPoint = CGPointFromString(startPointObject as! String) 104 | let endPoint = CGPointFromString(endPoints[index] as! String) 105 | 106 | if startPoint.x > width { 107 | width = startPoint.x 108 | } 109 | if endPoint.x > width { 110 | width = endPoint.x 111 | } 112 | if startPoint.y > height { 113 | height = startPoint.y 114 | } 115 | if endPoint.y > height { 116 | height = endPoint.y 117 | } 118 | } 119 | 120 | refreshControl.frame = CGRect(x: 0, y: 0, width: width, height: height) 121 | 122 | var surfingBarItems = [SurfingBarItem]() 123 | 124 | for (index,startPointObject) in startPoints.enumerated() { 125 | let startPoint = CGPointFromString(startPointObject as! String) 126 | let endPoint = CGPointFromString(endPoints[index] as! String) 127 | let surfingBarItem = SurfingBarItem(frame: refreshControl.frame, startPoint: startPoint, endPoint: endPoint, color: color, lineWidth: lineWidth) 128 | surfingBarItem.tag = index 129 | surfingBarItem.backgroundColor = UIColor.clear 130 | surfingBarItem.alpha = 0 131 | surfingBarItem.setHorizontalRandomness(refreshControl.horizontalRandomness, dropHeight: refreshControl.dropHeight) 132 | surfingBarItems.append(surfingBarItem) 133 | refreshControl.addSubview(surfingBarItem) 134 | } 135 | refreshControl.barItems = surfingBarItems 136 | refreshControl.transform = CGAffineTransform(scaleX: scale, y: scale) 137 | 138 | return refreshControl 139 | } 140 | 141 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 142 | 143 | if scrollView != self.scrollView { 144 | return 145 | } 146 | 147 | self.center = CGPoint(x: scrollView.frame.size.width*0.5, y: self.scrollView.contentOffset.y*SurfingConfigs.relativeHeightFactor) 148 | if self.state == SurfingRefreshControlState.idle { 149 | self.updateBarItemsWithProgress(self.surfingBarItemsAnimationProgress) 150 | } 151 | } 152 | 153 | 154 | func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { 155 | if self.state == .idle && self.realContentOffsetY < -self.dropHeight { 156 | if self.surfingBarItemsAnimationProgress == 1 { 157 | self.state = .refreshing 158 | var newInsets = self.scrollView.contentInset 159 | newInsets.top += self.dropHeight 160 | let contentOffset = self.scrollView.contentOffset 161 | 162 | UIView.animate(withDuration: 0, animations: { 163 | self.scrollView.contentInset = newInsets 164 | self.scrollView.contentOffset = contentOffset 165 | }) 166 | 167 | if self.target.responds(to: self.action) { 168 | self.target.perform(self.action, with: self) 169 | } 170 | self.startLoadingAnimation() 171 | } 172 | } 173 | } 174 | 175 | func updateBarItemsWithProgress(_ progress:CGFloat) { 176 | for (index,barItem) in self.barItems.enumerated() { 177 | 178 | //internalAnimationFactor: 1 means normal animation, 0.7 cut the animation to 70% compared to normal animation, in this case , we could handle 1-0.7 = 30%. 179 | 180 | 181 | let startPadding = (1 - self.internalAnimationFactor)/CGFloat(self.barItems.count)*CGFloat(index) 182 | let endPadding = 1 - self.internalAnimationFactor - startPadding 183 | 184 | if (progress == 1 || progress >= 1 - endPadding) { 185 | barItem.transform = CGAffineTransform.identity 186 | barItem.alpha = SurfingConfigs.barDarkAlpha 187 | } else if progress == 0 { 188 | barItem.setHorizontalRandomness(self.horizontalRandomness, dropHeight: self.dropHeight) 189 | } 190 | else { 191 | 192 | var surfingProgress:CGFloat! 193 | if progress <= startPadding { 194 | //key for internalAnimationFactor 195 | surfingProgress = 0 196 | } else { 197 | surfingProgress = min(1, (progress - startPadding)/self.internalAnimationFactor) 198 | } 199 | barItem.transform = CGAffineTransform(translationX: barItem.translationX*(1-surfingProgress), y: -self.dropHeight*(1-surfingProgress)) 200 | barItem.transform = barItem.transform.rotated(by: CGFloat(M_PI)*surfingProgress) 201 | barItem.transform = barItem.transform.scaledBy(x: surfingProgress, y: surfingProgress) 202 | barItem.alpha = surfingProgress * SurfingConfigs.barDarkAlpha 203 | } 204 | } 205 | } 206 | 207 | func startLoadingAnimation() { 208 | 209 | //NSRunLoopCommonModes make it possible that animating even when user's dragging scrollView 210 | if self.reverseLoadingAnimation == true { 211 | for (index,barItem) in self.barItems.reversed().enumerated() { 212 | self.perform(#selector(SurfingRefreshControl.barItemAnimation(_:)), with: barItem, afterDelay: Double(index) * SurfingConfigs.loadingTimingOffset, inModes: [RunLoopMode.commonModes]) 213 | } 214 | } else { 215 | for (index,barItem) in self.barItems.enumerated() { 216 | self.perform(#selector(SurfingRefreshControl.barItemAnimation(_:)), with: barItem, afterDelay: Double(index) * SurfingConfigs.loadingTimingOffset, inModes: [RunLoopMode.commonModes]) 217 | } 218 | } 219 | } 220 | 221 | func finishLoading() { 222 | self.state = .disappearing 223 | self.disappearProgress = 0 224 | var newInset = self.scrollView.contentInset 225 | newInset.top = self.scrollView.contentInset.top - self.dropHeight 226 | UIView.animate(withDuration: SurfingConfigs.disappearDuration,animations: { 227 | self.scrollView.contentInset = newInset 228 | }, completion: { (finished) in 229 | if finished { 230 | self.state = .idle 231 | self.displayLink?.isPaused = true 232 | self.displayLink?.invalidate() 233 | self.displayLink = nil 234 | self.disappearProgress = 1 235 | } 236 | }) 237 | 238 | self.displayLink = CADisplayLink(target: self, selector: #selector(SurfingRefreshControl.updateDisappearAnimation)) 239 | self.displayLink?.add(to: RunLoop.main, forMode: RunLoopMode.commonModes) 240 | } 241 | 242 | func updateDisappearAnimation() { 243 | if (self.disappearProgress >= 0 && self.disappearProgress <= 1) { 244 | //60.0 means this method get called 60 times per second , 60fps 245 | self.disappearProgress = self.disappearProgress + CGFloat(1/60.0/SurfingConfigs.disappearDuration); 246 | self.updateBarItemsWithProgress(1 - self.disappearProgress) 247 | 248 | } 249 | } 250 | 251 | func barItemAnimation(_ barItem:SurfingBarItem) { 252 | if self.state == .refreshing { 253 | barItem.alpha = 1 254 | barItem.layer.removeAllAnimations() 255 | UIView.animate(withDuration: SurfingConfigs.loadingIndividualAnimationTiming, animations: { 256 | barItem.alpha = SurfingConfigs.barDarkAlpha 257 | }) 258 | 259 | var isLastOne:Bool! 260 | 261 | if self.reverseLoadingAnimation == true { 262 | isLastOne = barItem.tag == 0 263 | } else { 264 | isLastOne = barItem.tag == self.barItems.count-1 265 | } 266 | 267 | //recursive 268 | if isLastOne == true { 269 | self.startLoadingAnimation() 270 | } 271 | } 272 | } 273 | 274 | } 275 | 276 | 277 | -------------------------------------------------------------------------------- /surfingRefreshControlDemo/surfingRefreshControlDemo/SurfingRefreshControl/SurfingRefreshControl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SurfingRefreshControl.swift 3 | // CBStoreHouseRefreshControl 4 | // 5 | // Created by chenpeiwei on 6/8/16. 6 | // Copyright © 2016 Peiwei Chen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | fileprivate func < (lhs: T?, rhs: T?) -> Bool { 11 | switch (lhs, rhs) { 12 | case let (l?, r?): 13 | return l < r 14 | case (nil, _?): 15 | return true 16 | default: 17 | return false 18 | } 19 | } 20 | 21 | fileprivate func >= (lhs: T?, rhs: T?) -> Bool { 22 | switch (lhs, rhs) { 23 | case let (l?, r?): 24 | return l >= r 25 | default: 26 | return !(lhs < rhs) 27 | } 28 | } 29 | 30 | fileprivate func <= (lhs: T?, rhs: T?) -> Bool { 31 | switch (lhs, rhs) { 32 | case let (l?, r?): 33 | return l <= r 34 | default: 35 | return !(rhs < lhs) 36 | } 37 | } 38 | 39 | struct SurfingConfigs { 40 | static let loadingIndividualAnimationTiming = 0.8 41 | static let barDarkAlpha:CGFloat = 0.4 42 | static let loadingTimingOffset = 0.1 43 | static let disappearDuration = 1.2 44 | static let relativeHeightFactor:CGFloat = 0.4 45 | static let startPointKey = "startPoints" 46 | static let endPointKey = "endPoints" 47 | static let xKey = "x" 48 | static let yKey = "y" 49 | } 50 | 51 | enum SurfingRefreshControlState { 52 | case idle 53 | case refreshing 54 | case disappearing 55 | } 56 | 57 | class SurfingRefreshControl: UIView { 58 | weak var scrollView:UIScrollView! 59 | var state:SurfingRefreshControlState = .idle 60 | var barItems:[SurfingBarItem]! 61 | var displayLink:CADisplayLink? 62 | var target:AnyObject! 63 | var action:Selector! 64 | 65 | var dropHeight:CGFloat! 66 | var disappearProgress:CGFloat! 67 | var internalAnimationFactor:CGFloat! 68 | var horizontalRandomness:Int! 69 | var reverseLoadingAnimation:Bool! 70 | 71 | var realContentOffsetY:CGFloat { 72 | get { 73 | return self.scrollView.contentOffset.y + self.scrollView.contentInset.top 74 | } 75 | } 76 | 77 | var surfingBarItemsAnimationProgress:CGFloat { 78 | get { 79 | return min(1.0,max(0,fabs(self.realContentOffsetY/self.dropHeight))) 80 | } 81 | } 82 | 83 | class func attachToScrollView(_ scrollView:UIScrollView,target:AnyObject,refreshAction:Selector,plist:String,color:UIColor=UIColor.black,lineWidth:CGFloat=2,dropHeight:CGFloat=80,scale:CGFloat=1,horizontalRandomness:Int=150,reverseLoadingAnimation:Bool=false,internalAnimationFactor:CGFloat=1.0) -> SurfingRefreshControl{ 84 | 85 | let refreshControl = SurfingRefreshControl() 86 | refreshControl.dropHeight = dropHeight 87 | refreshControl.horizontalRandomness = horizontalRandomness 88 | refreshControl.scrollView = scrollView 89 | refreshControl.target = target 90 | refreshControl.action = refreshAction 91 | refreshControl.reverseLoadingAnimation = reverseLoadingAnimation 92 | refreshControl.internalAnimationFactor = internalAnimationFactor 93 | scrollView.addSubview(refreshControl) 94 | 95 | var width:CGFloat = 0 96 | var height:CGFloat = 0 97 | 98 | let dataDictionary = NSDictionary(contentsOfFile: Bundle.main.path(forResource: plist, ofType: "plist")!) 99 | let startPoints = dataDictionary?.object(forKey: SurfingConfigs.startPointKey) as! NSArray 100 | let endPoints = dataDictionary?.object(forKey: SurfingConfigs.endPointKey) as! NSArray 101 | 102 | for (index,startPointObject) in startPoints.enumerated() { 103 | let startPoint = CGPointFromString(startPointObject as! String) 104 | let endPoint = CGPointFromString(endPoints[index] as! String) 105 | 106 | if startPoint.x > width { 107 | width = startPoint.x 108 | } 109 | if endPoint.x > width { 110 | width = endPoint.x 111 | } 112 | if startPoint.y > height { 113 | height = startPoint.y 114 | } 115 | if endPoint.y > height { 116 | height = endPoint.y 117 | } 118 | } 119 | 120 | refreshControl.frame = CGRect(x: 0, y: 0, width: width, height: height) 121 | 122 | var surfingBarItems = [SurfingBarItem]() 123 | 124 | for (index,startPointObject) in startPoints.enumerated() { 125 | let startPoint = CGPointFromString(startPointObject as! String) 126 | let endPoint = CGPointFromString(endPoints[index] as! String) 127 | let surfingBarItem = SurfingBarItem(frame: refreshControl.frame, startPoint: startPoint, endPoint: endPoint, color: color, lineWidth: lineWidth) 128 | surfingBarItem.tag = index 129 | surfingBarItem.backgroundColor = UIColor.clear 130 | surfingBarItem.alpha = 0 131 | surfingBarItem.setHorizontalRandomness(refreshControl.horizontalRandomness, dropHeight: refreshControl.dropHeight) 132 | surfingBarItems.append(surfingBarItem) 133 | refreshControl.addSubview(surfingBarItem) 134 | } 135 | refreshControl.barItems = surfingBarItems 136 | refreshControl.transform = CGAffineTransform(scaleX: scale, y: scale) 137 | 138 | return refreshControl 139 | } 140 | 141 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 142 | 143 | if scrollView != self.scrollView { 144 | return 145 | } 146 | 147 | self.center = CGPoint(x: scrollView.frame.size.width*0.5, y: self.scrollView.contentOffset.y*SurfingConfigs.relativeHeightFactor) 148 | if self.state == SurfingRefreshControlState.idle { 149 | self.updateBarItemsWithProgress(self.surfingBarItemsAnimationProgress) 150 | } 151 | } 152 | 153 | 154 | func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { 155 | if self.state == .idle && self.realContentOffsetY < -self.dropHeight { 156 | if self.surfingBarItemsAnimationProgress == 1 { 157 | self.state = .refreshing 158 | var newInsets = self.scrollView.contentInset 159 | newInsets.top += self.dropHeight 160 | let contentOffset = self.scrollView.contentOffset 161 | 162 | UIView.animate(withDuration: 0, animations: { 163 | self.scrollView.contentInset = newInsets 164 | self.scrollView.contentOffset = contentOffset 165 | }) 166 | 167 | if self.target.responds(to: self.action) { 168 | self.target.perform(self.action, with: self) 169 | } 170 | self.startLoadingAnimation() 171 | } 172 | } 173 | } 174 | 175 | func updateBarItemsWithProgress(_ progress:CGFloat) { 176 | for (index,barItem) in self.barItems.enumerated() { 177 | 178 | //internalAnimationFactor: 1 means normal animation, 0.7 cut the animation to 70% compared to normal animation, in this case , we could handle 1-0.7 = 30%. 179 | 180 | 181 | let startPadding = (1 - self.internalAnimationFactor)/CGFloat(self.barItems.count)*CGFloat(index) 182 | let endPadding = 1 - self.internalAnimationFactor - startPadding 183 | 184 | if (progress == 1 || progress >= 1 - endPadding) { 185 | barItem.transform = CGAffineTransform.identity 186 | barItem.alpha = SurfingConfigs.barDarkAlpha 187 | } else if progress == 0 { 188 | barItem.setHorizontalRandomness(self.horizontalRandomness, dropHeight: self.dropHeight) 189 | } 190 | else { 191 | 192 | var surfingProgress:CGFloat! 193 | if progress <= startPadding { 194 | //key for internalAnimationFactor 195 | surfingProgress = 0 196 | } else { 197 | surfingProgress = min(1, (progress - startPadding)/self.internalAnimationFactor) 198 | } 199 | barItem.transform = CGAffineTransform(translationX: barItem.translationX*(1-surfingProgress), y: -self.dropHeight*(1-surfingProgress)) 200 | barItem.transform = barItem.transform.rotated(by: CGFloat(M_PI)*surfingProgress) 201 | barItem.transform = barItem.transform.scaledBy(x: surfingProgress, y: surfingProgress) 202 | barItem.alpha = surfingProgress * SurfingConfigs.barDarkAlpha 203 | } 204 | } 205 | } 206 | 207 | func startLoadingAnimation() { 208 | 209 | //NSRunLoopCommonModes make it possible that animating even when user's dragging scrollView 210 | if self.reverseLoadingAnimation == true { 211 | for (index,barItem) in self.barItems.reversed().enumerated() { 212 | self.perform(#selector(SurfingRefreshControl.barItemAnimation(_:)), with: barItem, afterDelay: Double(index) * SurfingConfigs.loadingTimingOffset, inModes: [RunLoopMode.commonModes]) 213 | } 214 | } else { 215 | for (index,barItem) in self.barItems.enumerated() { 216 | self.perform(#selector(SurfingRefreshControl.barItemAnimation(_:)), with: barItem, afterDelay: Double(index) * SurfingConfigs.loadingTimingOffset, inModes: [RunLoopMode.commonModes]) 217 | } 218 | } 219 | } 220 | 221 | func finishLoading() { 222 | self.state = .disappearing 223 | self.disappearProgress = 0 224 | var newInset = self.scrollView.contentInset 225 | newInset.top = self.scrollView.contentInset.top - self.dropHeight 226 | UIView.animate(withDuration: SurfingConfigs.disappearDuration,animations: { 227 | self.scrollView.contentInset = newInset 228 | }, completion: { (finished) in 229 | if finished { 230 | self.state = .idle 231 | self.displayLink?.isPaused = true 232 | self.displayLink?.invalidate() 233 | self.displayLink = nil 234 | self.disappearProgress = 1 235 | } 236 | }) 237 | 238 | self.displayLink = CADisplayLink(target: self, selector: #selector(SurfingRefreshControl.updateDisappearAnimation)) 239 | self.displayLink?.add(to: RunLoop.main, forMode: RunLoopMode.commonModes) 240 | } 241 | 242 | func updateDisappearAnimation() { 243 | if (self.disappearProgress >= 0 && self.disappearProgress <= 1) { 244 | //60.0 means this method get called 60 times per second , 60fps 245 | self.disappearProgress = self.disappearProgress + CGFloat(1/60.0/SurfingConfigs.disappearDuration); 246 | self.updateBarItemsWithProgress(1 - self.disappearProgress) 247 | 248 | } 249 | } 250 | 251 | func barItemAnimation(_ barItem:SurfingBarItem) { 252 | if self.state == .refreshing { 253 | barItem.alpha = 1 254 | barItem.layer.removeAllAnimations() 255 | UIView.animate(withDuration: SurfingConfigs.loadingIndividualAnimationTiming, animations: { 256 | barItem.alpha = SurfingConfigs.barDarkAlpha 257 | }) 258 | 259 | var isLastOne:Bool! 260 | 261 | if self.reverseLoadingAnimation == true { 262 | isLastOne = barItem.tag == 0 263 | } else { 264 | isLastOne = barItem.tag == self.barItems.count-1 265 | } 266 | 267 | //recursive 268 | if isLastOne == true { 269 | self.startLoadingAnimation() 270 | } 271 | } 272 | } 273 | 274 | } 275 | 276 | 277 | -------------------------------------------------------------------------------- /surfingRefreshControlDemo/surfingRefreshControlDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | F441994C1D0BF05F00662A23 /* surfing.jpg in Resources */ = {isa = PBXBuildFile; fileRef = F441994B1D0BF05F00662A23 /* surfing.jpg */; }; 11 | F441997A1D0D7C6600662A23 /* polygon.plist in Resources */ = {isa = PBXBuildFile; fileRef = F44199741D0D7C6600662A23 /* polygon.plist */; }; 12 | F441997B1D0D7C6600662A23 /* square.plist in Resources */ = {isa = PBXBuildFile; fileRef = F44199751D0D7C6600662A23 /* square.plist */; }; 13 | F441997C1D0D7C6600662A23 /* storehouserefresh.plist in Resources */ = {isa = PBXBuildFile; fileRef = F44199761D0D7C6600662A23 /* storehouserefresh.plist */; }; 14 | F441997D1D0D7C6600662A23 /* surfing.plist in Resources */ = {isa = PBXBuildFile; fileRef = F44199771D0D7C6600662A23 /* surfing.plist */; }; 15 | F441997E1D0D7C6600662A23 /* SurfingBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = F44199781D0D7C6600662A23 /* SurfingBarItem.swift */; }; 16 | F441997F1D0D7C6600662A23 /* SurfingRefreshControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = F44199791D0D7C6600662A23 /* SurfingRefreshControl.swift */; }; 17 | F4EEEF371D0A3D5C00FA3BB4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4EEEF361D0A3D5C00FA3BB4 /* AppDelegate.swift */; }; 18 | F4EEEF3E1D0A3D5D00FA3BB4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F4EEEF3D1D0A3D5D00FA3BB4 /* Assets.xcassets */; }; 19 | F4EEEF411D0A3D5D00FA3BB4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F4EEEF3F1D0A3D5D00FA3BB4 /* LaunchScreen.storyboard */; }; 20 | F4EEEF571D0A3EB200FA3BB4 /* DemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4EEEF561D0A3EB200FA3BB4 /* DemoViewController.swift */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXFileReference section */ 24 | F441994B1D0BF05F00662A23 /* surfing.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = surfing.jpg; sourceTree = ""; }; 25 | F44199741D0D7C6600662A23 /* polygon.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = polygon.plist; sourceTree = ""; }; 26 | F44199751D0D7C6600662A23 /* square.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = square.plist; sourceTree = ""; }; 27 | F44199761D0D7C6600662A23 /* storehouserefresh.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = storehouserefresh.plist; sourceTree = ""; }; 28 | F44199771D0D7C6600662A23 /* surfing.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = surfing.plist; sourceTree = ""; }; 29 | F44199781D0D7C6600662A23 /* SurfingBarItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SurfingBarItem.swift; sourceTree = ""; }; 30 | F44199791D0D7C6600662A23 /* SurfingRefreshControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SurfingRefreshControl.swift; sourceTree = ""; }; 31 | F4EEEF331D0A3D5C00FA3BB4 /* surfingRefreshControlDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = surfingRefreshControlDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | F4EEEF361D0A3D5C00FA3BB4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33 | F4EEEF3D1D0A3D5D00FA3BB4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 34 | F4EEEF401D0A3D5D00FA3BB4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 35 | F4EEEF421D0A3D5D00FA3BB4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 36 | F4EEEF561D0A3EB200FA3BB4 /* DemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DemoViewController.swift; sourceTree = ""; }; 37 | /* End PBXFileReference section */ 38 | 39 | /* Begin PBXFrameworksBuildPhase section */ 40 | F4EEEF301D0A3D5C00FA3BB4 /* Frameworks */ = { 41 | isa = PBXFrameworksBuildPhase; 42 | buildActionMask = 2147483647; 43 | files = ( 44 | ); 45 | runOnlyForDeploymentPostprocessing = 0; 46 | }; 47 | /* End PBXFrameworksBuildPhase section */ 48 | 49 | /* Begin PBXGroup section */ 50 | F44199721D0D7C6600662A23 /* SurfingRefreshControl */ = { 51 | isa = PBXGroup; 52 | children = ( 53 | F44199731D0D7C6600662A23 /* Plists */, 54 | F44199781D0D7C6600662A23 /* SurfingBarItem.swift */, 55 | F44199791D0D7C6600662A23 /* SurfingRefreshControl.swift */, 56 | ); 57 | path = SurfingRefreshControl; 58 | sourceTree = ""; 59 | }; 60 | F44199731D0D7C6600662A23 /* Plists */ = { 61 | isa = PBXGroup; 62 | children = ( 63 | F44199741D0D7C6600662A23 /* polygon.plist */, 64 | F44199751D0D7C6600662A23 /* square.plist */, 65 | F44199761D0D7C6600662A23 /* storehouserefresh.plist */, 66 | F44199771D0D7C6600662A23 /* surfing.plist */, 67 | ); 68 | path = Plists; 69 | sourceTree = ""; 70 | }; 71 | F4EEEF2A1D0A3D5C00FA3BB4 = { 72 | isa = PBXGroup; 73 | children = ( 74 | F4EEEF351D0A3D5C00FA3BB4 /* surfingRefreshControlDemo */, 75 | F4EEEF341D0A3D5C00FA3BB4 /* Products */, 76 | ); 77 | sourceTree = ""; 78 | }; 79 | F4EEEF341D0A3D5C00FA3BB4 /* Products */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | F4EEEF331D0A3D5C00FA3BB4 /* surfingRefreshControlDemo.app */, 83 | ); 84 | name = Products; 85 | sourceTree = ""; 86 | }; 87 | F4EEEF351D0A3D5C00FA3BB4 /* surfingRefreshControlDemo */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | F44199721D0D7C6600662A23 /* SurfingRefreshControl */, 91 | F4EEEF361D0A3D5C00FA3BB4 /* AppDelegate.swift */, 92 | F4EEEF561D0A3EB200FA3BB4 /* DemoViewController.swift */, 93 | F441994B1D0BF05F00662A23 /* surfing.jpg */, 94 | F4EEEF3D1D0A3D5D00FA3BB4 /* Assets.xcassets */, 95 | F4EEEF3F1D0A3D5D00FA3BB4 /* LaunchScreen.storyboard */, 96 | F4EEEF421D0A3D5D00FA3BB4 /* Info.plist */, 97 | ); 98 | path = surfingRefreshControlDemo; 99 | sourceTree = ""; 100 | }; 101 | /* End PBXGroup section */ 102 | 103 | /* Begin PBXNativeTarget section */ 104 | F4EEEF321D0A3D5C00FA3BB4 /* surfingRefreshControlDemo */ = { 105 | isa = PBXNativeTarget; 106 | buildConfigurationList = F4EEEF451D0A3D5D00FA3BB4 /* Build configuration list for PBXNativeTarget "surfingRefreshControlDemo" */; 107 | buildPhases = ( 108 | F4EEEF2F1D0A3D5C00FA3BB4 /* Sources */, 109 | F4EEEF301D0A3D5C00FA3BB4 /* Frameworks */, 110 | F4EEEF311D0A3D5C00FA3BB4 /* Resources */, 111 | ); 112 | buildRules = ( 113 | ); 114 | dependencies = ( 115 | ); 116 | name = surfingRefreshControlDemo; 117 | productName = surfingRefreshControlDemo; 118 | productReference = F4EEEF331D0A3D5C00FA3BB4 /* surfingRefreshControlDemo.app */; 119 | productType = "com.apple.product-type.application"; 120 | }; 121 | /* End PBXNativeTarget section */ 122 | 123 | /* Begin PBXProject section */ 124 | F4EEEF2B1D0A3D5C00FA3BB4 /* Project object */ = { 125 | isa = PBXProject; 126 | attributes = { 127 | LastSwiftUpdateCheck = 0730; 128 | LastUpgradeCheck = 0800; 129 | TargetAttributes = { 130 | F4EEEF321D0A3D5C00FA3BB4 = { 131 | CreatedOnToolsVersion = 7.3.1; 132 | LastSwiftMigration = 0800; 133 | }; 134 | }; 135 | }; 136 | buildConfigurationList = F4EEEF2E1D0A3D5C00FA3BB4 /* Build configuration list for PBXProject "surfingRefreshControlDemo" */; 137 | compatibilityVersion = "Xcode 3.2"; 138 | developmentRegion = English; 139 | hasScannedForEncodings = 0; 140 | knownRegions = ( 141 | en, 142 | Base, 143 | ); 144 | mainGroup = F4EEEF2A1D0A3D5C00FA3BB4; 145 | productRefGroup = F4EEEF341D0A3D5C00FA3BB4 /* Products */; 146 | projectDirPath = ""; 147 | projectRoot = ""; 148 | targets = ( 149 | F4EEEF321D0A3D5C00FA3BB4 /* surfingRefreshControlDemo */, 150 | ); 151 | }; 152 | /* End PBXProject section */ 153 | 154 | /* Begin PBXResourcesBuildPhase section */ 155 | F4EEEF311D0A3D5C00FA3BB4 /* Resources */ = { 156 | isa = PBXResourcesBuildPhase; 157 | buildActionMask = 2147483647; 158 | files = ( 159 | F441997D1D0D7C6600662A23 /* surfing.plist in Resources */, 160 | F441994C1D0BF05F00662A23 /* surfing.jpg in Resources */, 161 | F441997C1D0D7C6600662A23 /* storehouserefresh.plist in Resources */, 162 | F441997B1D0D7C6600662A23 /* square.plist in Resources */, 163 | F4EEEF411D0A3D5D00FA3BB4 /* LaunchScreen.storyboard in Resources */, 164 | F4EEEF3E1D0A3D5D00FA3BB4 /* Assets.xcassets in Resources */, 165 | F441997A1D0D7C6600662A23 /* polygon.plist in Resources */, 166 | ); 167 | runOnlyForDeploymentPostprocessing = 0; 168 | }; 169 | /* End PBXResourcesBuildPhase section */ 170 | 171 | /* Begin PBXSourcesBuildPhase section */ 172 | F4EEEF2F1D0A3D5C00FA3BB4 /* Sources */ = { 173 | isa = PBXSourcesBuildPhase; 174 | buildActionMask = 2147483647; 175 | files = ( 176 | F441997F1D0D7C6600662A23 /* SurfingRefreshControl.swift in Sources */, 177 | F4EEEF571D0A3EB200FA3BB4 /* DemoViewController.swift in Sources */, 178 | F4EEEF371D0A3D5C00FA3BB4 /* AppDelegate.swift in Sources */, 179 | F441997E1D0D7C6600662A23 /* SurfingBarItem.swift in Sources */, 180 | ); 181 | runOnlyForDeploymentPostprocessing = 0; 182 | }; 183 | /* End PBXSourcesBuildPhase section */ 184 | 185 | /* Begin PBXVariantGroup section */ 186 | F4EEEF3F1D0A3D5D00FA3BB4 /* LaunchScreen.storyboard */ = { 187 | isa = PBXVariantGroup; 188 | children = ( 189 | F4EEEF401D0A3D5D00FA3BB4 /* Base */, 190 | ); 191 | name = LaunchScreen.storyboard; 192 | sourceTree = ""; 193 | }; 194 | /* End PBXVariantGroup section */ 195 | 196 | /* Begin XCBuildConfiguration section */ 197 | F4EEEF431D0A3D5D00FA3BB4 /* Debug */ = { 198 | isa = XCBuildConfiguration; 199 | buildSettings = { 200 | ALWAYS_SEARCH_USER_PATHS = NO; 201 | CLANG_ANALYZER_NONNULL = YES; 202 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 203 | CLANG_CXX_LIBRARY = "libc++"; 204 | CLANG_ENABLE_MODULES = YES; 205 | CLANG_ENABLE_OBJC_ARC = YES; 206 | CLANG_WARN_BOOL_CONVERSION = YES; 207 | CLANG_WARN_CONSTANT_CONVERSION = YES; 208 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 209 | CLANG_WARN_EMPTY_BODY = YES; 210 | CLANG_WARN_ENUM_CONVERSION = YES; 211 | CLANG_WARN_INFINITE_RECURSION = YES; 212 | CLANG_WARN_INT_CONVERSION = YES; 213 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 214 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 215 | CLANG_WARN_UNREACHABLE_CODE = YES; 216 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 217 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 218 | COPY_PHASE_STRIP = NO; 219 | DEBUG_INFORMATION_FORMAT = dwarf; 220 | ENABLE_STRICT_OBJC_MSGSEND = YES; 221 | ENABLE_TESTABILITY = YES; 222 | GCC_C_LANGUAGE_STANDARD = gnu99; 223 | GCC_DYNAMIC_NO_PIC = NO; 224 | GCC_NO_COMMON_BLOCKS = YES; 225 | GCC_OPTIMIZATION_LEVEL = 0; 226 | GCC_PREPROCESSOR_DEFINITIONS = ( 227 | "DEBUG=1", 228 | "$(inherited)", 229 | ); 230 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 231 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 232 | GCC_WARN_UNDECLARED_SELECTOR = YES; 233 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 234 | GCC_WARN_UNUSED_FUNCTION = YES; 235 | GCC_WARN_UNUSED_VARIABLE = YES; 236 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 237 | MTL_ENABLE_DEBUG_INFO = YES; 238 | ONLY_ACTIVE_ARCH = YES; 239 | SDKROOT = iphoneos; 240 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 241 | }; 242 | name = Debug; 243 | }; 244 | F4EEEF441D0A3D5D00FA3BB4 /* Release */ = { 245 | isa = XCBuildConfiguration; 246 | buildSettings = { 247 | ALWAYS_SEARCH_USER_PATHS = NO; 248 | CLANG_ANALYZER_NONNULL = YES; 249 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 250 | CLANG_CXX_LIBRARY = "libc++"; 251 | CLANG_ENABLE_MODULES = YES; 252 | CLANG_ENABLE_OBJC_ARC = YES; 253 | CLANG_WARN_BOOL_CONVERSION = YES; 254 | CLANG_WARN_CONSTANT_CONVERSION = YES; 255 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 256 | CLANG_WARN_EMPTY_BODY = YES; 257 | CLANG_WARN_ENUM_CONVERSION = YES; 258 | CLANG_WARN_INFINITE_RECURSION = YES; 259 | CLANG_WARN_INT_CONVERSION = YES; 260 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 261 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 262 | CLANG_WARN_UNREACHABLE_CODE = YES; 263 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 264 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 265 | COPY_PHASE_STRIP = NO; 266 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 267 | ENABLE_NS_ASSERTIONS = NO; 268 | ENABLE_STRICT_OBJC_MSGSEND = YES; 269 | GCC_C_LANGUAGE_STANDARD = gnu99; 270 | GCC_NO_COMMON_BLOCKS = YES; 271 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 272 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 273 | GCC_WARN_UNDECLARED_SELECTOR = YES; 274 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 275 | GCC_WARN_UNUSED_FUNCTION = YES; 276 | GCC_WARN_UNUSED_VARIABLE = YES; 277 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 278 | MTL_ENABLE_DEBUG_INFO = NO; 279 | SDKROOT = iphoneos; 280 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 281 | VALIDATE_PRODUCT = YES; 282 | }; 283 | name = Release; 284 | }; 285 | F4EEEF461D0A3D5D00FA3BB4 /* Debug */ = { 286 | isa = XCBuildConfiguration; 287 | buildSettings = { 288 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 289 | CLANG_ENABLE_MODULES = YES; 290 | INFOPLIST_FILE = surfingRefreshControlDemo/Info.plist; 291 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 292 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 293 | PRODUCT_BUNDLE_IDENTIFIER = peiwei.surfingRefreshControlDemo; 294 | PRODUCT_NAME = "$(TARGET_NAME)"; 295 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 296 | SWIFT_VERSION = 3.0; 297 | TARGETED_DEVICE_FAMILY = "1,2"; 298 | }; 299 | name = Debug; 300 | }; 301 | F4EEEF471D0A3D5D00FA3BB4 /* Release */ = { 302 | isa = XCBuildConfiguration; 303 | buildSettings = { 304 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 305 | CLANG_ENABLE_MODULES = YES; 306 | INFOPLIST_FILE = surfingRefreshControlDemo/Info.plist; 307 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 308 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 309 | PRODUCT_BUNDLE_IDENTIFIER = peiwei.surfingRefreshControlDemo; 310 | PRODUCT_NAME = "$(TARGET_NAME)"; 311 | SWIFT_VERSION = 3.0; 312 | TARGETED_DEVICE_FAMILY = "1,2"; 313 | }; 314 | name = Release; 315 | }; 316 | /* End XCBuildConfiguration section */ 317 | 318 | /* Begin XCConfigurationList section */ 319 | F4EEEF2E1D0A3D5C00FA3BB4 /* Build configuration list for PBXProject "surfingRefreshControlDemo" */ = { 320 | isa = XCConfigurationList; 321 | buildConfigurations = ( 322 | F4EEEF431D0A3D5D00FA3BB4 /* Debug */, 323 | F4EEEF441D0A3D5D00FA3BB4 /* Release */, 324 | ); 325 | defaultConfigurationIsVisible = 0; 326 | defaultConfigurationName = Release; 327 | }; 328 | F4EEEF451D0A3D5D00FA3BB4 /* Build configuration list for PBXNativeTarget "surfingRefreshControlDemo" */ = { 329 | isa = XCConfigurationList; 330 | buildConfigurations = ( 331 | F4EEEF461D0A3D5D00FA3BB4 /* Debug */, 332 | F4EEEF471D0A3D5D00FA3BB4 /* Release */, 333 | ); 334 | defaultConfigurationIsVisible = 0; 335 | defaultConfigurationName = Release; 336 | }; 337 | /* End XCConfigurationList section */ 338 | }; 339 | rootObject = F4EEEF2B1D0A3D5C00FA3BB4 /* Project object */; 340 | } 341 | --------------------------------------------------------------------------------