├── .gitignore ├── .swift-version ├── .travis.yml ├── ESPullToRefresh.podspec ├── ESPullToRefreshExample ├── ESPullToRefresh │ ├── ESPullToRefresh.h │ └── Info.plist ├── ESPullToRefreshExample.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── ESPullToRefresh.xcscheme └── ESPullToRefreshExample │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-60@1x-1.png │ │ ├── Icon-60@2x.png │ │ ├── Icon-60@3x.png │ │ ├── Icon-76.png │ │ ├── Icon-76@2x.png │ │ ├── Icon-Notification.png │ │ ├── Icon-Notification@2x-1.png │ │ ├── Icon-Notification@2x.png │ │ ├── Icon-Small.png │ │ ├── Icon-Small@2x.png │ │ ├── Icon-Small@3x.png │ │ ├── Icon-Spotlight-40.png │ │ ├── Icon-Spotlight-40@2x.png │ │ ├── Icon-Spotlight-40@3x.png │ │ ├── icon1024.png │ │ └── icon@2x.png │ ├── Contents.json │ ├── icon.imageset │ │ ├── Contents.json │ │ └── icon@2x.png │ ├── icon_wechat.imageset │ │ ├── Contents.json │ │ └── icon_wechat@2x.png │ ├── meituan │ │ ├── Contents.json │ │ ├── icon_pull_animation_1.imageset │ │ │ ├── Contents.json │ │ │ └── icon_pull_animation_1.png │ │ ├── icon_pull_animation_2.imageset │ │ │ ├── Contents.json │ │ │ └── icon_pull_animation_2.png │ │ ├── icon_pull_animation_3.imageset │ │ │ ├── Contents.json │ │ │ └── icon_pull_animation_3.png │ │ ├── icon_pull_animation_4.imageset │ │ │ ├── Contents.json │ │ │ └── icon_pull_animation_4.png │ │ ├── icon_pull_animation_5.imageset │ │ │ ├── Contents.json │ │ │ └── icon_pull_animation_5.png │ │ ├── icon_shake_animation_1.imageset │ │ │ ├── Contents.json │ │ │ └── icon_shake_animation_1.png │ │ ├── icon_shake_animation_2.imageset │ │ │ ├── Contents.json │ │ │ └── icon_shake_animation_2.png │ │ ├── icon_shake_animation_3.imageset │ │ │ ├── Contents.json │ │ │ └── icon_shake_animation_3.png │ │ ├── icon_shake_animation_4.imageset │ │ │ ├── Contents.json │ │ │ └── icon_shake_animation_4.png │ │ ├── icon_shake_animation_5.imageset │ │ │ ├── Contents.json │ │ │ └── icon_shake_animation_5.png │ │ ├── icon_shake_animation_6.imageset │ │ │ ├── Contents.json │ │ │ └── icon_shake_animation_6.png │ │ ├── icon_shake_animation_7.imageset │ │ │ ├── Contents.json │ │ │ └── icon_shake_animation_7.png │ │ └── icon_shake_animation_8.imageset │ │ │ ├── Contents.json │ │ │ └── icon_shake_animation_8.png │ ├── network.imageset │ │ ├── Contents.json │ │ └── network@2x.png │ └── photo │ │ ├── Contents.json │ │ └── lofter │ │ ├── Contents.json │ │ ├── Photo_Lofter_1.imageset │ │ ├── Contents.json │ │ └── Photo_Lofter_1@2x.jpg │ │ ├── Photo_Lofter_2.imageset │ │ ├── Contents.json │ │ └── Photo_Lofter_2@2x.jpg │ │ ├── Photo_Lofter_3.imageset │ │ ├── Contents.json │ │ └── Photo_Lofter_3@2x.jpg │ │ ├── Photo_Lofter_4.imageset │ │ ├── Contents.json │ │ └── Photo_Lofter_4@2x.jpg │ │ ├── Photo_Lofter_5.imageset │ │ ├── Contents.json │ │ └── Photo_Lofter_5@2x.jpg │ │ ├── Photo_Lofter_6.imageset │ │ ├── Contents.json │ │ └── Photo_Lofter_6@2x.jpg │ │ ├── Photo_Lofter_7.imageset │ │ ├── Contents.json │ │ └── Photo_Lofter_7@2x.jpg │ │ ├── Photo_Lofter_8.imageset │ │ ├── Contents.json │ │ └── Photo_Lofter_8@2x.jpg │ │ ├── Photo_Lofter_9.imageset │ │ ├── Contents.json │ │ └── Photo_Lofter_9@2x.jpg │ │ ├── Photo_Lofter_Info.imageset │ │ ├── Contents.json │ │ └── Photo_Lofter_Info@2x.png │ │ └── Photo_Lofter_Tab.imageset │ │ ├── Contents.json │ │ └── Photo_Lofter_Tab@2x.png │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Custom │ ├── CollectionView │ │ └── CollectionViewController.swift │ ├── Day │ │ ├── ESRefreshDayHeaderAnimator.swift │ │ └── ESRefreshDayTableViewController.swift │ ├── ESPhotoTableViewCell.swift │ ├── ESPhotoTableViewCell.xib │ ├── ESRefreshTableViewCell.swift │ ├── ESRefreshTableViewCell.xib │ ├── ESRefreshTableViewController.swift │ ├── Meituan │ │ ├── MTRefreshFooterAnimator.swift │ │ ├── MTRefreshHeaderAnimator.swift │ │ └── MeituanTableViewController.swift │ ├── TextView │ │ └── TextViewController.swift │ └── WeChat │ │ ├── WCRefreshHeaderAnimator.swift │ │ ├── WeChatTableHeaderView.swift │ │ └── WeChatTableViewController.swift │ ├── Info.plist │ ├── ListTableViewCell.swift │ ├── ListTableViewCell.xib │ ├── ViewController.swift │ ├── WebViewController.swift │ └── WebViewController.xib ├── LICENSE ├── Package.swift ├── README.md ├── README_CN.md ├── Sources ├── Animator │ ├── Base.lproj │ │ └── Localizable.strings │ ├── ESRefreshFooterAnimator.swift │ ├── ESRefreshHeaderAnimator.swift │ ├── icon_pull_to_refresh_arrow@2x.png │ ├── zh-Hans.lproj │ │ └── Localizable.strings │ └── zh-Hant.lproj │ │ └── Localizable.strings ├── ES.swift ├── ESPullToRefresh+Manager.swift ├── ESPullToRefresh.swift ├── ESRefreshAnimator.swift ├── ESRefreshComponent.swift └── ESRefreshProtocol.swift ├── example_default.gif ├── example_meituan.gif ├── example_wechat.gif └── logo.png /.gitignore: -------------------------------------------------------------------------------- 1 | reated by https://www.gitignore.io/api/xcode,objective-c,swift,osx 2 | 3 | ### Xcode ### 4 | # Xcode 5 | # 6 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 7 | 8 | ## Build generated 9 | build/ 10 | DerivedData/ 11 | 12 | ## Various settings 13 | *.pbxuser 14 | !default.pbxuser 15 | *.mode1v3 16 | !default.mode1v3 17 | *.mode2v3 18 | !default.mode2v3 19 | *.perspectivev3 20 | !default.perspectivev3 21 | xcuserdata/ 22 | 23 | ## Other 24 | *.moved-aside 25 | *.xccheckout 26 | *.xcscmblueprint 27 | 28 | 29 | ### Objective-C ### 30 | # Xcode 31 | # 32 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 33 | 34 | ## Build generated 35 | build/ 36 | DerivedData/ 37 | 38 | ## Various settings 39 | *.pbxuser 40 | !default.pbxuser 41 | *.mode1v3 42 | !default.mode1v3 43 | *.mode2v3 44 | !default.mode2v3 45 | *.perspectivev3 46 | !default.perspectivev3 47 | xcuserdata/ 48 | 49 | ## Other 50 | *.moved-aside 51 | *.xcuserstate 52 | 53 | ## Obj-C/Swift specific 54 | *.hmap 55 | *.ipa 56 | 57 | # CocoaPods 58 | # 59 | # We recommend against adding the Pods directory to your .gitignore. However 60 | # you should judge for yourself, the pros and cons are mentioned at: 61 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 62 | # 63 | # Pods/ 64 | 65 | # Carthage 66 | # 67 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 68 | # Carthage/Checkouts 69 | 70 | Carthage 71 | 72 | # fastlane 73 | # 74 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 75 | # screenshots whenever they are needed. 76 | # For more information about the recommended setup visit: 77 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 78 | 79 | fastlane/report.xml 80 | fastlane/screenshots 81 | 82 | ### Objective-C Patch ### 83 | *.xcscmblueprint 84 | 85 | 86 | ### Swift ### 87 | # Xcode 88 | # 89 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 90 | 91 | ## Build generated 92 | build/ 93 | DerivedData/ 94 | 95 | ## Various settings 96 | *.pbxuser 97 | !default.pbxuser 98 | *.mode1v3 99 | !default.mode1v3 100 | *.mode2v3 101 | !default.mode2v3 102 | *.perspectivev3 103 | !default.perspectivev3 104 | xcuserdata/ 105 | 106 | ## Other 107 | *.moved-aside 108 | *.xcuserstate 109 | 110 | ## Obj-C/Swift specific 111 | *.hmap 112 | *.ipa 113 | 114 | ## Playgrounds 115 | timeline.xctimeline 116 | playground.xcworkspace 117 | 118 | # Swift Package Manager 119 | # 120 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 121 | # Packages/ 122 | .build/ 123 | 124 | # CocoaPods 125 | # 126 | # We recommend against adding the Pods directory to your .gitignore. However 127 | # you should judge for yourself, the pros and cons are mentioned at: 128 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 129 | # 130 | # Pods/ 131 | 132 | # Carthage 133 | # 134 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 135 | # Carthage/Checkouts 136 | 137 | Carthage 138 | 139 | # fastlane 140 | # 141 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 142 | # screenshots whenever they are needed. 143 | # For more information about the recommended setup visit: 144 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 145 | 146 | fastlane/report.xml 147 | fastlane/Preview.html 148 | fastlane/screenshots 149 | fastlane/test_output 150 | 151 | 152 | ### OSX ### 153 | .DS_Store 154 | .AppleDouble 155 | .LSOverride 156 | 157 | # Icon must end with two \r 158 | Icon 159 | 160 | 161 | # Thumbnails 162 | ._* 163 | 164 | # Files that might appear in the root of a volume 165 | .DocumentRevisions-V100 166 | .fseventsd 167 | .Spotlight-V100 168 | .TemporaryItems 169 | .Trashes 170 | .VolumeIcon.icns 171 | 172 | # Directories potentially created on remote AFP share 173 | .AppleDB 174 | .AppleDesktop 175 | Network Trash Folder 176 | Temporary Items 177 | .apdisk 178 | 179 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 5.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | osx_image: xcode11.2 2 | language: swift 3 | xcode_sdk: iphonesimulator13.2 4 | 5 | xcode_project: ESPullToRefreshExample/ESPullToRefreshExample.xcodeproj 6 | xcode_scheme: ESPullToRefreshExample 7 | 8 | # whitelist 9 | branches: 10 | only: 11 | - master -------------------------------------------------------------------------------- /ESPullToRefresh.podspec: -------------------------------------------------------------------------------- 1 | 2 | Pod::Spec.new do |s| 3 | s.name = 'ESPullToRefresh' 4 | s.version = '2.9.3' 5 | s.summary = 'An easy way to use pull-to-refresh and loading-more' 6 | s.description = 'An easiest way to give pull-to-refresh and loading-more to any UIScrollView. Using swift!' 7 | s.homepage = 'https://github.com/eggswift/pull-to-refresh' 8 | 9 | s.license = { :type => 'MIT', :file => 'LICENSE' } 10 | s.authors = { 'lihao' => 'lihao_ios@hotmail.com'} 11 | s.social_media_url = 'https://github.com/eggswift' 12 | s.platform = :ios, '8.0' 13 | s.source = {:git => 'https://github.com/eggswift/pull-to-refresh.git', :tag => s.version} 14 | s.source_files = ['Sources/**/*.{swift}'] 15 | s.resource_bundles = { 'ESPullToRefresh' => 'Sources/Animator/*.png' } 16 | s.requires_arc = true 17 | s.swift_version = '5.0' 18 | end 19 | -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefresh/ESPullToRefresh.h: -------------------------------------------------------------------------------- 1 | // 2 | // ESPullToRefresh.h 3 | // ESPullToRefresh 4 | // 5 | // Created by lihao on 16/6/12. 6 | // Copyright © 2016年 egg swift. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for ESPullToRefresh. 12 | FOUNDATION_EXPORT double ESPullToRefreshVersionNumber; 13 | 14 | //! Project version string for ESPullToRefresh. 15 | FOUNDATION_EXPORT const unsigned char ESPullToRefreshVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefresh/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 | 2.9 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample.xcodeproj/xcshareddata/xcschemes/ESPullToRefresh.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // ESPullToRefreshExample 4 | // 5 | // Created by egg swift on 16/5/5. 6 | // Copyright © 2016年 egg swift. All rights reserved. 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: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | 46 | } 47 | 48 | -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-Notification@2x-1.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-60@1x-1.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-Small@2x.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-Small@3x.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "40x40", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-Spotlight-40@2x.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-Spotlight-40@3x.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "60x60", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-60@2x.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-60@3x.png", 49 | "scale" : "3x" 50 | }, 51 | { 52 | "size" : "20x20", 53 | "idiom" : "ipad", 54 | "filename" : "Icon-Notification.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-Notification@2x.png", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "size" : "29x29", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-Small.png", 67 | "scale" : "1x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-Small@2x.png", 73 | "scale" : "2x" 74 | }, 75 | { 76 | "size" : "40x40", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-Spotlight-40.png", 79 | "scale" : "1x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-Spotlight-40@2x.png", 85 | "scale" : "2x" 86 | }, 87 | { 88 | "size" : "76x76", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-76.png", 91 | "scale" : "1x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-76@2x.png", 97 | "scale" : "2x" 98 | }, 99 | { 100 | "size" : "83.5x83.5", 101 | "idiom" : "ipad", 102 | "filename" : "icon@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "1024x1024", 107 | "idiom" : "ios-marketing", 108 | "filename" : "icon1024.png", 109 | "scale" : "1x" 110 | } 111 | ], 112 | "info" : { 113 | "version" : 1, 114 | "author" : "xcode" 115 | } 116 | } -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/AppIcon.appiconset/Icon-60@1x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/AppIcon.appiconset/Icon-60@1x-1.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/AppIcon.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/AppIcon.appiconset/Icon-76.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/AppIcon.appiconset/Icon-Notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/AppIcon.appiconset/Icon-Notification.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/AppIcon.appiconset/Icon-Notification@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/AppIcon.appiconset/Icon-Notification@2x-1.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/AppIcon.appiconset/Icon-Notification@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/AppIcon.appiconset/Icon-Notification@2x.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/AppIcon.appiconset/Icon-Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/AppIcon.appiconset/Icon-Small.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/AppIcon.appiconset/Icon-Spotlight-40@3x.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/AppIcon.appiconset/icon1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/AppIcon.appiconset/icon1024.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/AppIcon.appiconset/icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/AppIcon.appiconset/icon@2x.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "icon@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/icon.imageset/icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/icon.imageset/icon@2x.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/icon_wechat.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "icon_wechat@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/icon_wechat.imageset/icon_wechat@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/icon_wechat.imageset/icon_wechat@2x.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_pull_animation_1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon_pull_animation_1.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_pull_animation_1.imageset/icon_pull_animation_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_pull_animation_1.imageset/icon_pull_animation_1.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_pull_animation_2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon_pull_animation_2.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_pull_animation_2.imageset/icon_pull_animation_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_pull_animation_2.imageset/icon_pull_animation_2.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_pull_animation_3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon_pull_animation_3.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_pull_animation_3.imageset/icon_pull_animation_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_pull_animation_3.imageset/icon_pull_animation_3.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_pull_animation_4.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon_pull_animation_4.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_pull_animation_4.imageset/icon_pull_animation_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_pull_animation_4.imageset/icon_pull_animation_4.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_pull_animation_5.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon_pull_animation_5.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_pull_animation_5.imageset/icon_pull_animation_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_pull_animation_5.imageset/icon_pull_animation_5.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_shake_animation_1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon_shake_animation_1.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_shake_animation_1.imageset/icon_shake_animation_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_shake_animation_1.imageset/icon_shake_animation_1.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_shake_animation_2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon_shake_animation_2.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_shake_animation_2.imageset/icon_shake_animation_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_shake_animation_2.imageset/icon_shake_animation_2.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_shake_animation_3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon_shake_animation_3.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_shake_animation_3.imageset/icon_shake_animation_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_shake_animation_3.imageset/icon_shake_animation_3.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_shake_animation_4.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon_shake_animation_4.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_shake_animation_4.imageset/icon_shake_animation_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_shake_animation_4.imageset/icon_shake_animation_4.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_shake_animation_5.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon_shake_animation_5.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_shake_animation_5.imageset/icon_shake_animation_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_shake_animation_5.imageset/icon_shake_animation_5.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_shake_animation_6.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon_shake_animation_6.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_shake_animation_6.imageset/icon_shake_animation_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_shake_animation_6.imageset/icon_shake_animation_6.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_shake_animation_7.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon_shake_animation_7.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_shake_animation_7.imageset/icon_shake_animation_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_shake_animation_7.imageset/icon_shake_animation_7.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_shake_animation_8.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon_shake_animation_8.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_shake_animation_8.imageset/icon_shake_animation_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/meituan/icon_shake_animation_8.imageset/icon_shake_animation_8.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/network.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "network@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/network.imageset/network@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/network.imageset/network@2x.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Photo_Lofter_1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "Photo_Lofter_1@2x.jpg", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Photo_Lofter_1.imageset/Photo_Lofter_1@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Photo_Lofter_1.imageset/Photo_Lofter_1@2x.jpg -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Photo_Lofter_2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "Photo_Lofter_2@2x.jpg", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Photo_Lofter_2.imageset/Photo_Lofter_2@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Photo_Lofter_2.imageset/Photo_Lofter_2@2x.jpg -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Photo_Lofter_3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "Photo_Lofter_3@2x.jpg", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Photo_Lofter_3.imageset/Photo_Lofter_3@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Photo_Lofter_3.imageset/Photo_Lofter_3@2x.jpg -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Photo_Lofter_4.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "Photo_Lofter_4@2x.jpg", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Photo_Lofter_4.imageset/Photo_Lofter_4@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Photo_Lofter_4.imageset/Photo_Lofter_4@2x.jpg -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Photo_Lofter_5.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "Photo_Lofter_5@2x.jpg", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Photo_Lofter_5.imageset/Photo_Lofter_5@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Photo_Lofter_5.imageset/Photo_Lofter_5@2x.jpg -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Photo_Lofter_6.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "Photo_Lofter_6@2x.jpg", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Photo_Lofter_6.imageset/Photo_Lofter_6@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Photo_Lofter_6.imageset/Photo_Lofter_6@2x.jpg -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Photo_Lofter_7.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "Photo_Lofter_7@2x.jpg", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Photo_Lofter_7.imageset/Photo_Lofter_7@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Photo_Lofter_7.imageset/Photo_Lofter_7@2x.jpg -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Photo_Lofter_8.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "Photo_Lofter_8@2x.jpg", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Photo_Lofter_8.imageset/Photo_Lofter_8@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Photo_Lofter_8.imageset/Photo_Lofter_8@2x.jpg -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Photo_Lofter_9.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "Photo_Lofter_9@2x.jpg", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Photo_Lofter_9.imageset/Photo_Lofter_9@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Photo_Lofter_9.imageset/Photo_Lofter_9@2x.jpg -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Photo_Lofter_Info.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "Photo_Lofter_Info@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Photo_Lofter_Info.imageset/Photo_Lofter_Info@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Photo_Lofter_Info.imageset/Photo_Lofter_Info@2x.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Photo_Lofter_Tab.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "Photo_Lofter_Tab@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Photo_Lofter_Tab.imageset/Photo_Lofter_Tab@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/ESPullToRefreshExample/ESPullToRefreshExample/Assets.xcassets/photo/lofter/Photo_Lofter_Tab.imageset/Photo_Lofter_Tab@2x.png -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/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 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Custom/CollectionView/CollectionViewController.swift: -------------------------------------------------------------------------------- 1 | 2 | // File.swift 3 | // ESPullToRefreshExample 4 | // 5 | // Created by Meng Ye on 2016/11/2. 6 | // Copyright © 2016年 egg swift. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CollectionViewController: UIViewController { 12 | internal var list: [String] = [] 13 | 14 | private let collectionView: UICollectionView = { 15 | let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) 16 | collectionView.translatesAutoresizingMaskIntoConstraints = false 17 | collectionView.alwaysBounceVertical = true 18 | collectionView.register(DemoCollectionViewCell.self, forCellWithReuseIdentifier: "DemoCell") 19 | collectionView.contentInset = UIEdgeInsets.init(top: 12.0, left: 15.0, bottom: 0, right: 15.0) 20 | collectionView.backgroundColor = .white 21 | return collectionView 22 | }() 23 | 24 | override func viewDidLoad() { 25 | super.viewDidLoad() 26 | 27 | setupViews() 28 | } 29 | 30 | // MARK: Private Helper Methods 31 | private func setupViews() { 32 | self.collectionView.dataSource = self 33 | 34 | let layout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout 35 | self.view.addSubview(collectionView) 36 | let itemWidth = (UIScreen.main.bounds.width - 40) / 3 37 | layout.itemSize = CGSize(width: itemWidth, height: itemWidth) 38 | layout.minimumLineSpacing = 5.0 39 | layout.minimumInteritemSpacing = 5.0 40 | layout.scrollDirection = .vertical 41 | 42 | let viewsDict = [ 43 | "collectionView": collectionView 44 | ] 45 | let vflDict = [ 46 | "H:|-0-[collectionView]-0-|", 47 | "V:|-0-[collectionView]-0-|" 48 | ] 49 | self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: vflDict[0] as String, options: [], metrics: nil, views: viewsDict)) 50 | self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: vflDict[1] as String, options: [], metrics: nil, views: viewsDict)) 51 | 52 | // add pull to refresh 53 | self.collectionView.es.addPullToRefresh { 54 | self.loadData() 55 | } 56 | self.collectionView.es.startPullToRefresh() 57 | self.collectionView.es.addInfiniteScrolling { 58 | self.delay(time: 2) { 59 | for item in 21...40 { 60 | self.list.append(String(item)) 61 | } 62 | self.collectionView.es.stopLoadingMore() 63 | self.collectionView.reloadData() 64 | } 65 | } 66 | } 67 | 68 | func delay(time: TimeInterval, completionHandler: @escaping ()-> Void) { 69 | DispatchQueue.main.asyncAfter(deadline: .now() + time) { 70 | completionHandler() 71 | } 72 | } 73 | 74 | private func loadData() { 75 | delay(time: 2) { 76 | self.list = [] 77 | for item in 0...20 { 78 | self.list.append(String(item)) 79 | } 80 | self.collectionView.es.stopPullToRefresh() 81 | self.collectionView.reloadData() 82 | } 83 | } 84 | } 85 | 86 | // MARK: UICollectionViewDataSource 87 | extension CollectionViewController: UICollectionViewDataSource { 88 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 89 | return self.list.count 90 | } 91 | 92 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 93 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "DemoCell", for: indexPath) as! DemoCollectionViewCell 94 | cell.configureCell(title: self.list[indexPath.row]) 95 | 96 | return cell 97 | } 98 | } 99 | 100 | // MARK: DemoCollectionViewCell 101 | class DemoCollectionViewCell: UICollectionViewCell { 102 | lazy var label: UILabel = { 103 | let label = UILabel() 104 | label.font = UIFont(name: "HelveticaNeue-Bold", size: 15) 105 | label.translatesAutoresizingMaskIntoConstraints = false 106 | label.textAlignment = .center 107 | label.textColor = .black 108 | return label 109 | }() 110 | 111 | override init(frame: CGRect) { 112 | super.init(frame: frame) 113 | 114 | let viewsDict = ["label" : label] 115 | let vflDict = ["H:|-0-[label]-0-|", 116 | "V:|-0-[label]-0-|"] 117 | contentView.addSubview(label) 118 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: vflDict[0] as String, options: [], metrics: nil, views: viewsDict)) 119 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: vflDict[1] as String, options: [], metrics: nil, views: viewsDict)) 120 | } 121 | 122 | required init?(coder aDecoder: NSCoder) { 123 | fatalError("init(coder:) has not been implemented") 124 | } 125 | 126 | func configureCell(title: String) { 127 | label.text = title 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Custom/Day/ESRefreshDayHeaderAnimator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ESRefreshDayHeaderAnimator.swift 3 | // ESPullToRefreshExample 4 | // 5 | // Created by lihao on 16/7/18. 6 | // Copyright © 2016年 egg swift. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public class ESRefreshDayHeaderAnimator: UIView, ESRefreshProtocol, ESRefreshAnimatorProtocol { 12 | public var insets: UIEdgeInsets = UIEdgeInsets.zero 13 | public var view: UIView { return self } 14 | public var trigger: CGFloat = 120.0 15 | public var executeIncremental: CGFloat = 120.0 16 | public var state: ESRefreshViewState = .pullToRefresh 17 | 18 | private var percent: CGFloat = 0 19 | private var isDay: Bool = true { 20 | didSet { 21 | self.dayImageView.isHidden = !isDay 22 | self.nightImageView.isHidden = isDay 23 | } 24 | } 25 | private lazy var timer: Timer! = { 26 | let timer = Timer.scheduledTimer(timeInterval: 0.002, target: self, selector: #selector(ESRefreshDayHeaderAnimator.timerAction), userInfo: nil, repeats: true) 27 | return timer 28 | }() 29 | 30 | private let backImageView: UIImageView = { 31 | let imageView = UIImageView.init() 32 | imageView.image = UIImage.init(named: "icon_pull_to_refresh_back") 33 | imageView.contentMode = .scaleAspectFill 34 | imageView.clipsToBounds = true 35 | return imageView 36 | }() 37 | private let dayImageView: UIImageView = { 38 | let imageView = UIImageView.init() 39 | imageView.image = UIImage.init(named: "icon_pull_to_refresh_day") 40 | imageView.sizeToFit() 41 | return imageView 42 | }() 43 | private let nightImageView: UIImageView = { 44 | let imageView = UIImageView.init() 45 | imageView.image = UIImage.init(named: "icon_pull_to_refresh_night") 46 | imageView.sizeToFit() 47 | imageView.isHidden = true 48 | return imageView 49 | }() 50 | 51 | override init(frame: CGRect) { 52 | super.init(frame: frame) 53 | self.addSubview(backImageView) 54 | self.addSubview(dayImageView) 55 | self.addSubview(nightImageView) 56 | } 57 | 58 | public required init(coder aDecoder: NSCoder) { 59 | fatalError("init(coder:) has not been implemented") 60 | } 61 | 62 | public func refreshAnimationBegin(view: ESRefreshComponent) { 63 | self.timer.fire() 64 | percent = 0.5 65 | } 66 | 67 | public func refreshAnimationEnd(view: ESRefreshComponent) { 68 | self.timer.invalidate() 69 | self.timer = nil 70 | isDay = true 71 | percent = 0.5 72 | } 73 | 74 | public func refresh(view: ESRefreshComponent, progressDidChange progress: CGFloat) { 75 | let buttom: CGFloat = 2.0 76 | let top: CGFloat = 12.0 77 | let p = min(1.0, max(0.0, progress)) 78 | let w = self.bounds.size.width 79 | let h = self.bounds.size.height 80 | let size = dayImageView.image?.size ?? CGSize.zero 81 | self.dayImageView.center = CGPoint.init(x: w / 2.0, y: (h - buttom - size.height / 2.0) - (h - top - buttom - size.height) * p) 82 | } 83 | 84 | public func refresh(view: ESRefreshComponent, stateDidChange state: ESRefreshViewState) { 85 | 86 | } 87 | 88 | @objc public func timerAction() { 89 | percent += 0.001 90 | if percent >= 1.0 { 91 | percent = 0.0 92 | isDay = !isDay 93 | } 94 | self.animateAction() 95 | } 96 | 97 | public func animateAction() { 98 | let p = percent < 0.25 ? percent * 2 : percent > 0.75 ? (0.5 + (percent - 0.75) * 2) : 0.5 99 | 100 | let top: CGFloat = 12.0 101 | let buttom: CGFloat = 16.0 102 | let size = dayImageView.image?.size ?? CGSize.zero 103 | let w = self.bounds.size.width 104 | let h = self.bounds.size.height 105 | let x = p * w 106 | let y = top + (size.height / 2.0) + (h - top - buttom - size.height) * (1 - CGFloat(sin(CGFloat(Double.pi) * p))) 107 | if isDay { 108 | let colorP = p < 0.5 ? (0.5 + p) : (p == 0.5 ? 1.0 : (0.5 + 1.0 - p)) 109 | self.backgroundColor = UIColor.init(white: colorP, alpha: 1.0) 110 | self.dayImageView.center = CGPoint.init(x: x, y: y) 111 | } else { 112 | let colorP = p < 0.5 ? (0.5 - p) : (p == 0.5 ? 0.0 : (p - 0.5)) 113 | self.backgroundColor = UIColor.init(white: colorP, alpha: 1.0) 114 | self.nightImageView.center = CGPoint.init(x: x, y: y) 115 | } 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Custom/Day/ESRefreshDayTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ESRefreshDayTableViewController.swift 3 | // ESPullToRefreshExample 4 | // 5 | // Created by lihao on 16/7/18. 6 | // Copyright © 2016年 egg swift. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public class ESRefreshDayTableViewController: UITableViewController { 12 | var array = [String]() 13 | var page = 1 14 | 15 | override public func viewDidLoad() { 16 | super.viewDidLoad() 17 | self.view.translatesAutoresizingMaskIntoConstraints = false 18 | self.view.backgroundColor = UIColor.init(red: 240/255.0, green: 239/255.0, blue: 237/255.0, alpha: 1.0) 19 | self.tableView.register(UINib.init(nibName: "DefaultTableViewCell", bundle: nil), forCellReuseIdentifier: "DefaultTableViewCell") 20 | 21 | for _ in 1...8{ 22 | self.array.append(" ") 23 | } 24 | 25 | self.tableView.es_addPullToRefresh(animator: ESRefreshDayHeaderAnimator.init(frame: CGRect.zero)) { 26 | [weak self] in 27 | let minseconds = 3.0 * Double(NSEC_PER_SEC) 28 | let dtime = DispatchTime.now(dispatch_time_t(DispatchTime.now), Int64(minseconds)) 29 | dispatch_after(dtime, DispatchQueue.main , { 30 | self?.page = 1 31 | self?.array.removeAll() 32 | for _ in 1...8{ 33 | self?.array.append(" ") 34 | } 35 | self?.tableView.reloadData() 36 | self?.tableView.es_stopPullToRefresh(completion: true) 37 | }) 38 | } 39 | self.tableView.refreshIdentifier = NSStringFromClass(DefaultTableViewController) // Set refresh identifier 40 | self.tableView.expriedTimeInterval = 20.0 // 20 second alive. 41 | 42 | self.tableView.es_addInfiniteScrolling(animator: MTRefreshFooterAnimator.init(frame: CGRect.zero)) { 43 | [weak self] in 44 | let minseconds = 3.0 * Double(NSEC_PER_SEC) 45 | let dtime = DispatchTime.now(dispatch_time_t(DispatchTime.now), Int64(minseconds)) 46 | dispatch_after(dtime, DispatchQueue.main , { 47 | self?.page += 1 48 | if self?.page <= 3 { 49 | for _ in 1...8{ 50 | self?.array.append(" ") 51 | } 52 | self?.tableView.reloadData() 53 | self?.tableView.es_stopLoadingMore() 54 | } else { 55 | self?.tableView.es_noticeNoMoreData() 56 | } 57 | }) 58 | } 59 | } 60 | 61 | override public func viewDidAppear(animated: Bool) { 62 | super.viewDidAppear(animated) 63 | self.tableView.es_autoPullToRefresh() 64 | } 65 | 66 | // MARK: - Table view data source 67 | override public func numberOfSectionsInTableView(tableView: UITableView) -> Int { 68 | return 1 69 | } 70 | 71 | override public func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 72 | return array.count 73 | } 74 | 75 | override public func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { 76 | return 100.0 77 | } 78 | 79 | override public func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { 80 | return CGFloat.leastNormalMagnitude 81 | } 82 | 83 | override public func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 84 | let cell = tableView.dequeueReusableCell(withIdentifier: "DefaultTableViewCell", for: indexPath as IndexPath) 85 | cell.backgroundColor = UIColor.init(white: 250.0 / 255.0, alpha: 1.0) 86 | return cell 87 | } 88 | 89 | override public func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { 90 | tableView.deselectRow(at: indexPath as IndexPath, animated: true) 91 | let vc = WebViewController.init() 92 | self.navigationController?.pushViewController(vc, animated: true) 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Custom/ESPhotoTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ESPhotoTableViewCell.swift 3 | // ESPullToRefreshExample 4 | // 5 | // Created by lihao on 2016/11/9. 6 | // Copyright © 2016年 egg swift. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ESPhotoTableViewCell: UITableViewCell { 12 | 13 | @IBOutlet weak var indexLabel: UILabel! 14 | @IBOutlet weak var photoImageView: UIImageView! 15 | 16 | override func awakeFromNib() { 17 | super.awakeFromNib() 18 | // Initialization code 19 | } 20 | 21 | func updateContent(indexPath: IndexPath) { 22 | let name = String.init(format: "Photo_Lofter_%d", (indexPath.row) % 9 + 1) 23 | self.photoImageView.image = UIImage.init(named: name) 24 | self.indexLabel.text = String.init(format: "Section: %d Row: %d", indexPath.section, indexPath.row) 25 | } 26 | 27 | override func setHighlighted(_ highlighted: Bool, animated: Bool) { 28 | super.setHighlighted(highlighted, animated: animated) 29 | self.photoImageView.alpha = highlighted ? 0.8 : 1.0 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Custom/ESPhotoTableViewCell.xib: -------------------------------------------------------------------------------- 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 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 81 | 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 | -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Custom/ESRefreshTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultTableViewCell.swift 3 | // ESPullToRefreshExample 4 | // 5 | // Created by lihao on 16/5/6. 6 | // Copyright © 2016年 egg swift. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ESRefreshTableViewCell: UITableViewCell { 12 | 13 | override func awakeFromNib() { 14 | super.awakeFromNib() 15 | // Initialization code 16 | } 17 | 18 | override func setHighlighted(_ highlighted: Bool, animated: Bool) { 19 | super.setHighlighted(highlighted, animated: animated) 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Custom/ESRefreshTableViewCell.xib: -------------------------------------------------------------------------------- 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 | 40 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Custom/ESRefreshTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ESRefreshTableViewController.swift 3 | // ESPullToRefreshExample 4 | // 5 | // Created by lihao on 16/8/18. 6 | // Copyright © 2016年 egg swift. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public class ESRefreshTableViewController: UITableViewController { 12 | 13 | public var array = [String]() 14 | public var page = 1 15 | public var type: ESRefreshExampleType = .defaulttype 16 | 17 | public override init(style: UITableView.Style) { 18 | super.init(style: style) 19 | for num in 1...8{ 20 | if num % 2 == 0 && arc4random() % 4 == 0 { 21 | self.array.append("info") 22 | } else { 23 | self.array.append("photo") 24 | } 25 | } 26 | } 27 | 28 | public required init?(coder aDecoder: NSCoder) { 29 | fatalError("init(coder:) has not been implemented") 30 | } 31 | 32 | public override func viewDidLoad() { 33 | super.viewDidLoad() 34 | 35 | self.tableView.backgroundColor = UIColor.init(red: 244.0 / 255.0, green: 245.0 / 255.0, blue: 245.0 / 255.0, alpha: 1.0) 36 | 37 | self.tableView.register(UINib.init(nibName: "ESRefreshTableViewCell", bundle: nil), forCellReuseIdentifier: "ESRefreshTableViewCell") 38 | self.tableView.register(UINib.init(nibName: "ESPhotoTableViewCell", bundle: nil), forCellReuseIdentifier: "ESPhotoTableViewCell") 39 | self.tableView.rowHeight = UITableView.automaticDimension 40 | self.tableView.estimatedRowHeight = 560 41 | self.tableView.separatorStyle = .none 42 | self.tableView.separatorColor = UIColor.clear 43 | 44 | var header: ESRefreshProtocol & ESRefreshAnimatorProtocol 45 | var footer: ESRefreshProtocol & ESRefreshAnimatorProtocol 46 | switch type { 47 | case .meituan: 48 | header = MTRefreshHeaderAnimator.init(frame: CGRect.zero) 49 | footer = MTRefreshFooterAnimator.init(frame: CGRect.zero) 50 | case .wechat: 51 | header = WCRefreshHeaderAnimator.init(frame: CGRect.zero) 52 | footer = ESRefreshFooterAnimator.init(frame: CGRect.zero) 53 | default: 54 | header = ESRefreshHeaderAnimator.init(frame: CGRect.zero) 55 | footer = ESRefreshFooterAnimator.init(frame: CGRect.zero) 56 | break 57 | } 58 | 59 | self.tableView.es.addPullToRefresh(animator: header) { [weak self] in 60 | self?.refresh() 61 | } 62 | self.tableView.es.addInfiniteScrolling(animator: footer) { [weak self] in 63 | self?.loadMore() 64 | } 65 | self.tableView.refreshIdentifier = String.init(describing: type) 66 | self.tableView.expiredTimeInterval = 20.0 67 | 68 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { 69 | self.tableView.es.autoPullToRefresh() 70 | } 71 | } 72 | 73 | private func refresh() { 74 | DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { 75 | self.page = 1 76 | self.array.removeAll() 77 | for num in 1...8{ 78 | if num % 2 == 0 && arc4random() % 4 == 0 { 79 | self.array.append("info") 80 | } else { 81 | self.array.append("photo") 82 | } 83 | } 84 | self.tableView.reloadData() 85 | self.tableView.es.stopPullToRefresh() 86 | } 87 | } 88 | 89 | private func loadMore() { 90 | DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { 91 | self.page += 1 92 | if self.page <= 3 { 93 | for num in 1...8{ 94 | if num % 2 == 0 && arc4random() % 4 == 0 { 95 | self.array.append("info") 96 | } else { 97 | self.array.append("photo") 98 | } 99 | } 100 | self.tableView.reloadData() 101 | self.tableView.es.stopLoadingMore() 102 | } else { 103 | self.tableView.es.noticeNoMoreData() 104 | } 105 | } 106 | } 107 | 108 | // MARK: - Table view data source 109 | public override func numberOfSections(in tableView: UITableView) -> Int { 110 | return 1 111 | } 112 | 113 | public override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 114 | return array.count 115 | } 116 | 117 | public override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { 118 | return CGFloat.leastNormalMagnitude 119 | } 120 | 121 | public override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 122 | var cell: UITableViewCell! 123 | let string = self.array[indexPath.row] 124 | if string == "info" { 125 | cell = tableView.dequeueReusableCell(withIdentifier: "ESRefreshTableViewCell", for: indexPath as IndexPath) 126 | } else if string == "photo" { 127 | cell = tableView.dequeueReusableCell(withIdentifier: "ESPhotoTableViewCell", for: indexPath as IndexPath) 128 | if let cell = cell as? ESPhotoTableViewCell { 129 | cell.updateContent(indexPath: indexPath) 130 | } 131 | } else { 132 | cell = tableView.dequeueReusableCell(withIdentifier: "UITableViewCell", for: indexPath as IndexPath) 133 | } 134 | return cell 135 | } 136 | 137 | public override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 138 | tableView.deselectRow(at: indexPath as IndexPath, animated: true) 139 | self.navigationController?.pushViewController(WebViewController.init(), animated: true) 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Custom/Meituan/MTRefreshFooterAnimator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MTRefreshFooterAnimator.swift 3 | // ESPullToRefreshExample 4 | // 5 | // Created by lihao on 16/5/7. 6 | // Copyright © 2016年 egg swift. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public class MTRefreshFooterAnimator: UIView, ESRefreshProtocol, ESRefreshAnimatorProtocol { 12 | public let loadingMoreDescription: String = "Loading more" 13 | public let noMoreDataDescription: String = "No more data" 14 | public let loadingDescription: String = "Loading..." 15 | 16 | public var view: UIView { 17 | return self 18 | } 19 | public var insets: UIEdgeInsets = UIEdgeInsets.zero 20 | public var trigger: CGFloat = 48.0 21 | public var executeIncremental: CGFloat = 48.0 22 | public var state: ESRefreshViewState = .pullToRefresh 23 | 24 | private let topLine: UIView = { 25 | let topLine = UIView.init(frame: CGRect.zero) 26 | topLine.backgroundColor = UIColor.init(red: 214/255.0, green: 211/255.0, blue: 206/255.0, alpha: 1.0) 27 | return topLine 28 | }() 29 | private let bottomLine: UIView = { 30 | let bottomLine = UIView.init(frame: CGRect.zero) 31 | bottomLine.backgroundColor = UIColor.init(red: 214/255.0, green: 211/255.0, blue: 206/255.0, alpha: 1.0) 32 | return bottomLine 33 | }() 34 | private let titleLabel: UILabel = { 35 | let label = UILabel.init(frame: CGRect.zero) 36 | label.font = UIFont.systemFont(ofSize: 14.0) 37 | label.textColor = UIColor.init(white: 160.0 / 255.0, alpha: 1.0) 38 | label.textAlignment = .center 39 | return label 40 | }() 41 | private let indicatorView: UIActivityIndicatorView = { 42 | let indicatorView = UIActivityIndicatorView.init(style: .gray) 43 | indicatorView.isHidden = true 44 | return indicatorView 45 | }() 46 | 47 | override init(frame: CGRect) { 48 | super.init(frame: frame) 49 | self.backgroundColor = UIColor.white 50 | titleLabel.text = loadingMoreDescription 51 | addSubview(titleLabel) 52 | addSubview(indicatorView) 53 | addSubview(topLine) 54 | addSubview(bottomLine) 55 | } 56 | 57 | public required init(coder aDecoder: NSCoder) { 58 | fatalError("init(coder:) has not been implemented") 59 | } 60 | 61 | public func refreshAnimationBegin(view: ESRefreshComponent) { 62 | indicatorView.startAnimating() 63 | indicatorView.isHidden = false 64 | } 65 | 66 | public func refreshAnimationEnd(view: ESRefreshComponent) { 67 | indicatorView.stopAnimating() 68 | indicatorView.isHidden = true 69 | } 70 | 71 | public func refresh(view: ESRefreshComponent, progressDidChange progress: CGFloat) { 72 | // do nothing 73 | } 74 | 75 | public func refresh(view: ESRefreshComponent, stateDidChange state: ESRefreshViewState) { 76 | switch state { 77 | case .refreshing : 78 | titleLabel.text = loadingDescription 79 | break 80 | case .autoRefreshing : 81 | titleLabel.text = loadingDescription 82 | break 83 | case .noMoreData: 84 | titleLabel.text = noMoreDataDescription 85 | break 86 | default: 87 | titleLabel.text = loadingMoreDescription 88 | break 89 | } 90 | } 91 | 92 | public override func layoutSubviews() { 93 | super.layoutSubviews() 94 | let s = self.bounds.size 95 | let w = s.width 96 | let h = s.height 97 | titleLabel.frame = self.bounds 98 | indicatorView.center = CGPoint.init(x: 32.0, y: h / 2.0) 99 | topLine.frame = CGRect.init(x: 0.0, y: 0.0, width: w, height: 0.5) 100 | bottomLine.frame = CGRect.init(x: 0.0, y: h - 1.0, width: w, height: 1.0) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Custom/Meituan/MTRefreshHeaderAnimator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MTRefreshHeaderAnimator.swift 3 | // ESPullToRefreshExample 4 | // 5 | // Created by lihao on 16/5/7. 6 | // Copyright © 2016年 egg swift. All rights reserved. 7 | // 8 | // Icon from 美团 iOS app. 9 | // https://itunes.apple.com/cn/app/mei-tuan-tuan-gou-tuan-gou/id423084029?mt=8 10 | // 11 | 12 | import UIKit 13 | 14 | public class MTRefreshHeaderAnimator: UIView, ESRefreshProtocol, ESRefreshAnimatorProtocol { 15 | 16 | public var insets: UIEdgeInsets = UIEdgeInsets.zero 17 | public var view: UIView { return self } 18 | public var duration: TimeInterval = 0.3 19 | public var trigger: CGFloat = 56.0 20 | public var executeIncremental: CGFloat = 56.0 21 | public var state: ESRefreshViewState = .pullToRefresh 22 | 23 | private let imageView: UIImageView = { 24 | let imageView = UIImageView.init() 25 | imageView.image = UIImage.init(named: "icon_pull_animation_1") 26 | return imageView 27 | }() 28 | 29 | override init(frame: CGRect) { 30 | super.init(frame: frame) 31 | self.addSubview(imageView) 32 | } 33 | 34 | public required init(coder aDecoder: NSCoder) { 35 | fatalError("init(coder:) has not been implemented") 36 | } 37 | 38 | public func refreshAnimationBegin(view: ESRefreshComponent) { 39 | imageView.center = self.center 40 | UIView.animate(withDuration: 0.2, delay: 0, options: .curveLinear, animations: { 41 | self.imageView.frame = CGRect.init(x: (self.bounds.size.width - 39.0) / 2.0, 42 | y: self.bounds.size.height - 50.0, 43 | width: 39.0, 44 | height: 50.0) 45 | 46 | 47 | }, completion: { (finished) in 48 | var images = [UIImage]() 49 | for idx in 1 ... 8 { 50 | if let aImage = UIImage(named: "icon_shake_animation_\(idx)") { 51 | images.append(aImage) 52 | } 53 | } 54 | self.imageView.animationDuration = 0.5 55 | self.imageView.animationRepeatCount = 0 56 | self.imageView.animationImages = images 57 | self.imageView.startAnimating() 58 | }) 59 | } 60 | 61 | public func refreshAnimationEnd(view: ESRefreshComponent) { 62 | imageView.stopAnimating() 63 | imageView.image = UIImage.init(named: "icon_pull_animation_1") 64 | 65 | UIView.animate(withDuration: 0.2, delay: 0, options: .curveLinear, animations: { 66 | self.refresh(view: view, progressDidChange: 0.0) 67 | }, completion: { (finished) in 68 | }) 69 | } 70 | 71 | public func refresh(view: ESRefreshComponent, progressDidChange progress: CGFloat) { 72 | let p = max(0.0, min(1.0, progress)) 73 | imageView.frame = CGRect.init(x: (self.bounds.size.width - 39.0) / 2.0, 74 | y: self.bounds.size.height - 50.0 * p, 75 | width: 39.0, 76 | height: 50.0 * p) 77 | } 78 | 79 | public func refresh(view: ESRefreshComponent, stateDidChange state: ESRefreshViewState) { 80 | guard self.state != state else { 81 | return 82 | } 83 | self.state = state 84 | 85 | switch state { 86 | case .pullToRefresh: 87 | var images = [UIImage]() 88 | for idx in 1 ... 5 { 89 | if let aImage = UIImage(named: "icon_pull_animation_\((5 - idx + 1))") { 90 | images.append(aImage) 91 | } 92 | } 93 | imageView.animationDuration = 0.2 94 | imageView.animationRepeatCount = 1 95 | imageView.animationImages = images 96 | imageView.image = UIImage.init(named: "icon_pull_animation_1") 97 | imageView.startAnimating() 98 | break 99 | case .releaseToRefresh: 100 | var images = [UIImage]() 101 | for idx in 1 ... 5 { 102 | if let aImage = UIImage(named: "icon_pull_animation_\(idx)") { 103 | images.append(aImage) 104 | } 105 | } 106 | imageView.animationDuration = 0.2 107 | imageView.animationRepeatCount = 1 108 | imageView.animationImages = images 109 | imageView.image = UIImage.init(named: "icon_pull_animation_5") 110 | imageView.startAnimating() 111 | break 112 | default: 113 | break 114 | } 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Custom/Meituan/MeituanTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MeituanTableViewController.swift 3 | // ESPullToRefreshExample 4 | // 5 | // Created by lihao on 16/5/7. 6 | // Copyright © 2016年 egg swift. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class MeituanTableViewController: UITableViewController { 12 | var array = [String]() 13 | var page = 1 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | self.view.translatesAutoresizingMaskIntoConstraints = false 18 | self.view.backgroundColor = UIColor.init(red: 240/255.0, green: 239/255.0, blue: 237/255.0, alpha: 1.0) 19 | self.tableView.register(UINib.init(nibName: "DefaultTableViewCell", bundle: nil), forCellReuseIdentifier: "DefaultTableViewCell") 20 | 21 | for _ in 1...8{ 22 | self.array.append(" ") 23 | } 24 | 25 | let _ = self.tableView.es_addPullToRefresh(animator: MTRefreshHeaderAnimator.init(frame: CGRect.zero)) { 26 | [weak self] in 27 | DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { 28 | self?.page = 1 29 | self?.array.removeAll() 30 | for _ in 1...8{ 31 | self?.array.append(" ") 32 | } 33 | self?.tableView.reloadData() 34 | self?.tableView.es_stopPullToRefresh(completion: true) 35 | } 36 | } 37 | self.tableView.refreshIdentifier = NSStringFromClass(DefaultTableViewController.self) // Set refresh identifier 38 | self.tableView.expriedTimeInterval = 20.0 // 20 second alive. 39 | 40 | let _ = self.tableView.es_addInfiniteScrolling(animator: MTRefreshFooterAnimator.init(frame: CGRect.zero)) { 41 | [weak self] in 42 | DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { 43 | self?.page += 1 44 | if self?.page ?? 0 <= 3 { 45 | for _ in 1...8{ 46 | self?.array.append(" ") 47 | } 48 | self?.tableView.reloadData() 49 | self?.tableView.es_stopLoadingMore() 50 | } else { 51 | self?.tableView.es_noticeNoMoreData() 52 | } 53 | } 54 | } 55 | } 56 | 57 | override func viewDidAppear(_ animated: Bool) { 58 | super.viewDidAppear(animated) 59 | self.tableView.es_autoPullToRefresh() 60 | } 61 | 62 | // MARK: - Table view data source 63 | func numberOfSectionsInTableView(tableView: UITableView) -> Int { 64 | return 1 65 | } 66 | 67 | func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 68 | return array.count 69 | } 70 | 71 | func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { 72 | return 100.0 73 | } 74 | 75 | func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { 76 | return CGFloat.leastNormalMagnitude 77 | } 78 | 79 | func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 80 | let cell = tableView.dequeueReusableCell(withIdentifier: "DefaultTableViewCell", for: indexPath as IndexPath) 81 | cell.backgroundColor = UIColor.init(white: 250.0 / 255.0, alpha: 1.0) 82 | return cell 83 | } 84 | 85 | func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { 86 | tableView.deselectRow(at: indexPath as IndexPath, animated: true) 87 | let vc = WebViewController.init() 88 | self.navigationController?.pushViewController(vc, animated: true) 89 | } 90 | 91 | 92 | } 93 | -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Custom/TextView/TextViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextViewController.swift 3 | // ESPullToRefreshExample 4 | // 5 | // Created by lihao on 16/6/23. 6 | // Copyright © 2016年 egg swift. All rights reserved. 7 | // Test from: '007 The Spy Who Loved Me' 8 | 9 | import UIKit 10 | 11 | class TextViewController: UIViewController { 12 | var textView: UITextView! 13 | var num: Int = 0 14 | var text1: String = """ 15 | \tPart One: Me One: Scaredy Cat 16 | \tI WAS running away. I was running away from England, from my childhood, from the winter, from a sequence of untidy, unattractive love-affairs, from the few sticks of furniture and jumble of overworn clothes that my London life had collected around me; and I was running away from drabness, fustiness, snobbery, the claustrophobia of close horizons, and from my inability, although I am quite an attractive rat, to make headway in the rat-race. In fact I was running away from almost everything except the law. 17 | \tAnd I had run a very long way indeed-almost, exaggerating a bit, halfway round the world. In fact I had come all the way from London to The Dreamy Pines Motor Court, which is ten miles west of Lake George, the famous American tourist resort in the Adirondacks-that vast expanse of mountains, lakes, and pine forests which forms most of the northern territory of New York State. I had started on September the first, and it was now Friday the thirteenth of October. When I had left, the grimy little row of domesticated maples in my square had been green, or as green as any tree can be in London in August. Now, in the billion-strong army of pine trees that marched away northward toward the Canadian border, the real, wild maples flamed here and there like shrapnel-bursts. And I felt that I, or at any rate my skin, had changed just as much-from the grimy sallowness that had been the badge of my London life to the snap and color and sparkle of living out of doors and going to bed early and all those other dear dull things that had been part of my life in Quebec before it was decided that I must go to England and learn to be a \"lady.\" Very unfashionable, of course, this cherry-ripe, strength-through-joy complexion, and I had even stopped using lipstick and nail polish, but to me it had been like sloughing off a borrowed skin and getting back into my own, and I was childishly happy and pleased with myself whenever I looked in the mirror (that's another thing-I'll never say \"looking-glass\" again; I just don't have to any more) and found myself not wanting to paint a different face over my own. I'm not being smug about this. I was just running away from the person I'd been for the past five years. I wasn't particularly pleased with the person I was now, but I had hated and despised the other one, and I was glad to be rid of her face. 18 | 19 | """ 20 | 21 | var text2: String = """ 22 | \tStation WOKO (they might have dreamed up a grander call-sign!) in Albany, the capital of New York State and about fifty miles due south of where I was, announced that it was six o'clock. The weather report that followed included a storm warning with gale-force winds. The storm was moving down from the north and would hit Albany around eight p.m. That meant that I would be having a noisy night. I didn't mind. Storms don't frighten me, and although the nearest living soul, as far as I knew, was ten miles away up the not very good secondary road to Lake George, the thought of the pines that would soon be thrashing outside, the thunder and lightning and rain, made me already feel snug and warm and protected in anticipation. And alone! But above all alone! \"Loneliness becomes a lover, solitude a darling sin.\" Where had I read that? Who had written it? It was so exactly the way I felt, the way that, as a child, I had always felt until I had forced myself to \"get into the swim,\" \"be one of the crowd\"-a good sort, on the ball, hep. And what a hash I had made of \"togetherness\"! I shrugged the memory of failure away. Everyone doesn't have to live in a heap. Painters, writers, musicians are lonely people. So are statesmen and admirals and generals. But then, I added to be fair, so are criminals and lunatics. Let's just say, not to be too flattering, that true Individuals are lonely. It's not a virtue-the reverse, if anything. One ought to share and communicate if one is to be a useful member of the tribe. The fact that I was so much happier when I was alone was surely the sign of a faulty, a neurotic character. I had said this so often to myself in the past five years that now, that evening, I just shrugged my shoulders and, hugging my solitude to me, walked across the big lobby to the door and went out to have a last look at the evening. 23 | 24 | """ 25 | 26 | var text3: String = """ 27 | \tI hate pine trees. They are dark and stand very still and you can't shelter under them or climb them. They are very dirty, with a most un-treelike black dirt, and if you get this dirt mixed with their resin they make you really filthy. I find their jagged shapes vaguely inimical, and the way they mass so closely together gives me the impression of an army of spears barring my passage. The only good thing about them is their smell, and, when I can get hold of it, I use pine-needle essence in my bath. Here, in the Adirondacks, the endless vista of pine trees was positively sickening. They clothe every square yard of earth in the valleys and climb up to the top of every mountain so that the impression is of a spiky carpet spread to the horizon-an endless vista of rather stupid-looking green pyramids waiting to be cut down for matches and coat-hangers and copies of the New York Times. 28 | 29 | """ 30 | 31 | var text4: String = """ 32 | \tFive acres or so of these stupid trees had been cleared to build the motel, which is all that this place really was. \"Motel\" isn't a good word any longer. It has become smart to use \"Motor Court\" or \"Ranch Cabins\" ever since motels became associated with prostitution, gangsters, and murders, for all of which their anonymity and lack of supervision is a convenience. The site, touristwise, in the lingo of the trade, was a good one. There was this wandering secondary road through the forest, which was a pleasant alternative route between Lake George and Glen Falls to the south, and halfway along it was a small lake, cutely called Dreamy Waters, that was a traditional favorite with picnickers. It was on the southern shore of this lake that the motel had been built, its reception lobby facing the road, with, behind this main building, the rooms fanning out in a semicircle. There were forty rooms with kitchen, shower, and lavatory, and they all had some kind of view of the lake behind them. The whole construction and design was the latest thing-glazed pitch-pine frontages and pretty timber roofs all over knobbles, air-conditioning, television in every cabin, children's playground, swimming pool, golf range out over the lake with floating balls (fifty balls, one dollar)-all the gimmicks. Food? Cafeteria in the lobby, and grocery and liquor deliveries twice a day from Lake George. All this for ten dollars single and sixteen double. No wonder that, with around two hundred thousand dollars' capital outlay and a season lasting only from July the first to the beginning of October, or, so far as the NO VACANCY sign was concerned, from July fourteenth to Labor Day, the owners were finding the going hard. Or so those dreadful Phanceys had told me when they'd taken me on as receptionist for only thirty dollars a week plus keep. Thank heavens they were out of my hair! Song in my heart? There had been the whole heavenly choir at six o'clock that morning when their shiny station-wagon had disappeared down the road on their way to Glens Falls and then to Troy where the monsters came from. Mr. Phancey had made a last grab at me, and I hadn't been quick enough. His free hand had run like a fast lizard over my body before I had crunched my heel into his instep. He had let go then. When his contorted face had cleared, he said softly, \"All right, sex-box. Just see that you mind camp good until the boss comes to take over the keys tomorrow noon. Happy dreams tonight.\" Then he had grinned a grin I hadn't understood, and had gone over to the station-wagon, where his wife had been watching from the driver's seat. \"Come on, Jed,\" she had said sharply. \"You can work off those urges on West Street tonight.\" She put the car in gear and called over to me sweetly, \" \'By now, cutie-pie. Write us every day.\" Then she had wiped the crooked smile off her face and I caught a last glimpse of her withered hatchet profile as the car turned out onto the road. Phew! What a couple! Right out of a book-and what a book! Dear Diary! Well, people couldn\'t come much Worse, and now they\'d gone. From now on, on my travels, the human race must improve! 33 | 34 | """ 35 | 36 | var text5: String = """ 37 | \tI had been standing there, looking down the way the Phanceys had gone, remembering them. Now I turned and looked to the north to see after the weather. It had been a beautiful day, Swiss clear and hot for the middle of October, but now high fretful clouds, black with jagged pink hair from the setting sun, were piling down the sky. Fast little winds were zigzagging among the forest tops and every now and then they hit the single yellow light above the deserted gas station down the road at the tail of the lake and set it swaying. When a longer gust reached me, cold and buffeting, it brought with it the whisper of a metallic squeak from the dancing light, and the first time this happened I shivered deliciously at the little ghostly noise. On the lake shore, beyond the last of the cabins, small waves were lapping fast against the stones, and the gunmetal surface of the lake was fretted with sudden cat\'s-paws that sometimes showed a fleck of white. But, in between the angry gusts, the air was still, and the sentinel trees across the road and behind the motel seemed to be pressing silently closer to huddle round the campfire of the brightly lit building at my back. \n I suddenly wanted to go to the john, and I smiled to myself. It was the piercing tickle that comes to children during hide-and-seek-in-the-dark and Sardines, when, in your cupboard under the stairs, you heard the soft creak of a floorboard, the approaching whisper of the searchers. Then you clutched yourself in thrilling anguish and squeezed your legs together and waited for the ecstasy of discovery, the crack of light from the opening door and then-the supreme moment-your urgent \"Ssh! Come in with me!\" the softly closing door and the giggling warm body pressed tight against your own. 38 | 39 | """ 40 | var text6: String = """ 41 | \tStanding there, a \"big girl\" now, I remembered it all and recognized the sensual itch brought on by a fleeting apprehension-the shiver down the spine, the intuitive gooseflesh that come from the primitive fear-signals of animal ancestors. 1 was amused and I hugged the moment to me. Soon the thunderheads would burst and I would step back from the howl and chaos of the storm into my well-lighted comfortable cave, make myself a drink, listen to the radio, and feel safe and cosseted. \n It was getting dark. Tonight there would be no evening chorus from the birds. They had long ago read the signs and disappeared into their own shelters in the forest, as had the animals-the squirrels and the chipmunks and the deer. In all this huge wild area there was now only me out in the open. I took a last few deep breaths of the soft, moist air. The humidity had strengthened the scent of pine and moss, and now there was also a strong underlying armpit smell of earth. It was almost as if the forest was sweating with the same pleasurable excitement I was feeling. Somewhere, from quite close, a nervous owl asked loudly \"Who?\" and then was silent. I took a few steps away from the lighted doorway and stood in the middle of the dusty road, looking north. A strong gust of wind hit me and blew back my hair. Lightning threw a quick blue-white hand across the horizon. Seconds later thunder growled softly like a wakening guard dog, and then the big wind came and the tops of the trees began to dance and thrash and the yellow light over the gas station jigged and blinked down the road as if to warn me. It was warning me. Suddenly the dancing light was blurred with rain, its luminosity fogged by an advancing gray sheet of water. The first heavy drops hit me, and 1 turned and ran. \n I banged the door behind me, locked it, and put up the chain. I was only just in time. Then the avalanche crashed down and settled into a steady roar of water whose patterns of sound varied from a heavy drumming on the slanting timbers of the roof to a higher, more precise slashing at the windows. In a moment these sounds were joined by the busy violence of the overflow drainpipes. And the noisy background pattern of the storm was set. 42 | 43 | """ 44 | var text7: String = """ 45 | \tI was still standing there, cozily listening, when the thunder that had been creeping quietly up behind my back sprang its ambush. Suddenly lightning blazed in the room, and at the same instant there came a blockbusting crash that shook the building and made the air twang like piano wire. It was just one single colossal explosion that might have been a huge bomb falling only yards away. There was a sharp tinkle as a piece of glass fell out of one of the windows onto the floor, and then the noise of water pattering in onto the linoleum. \n I didn\'t move. I couldn\'t. I stood and cringed, my hands over my ears. I hadn\'t meant it to be like this! The silence, that had been deafening, resolved itself back into the roar of the rain, the roar that had been so comforting but that now said, \"You hadn\'t thought it could be so bad. You had never seen a storm in these mountains. Pretty flimsy this little shelter of yours, really. How\'d you like to have the lights put out as a start? Then the crash of a thunderbolt through that matchwood ceiling of yours? Then, just to finish you off, lightning to set fire to the place-perhaps electrocute you? Or shall we just frighten you so much that you dash out in the rain and try and make those ten miles to Lake George. Like to be alone, do you? Well, just try this for size!\" Again the room turned blue-white, again, just overhead, there came the ear-splitting crack of the explosion, but this time the crack widened and racketed to and fro in a furious cannonade that set the cups and glasses rattling behind the bar and made the woodwork creak with the pressure of the sound-waves.My legs felt weak, and I faltered to the nearest chair and sat down, my head in my hands. How could I have been so foolish, so-so impudent? If only someone would come, someone to stay with me, someone to tell me that this was only a storm! But it wasn\'t! It was catastrophe, the end of the world! And all aimed at me! Now! It would be coming again! Any minute now! I must do something, get help! But the Phanceys had paid off the telephone company, and the service had been disconnected. There was only one hope! I got up and ran to the door, reaching up for the big switch that controlled the VACANCY/NO VACANCY sign in red neon above the threshold. If I put it to VACANCY, there might be someone driving down the road. Someone who would be glad of shelter. But, as I pulled the switch, the lightning that had been watching me crackled viciously in the room, and, as the thunder crashed, I was seized by a giant hand and hurled to the floor. 46 | 47 | """ 48 | 49 | override func viewDidLoad() { 50 | super.viewDidLoad() 51 | 52 | textView = UITextView.init(frame: self.view.bounds) 53 | textView.isEditable = false 54 | textView.alwaysBounceVertical = true 55 | textView.textColor = UIColor.init(white: 0.3, alpha: 1.0) 56 | textView.textAlignment = .justified 57 | textView.textContainerInset = UIEdgeInsets.init(top: 12, left: 8, bottom: 12, right: 8) 58 | self.view.addSubview(textView) 59 | 60 | textView.es.addPullToRefresh { 61 | [weak self] in 62 | DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { 63 | self?.num = 0 64 | let style = NSMutableParagraphStyle.init() 65 | style.lineSpacing = 0.0 66 | style.firstLineHeadIndent = 10.0 67 | style.alignment = .justified 68 | self?.textView.attributedText = NSAttributedString.init(string: (self?.text1)!, attributes: [NSAttributedString.Key.paragraphStyle : style, NSAttributedString.Key.font: UIFont.init(name: "ChalkboardSE-Bold", size: 16.0)!, NSAttributedString.Key.foregroundColor: UIColor.init(white: 0.3, alpha: 1.0)]) 69 | self?.textView.es.stopPullToRefresh() 70 | } 71 | } 72 | 73 | textView.es.addInfiniteScrolling { 74 | [weak self] in 75 | DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { 76 | let i = self?.num ?? 0 77 | self?.num += 1 78 | var str: String = self?.text1 ?? "" 79 | if i >= 1 { 80 | str += self?.text2 ?? "" 81 | } 82 | if i >= 2 { 83 | str += self?.text3 ?? "" 84 | } 85 | if i >= 3 { 86 | str += self?.text4 ?? "" 87 | } 88 | if i >= 4 { 89 | str += self?.text5 ?? "" 90 | } 91 | if i >= 5 { 92 | str += self?.text6 ?? "" 93 | } 94 | if i >= 6 { 95 | str += self?.text7 ?? "" 96 | } 97 | if i >= 7 { 98 | self?.textView.es.noticeNoMoreData() 99 | } else { 100 | let style = NSMutableParagraphStyle.init() 101 | style.lineSpacing = 0.0 102 | style.firstLineHeadIndent = 10.0 103 | style.alignment = .justified 104 | self?.textView.attributedText = NSAttributedString.init(string: str, attributes: [NSAttributedString.Key.paragraphStyle : style, NSAttributedString.Key.font: UIFont.init(name: "ChalkboardSE-Bold", size: 16.0)!, NSAttributedString.Key.foregroundColor: UIColor.init(white: 0.3, alpha: 1.0)]) 105 | 106 | self?.textView.es.stopLoadingMore() 107 | } 108 | } 109 | } 110 | } 111 | 112 | override func viewDidAppear(_ animated: Bool) { 113 | super.viewDidAppear(animated) 114 | self.textView.es.startPullToRefresh() 115 | } 116 | 117 | override func viewDidLayoutSubviews() { 118 | super.viewDidLayoutSubviews() 119 | if !textView.frame.equalTo(self.view.bounds) { 120 | textView.frame = self.view.bounds 121 | } 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Custom/WeChat/WCRefreshHeaderAnimator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WCRefreshHeaderAnimator.swift 3 | // ESPullToRefreshExample 4 | // 5 | // Created by lihao on 16/5/9. 6 | // Copyright © 2016年 egg swift. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public class WCRefreshHeaderAnimator: UIView, ESRefreshProtocol, ESRefreshAnimatorProtocol { 12 | 13 | public var insets: UIEdgeInsets = UIEdgeInsets.zero 14 | public var view: UIView { return self } 15 | public var trigger: CGFloat = 56.0 16 | public var executeIncremental: CGFloat = 0.0 17 | public var state: ESRefreshViewState = .pullToRefresh 18 | 19 | private var timer: Timer? 20 | private var timerProgress: Double = 0.0 21 | 22 | private let imageView: UIImageView = { 23 | let imageView = UIImageView.init() 24 | imageView.image = UIImage.init(named: "icon_wechat") 25 | imageView.sizeToFit() 26 | let size = imageView.image?.size ?? CGSize.zero 27 | imageView.center = CGPoint.init(x: UIScreen.main.bounds.size.width / 2.0, y: -size.height) 28 | return imageView 29 | }() 30 | 31 | public override init(frame: CGRect) { 32 | super.init(frame: frame) 33 | self.addSubview(imageView) 34 | } 35 | 36 | public required init(coder aDecoder: NSCoder) { 37 | fatalError("init(coder:) has not been implemented") 38 | } 39 | 40 | public func refreshAnimationBegin(view: ESRefreshComponent) { 41 | self.startAnimating() 42 | UIView.animate(withDuration: 0.2, delay: 0, options: .curveLinear, animations: { 43 | let size = self.imageView.image?.size ?? CGSize.zero 44 | self.imageView.center = CGPoint.init(x: UIScreen.main.bounds.size.width / 2.0, y: 16.0 + size.height / 2.0) 45 | }, completion: { (finished) in }) 46 | } 47 | 48 | public func refreshAnimationEnd(view: ESRefreshComponent) { 49 | UIView.animate(withDuration: 0.1, delay: 0, options: .curveEaseIn, animations: { 50 | self.imageView.transform = CGAffineTransform.identity 51 | self.imageView.center = CGPoint.init(x: UIScreen.main.bounds.size.width / 2.0, y: self.imageView.center.y + 10.0) 52 | }, completion: { (finished) in 53 | UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseOut, animations: { 54 | let size = self.imageView.image?.size ?? CGSize.zero 55 | self.imageView.transform = CGAffineTransform.identity 56 | self.imageView.center = CGPoint.init(x: UIScreen.main.bounds.size.width / 2.0, y: -size.height) 57 | }, completion: { (finished) in 58 | self.stopAnimating() 59 | }) 60 | }) 61 | } 62 | 63 | public func refresh(view: ESRefreshComponent, progressDidChange progress: CGFloat) { 64 | let size = imageView.image?.size ?? CGSize.zero 65 | let p = min(1.0, max(0.0, progress)) 66 | let y = (-self.trigger * progress) + 16.0 - (size.height + 16.0) * (1 - p) 67 | let center = CGPoint.init(x: UIScreen.main.bounds.size.width / 2.0, y: y + (size.height / 2.0)) 68 | self.imageView.center = center 69 | self.imageView.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi) * progress) 70 | } 71 | 72 | public func refresh(view: ESRefreshComponent, stateDidChange state: ESRefreshViewState) { 73 | guard self.state != state else { 74 | return 75 | } 76 | self.state = state 77 | } 78 | 79 | @objc func timerAction() { 80 | timerProgress += 0.01 81 | self.imageView.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi) * CGFloat(timerProgress)) 82 | } 83 | 84 | func startAnimating() { 85 | if timer == nil { 86 | timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(WCRefreshHeaderAnimator.timerAction), userInfo: nil, repeats: true) 87 | RunLoop.current.add(timer!, forMode: RunLoop.Mode.common) 88 | } 89 | } 90 | 91 | func stopAnimating() { 92 | if timer != nil { 93 | timerProgress = 0.0 94 | timer?.invalidate() 95 | timer = nil 96 | } 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Custom/WeChat/WeChatTableHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WeChatTableHeaderView.swift 3 | // ESPullToRefreshExample 4 | // 5 | // Created by lihao on 16/5/9. 6 | // Copyright © 2016年 egg swift. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class WeChatTableHeaderView: UIView { 12 | let imageView: UIImageView = { 13 | let imageView = UIImageView.init(image: UIImage.init(named: "icon_wechat_header")) 14 | imageView.contentMode = .scaleAspectFill 15 | imageView.clipsToBounds = true 16 | return imageView 17 | }() 18 | 19 | let avatarView: UIImageView = { 20 | let avatarView = UIImageView.init(image: UIImage.init(named: "icon")) 21 | return avatarView 22 | }() 23 | 24 | override init(frame: CGRect) { 25 | super.init(frame: frame) 26 | self.addSubview(imageView) 27 | self.addSubview(avatarView) 28 | } 29 | 30 | required init?(coder aDecoder: NSCoder) { 31 | fatalError("init(coder:) has not been implemented") 32 | } 33 | 34 | override func layoutSubviews() { 35 | super.layoutSubviews() 36 | imageView.frame = self.bounds 37 | avatarView.frame = CGRect.init(x: self.bounds.size.width - 10.0 - 75.0, y: self.bounds.size.height - 48.0, width: 75.0, height: 75.0) 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/Custom/WeChat/WeChatTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WeChatTableViewController.swift 3 | // ESPullToRefreshExample 4 | // 5 | // Created by lihao on 16/5/9. 6 | // Copyright © 2016年 egg swift. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class WeChatTableViewController: UITableViewController { 12 | var array = [String]() 13 | var page = 1 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | self.view.translatesAutoresizingMaskIntoConstraints = false 18 | self.view.backgroundColor = UIColor.init(red: 46/255.0, green: 49/255.0, blue: 50/255.0, alpha: 1.0) 19 | self.tableView.register(UINib.init(nibName: "DefaultTableViewCell", bundle: nil), forCellReuseIdentifier: "DefaultTableViewCell") 20 | 21 | // Header like WeChat 22 | let header = WeChatTableHeaderView.init(frame: CGRect.init(origin: CGPoint.zero, size: CGSize.init(width: self.view.bounds.size.width, height: 260))) 23 | self.tableView.tableHeaderView = header 24 | 25 | /// Add some data 26 | for _ in 1...8{ 27 | self.array.append(" ") 28 | } 29 | 30 | let _ = self.tableView.es_addPullToRefresh(animator: WCRefreshHeaderAnimator.init(frame: CGRect.zero)) { 31 | [weak self] in 32 | DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { 33 | self?.page = 1 34 | self?.array.removeAll() 35 | for _ in 1...8{ 36 | self?.array.append(" ") 37 | } 38 | self?.tableView.reloadData() 39 | self?.tableView.es_stopPullToRefresh(completion: true) 40 | } 41 | } 42 | self.tableView.refreshIdentifier = NSStringFromClass(DefaultTableViewController.self) // Set refresh identifier 43 | self.tableView.expriedTimeInterval = 20.0 // 20 second alive. 44 | 45 | let _ = self.tableView.es_addInfiniteScrolling() { 46 | [weak self] in 47 | DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { 48 | self?.page += 1 49 | if self?.page ?? 0 <= 3 { 50 | for _ in 1...8{ 51 | self?.array.append(" ") 52 | } 53 | self?.tableView.reloadData() 54 | self?.tableView.es_stopLoadingMore() 55 | } else { 56 | self?.tableView.es_noticeNoMoreData() 57 | } 58 | } 59 | } 60 | self.tableView.es_footer?.backgroundColor = UIColor.white // Custom footer background color 61 | } 62 | 63 | override func viewDidAppear(_ animated: Bool) { 64 | super.viewDidAppear(animated) 65 | self.tableView.es_autoPullToRefresh() 66 | } 67 | 68 | // MARK: - Table view data source 69 | func numberOfSectionsInTableView(tableView: UITableView) -> Int { 70 | return 1 71 | } 72 | 73 | func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 74 | return array.count 75 | } 76 | 77 | func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { 78 | return 100.0 79 | } 80 | 81 | func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { 82 | return CGFloat.leastNormalMagnitude 83 | } 84 | 85 | func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { 86 | return CGFloat.leastNormalMagnitude 87 | } 88 | 89 | func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 90 | let cell = tableView.dequeueReusableCell(withIdentifier: "DefaultTableViewCell", for: indexPath as IndexPath) 91 | cell.backgroundColor = UIColor.init(white: 250.0 / 255.0, alpha: 1.0) 92 | return cell 93 | } 94 | 95 | func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { 96 | tableView.deselectRow(at: indexPath as IndexPath, animated: true) 97 | let vc = WebViewController.init() 98 | self.navigationController?.pushViewController(vc, animated: true) 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/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 | $(MARKETING_VERSION) 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 | UIStatusBarHidden 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | UIViewControllerBasedStatusBarAppearance 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/ListTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListTableViewCell.swift 3 | // ESPullToRefreshExample 4 | // 5 | // Created by lihao on 16/9/12. 6 | // Copyright © 2016年 egg swift. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ListTableViewCell: UITableViewCell { 12 | 13 | @IBOutlet weak var titleLabel: UILabel! 14 | 15 | override func awakeFromNib() { 16 | super.awakeFromNib() 17 | // Initialization code 18 | } 19 | 20 | override func setSelected(_ selected: Bool, animated: Bool) { 21 | super.setSelected(selected, animated: animated) 22 | 23 | // Configure the view for the selected state 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/ListTableViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // ESPullToRefreshExample 4 | // 5 | // Created by egg swift on 16/5/5. 6 | // Copyright © 2016年 egg swift. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public enum ESRefreshExampleType: String { 12 | case defaulttype = "Default" 13 | case meituan = "美团网 (Meituan.com)" 14 | case wechat = "WeChat" 15 | case textView = "TextView" 16 | case day = "Day" 17 | case collectionView = "CollectionView" 18 | } 19 | 20 | public enum ESRefreshExampleListType { 21 | case tableview, collectionview 22 | } 23 | 24 | public class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { 25 | @IBOutlet weak var tableView: UITableView! 26 | 27 | public var listType: ESRefreshExampleListType = .tableview 28 | 29 | public let dataArray: [ESRefreshExampleType] = [ 30 | .defaulttype, 31 | .meituan, 32 | .wechat, 33 | .textView, 34 | .day, 35 | .collectionView] 36 | 37 | public override func viewDidLoad() { 38 | super.viewDidLoad() 39 | let appearance = UIBarButtonItem.appearance() 40 | appearance.setBackButtonTitlePositionAdjustment(UIOffset.init(horizontal: 0.0, vertical: -60), for: .default) 41 | 42 | self.navigationController?.navigationBar.isTranslucent = false 43 | self.navigationController?.navigationBar.barTintColor = UIColor.init(red: 250/255.0, green: 250/255.0, blue: 250/255.0, alpha: 0.8) 44 | self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor : UIColor.init(red: 38/255.0, green: 38/255.0, blue: 38/255.0, alpha: 1.0), NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16.0)] 45 | self.navigationController?.navigationBar.tintColor = UIColor.init(red: 38/255.0, green: 38/255.0, blue: 38/255.0, alpha: 1.0) 46 | self.navigationItem.title = "Example" 47 | 48 | self.tableView.register(UINib.init(nibName: "ListTableViewCell", bundle: nil), forCellReuseIdentifier: "ListTableViewCell") 49 | } 50 | 51 | public func selectRow(_ element: ESRefreshExampleType) { 52 | var vc: UIViewController! 53 | switch element { 54 | case .defaulttype: 55 | vc = ESRefreshTableViewController.init(style: .plain) 56 | if let vc = vc as? ESRefreshTableViewController { 57 | vc.type = .defaulttype 58 | } 59 | case .meituan: 60 | vc = ESRefreshTableViewController.init(style: .plain) 61 | if let vc = vc as? ESRefreshTableViewController { 62 | vc.type = .meituan 63 | } 64 | case .wechat: 65 | vc = ESRefreshTableViewController.init(style: .plain) 66 | if let vc = vc as? ESRefreshTableViewController { 67 | vc.type = .wechat 68 | } 69 | case .textView: 70 | vc = TextViewController.init() 71 | case .day: 72 | vc = ESRefreshTableViewController.init(style: .plain) 73 | case .collectionView: 74 | vc = CollectionViewController() 75 | } 76 | vc.title = element.rawValue 77 | self.navigationController?.pushViewController(vc, animated: true) 78 | } 79 | 80 | // MARK: UITableViewDataSource 81 | public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 82 | return dataArray.count 83 | } 84 | 85 | public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 86 | return 54.0 87 | } 88 | 89 | public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 90 | let cell = self.tableView.dequeueReusableCell(withIdentifier: "ListTableViewCell") 91 | if let cell = cell as? ListTableViewCell { 92 | cell.titleLabel.text = "\(indexPath.row + 1). " + dataArray[indexPath.row].rawValue 93 | } 94 | return cell! 95 | } 96 | 97 | public func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { 98 | return 0.5 99 | } 100 | 101 | public func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { 102 | let view = UIView.init(frame: CGRect.init(x: 0, y: 0, width: tableView.bounds.size.width, height: 0.5)) 103 | view.backgroundColor = UIColor.lightGray 104 | return view 105 | } 106 | 107 | public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 108 | tableView.deselectRow(at: indexPath as IndexPath, animated: true) 109 | self.selectRow(dataArray[indexPath.row]) 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/WebViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebViewController.swift 3 | // ESPullToRefreshExample 4 | // 5 | // Created by lihao on 16/5/6. 6 | // Copyright © 2016年 egg swift. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class WebViewController: UIViewController, UIWebViewDelegate { 12 | 13 | @IBOutlet weak var networkTipsButton: UIButton! 14 | @IBOutlet weak var webViewXib: UIWebView! 15 | var webView: UIWebView! 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | if let _ = self.webViewXib { 21 | self.webView = self.webViewXib 22 | } else { 23 | self.webView = UIWebView() 24 | self.webView.frame = self.view.bounds 25 | self.view.addSubview(self.webView!) 26 | } 27 | 28 | self.webView!.delegate = self 29 | 30 | let url = "https://github.com/eggswift" 31 | self.title = "egg swift" 32 | let request = NSURLRequest.init(url: NSURL(string: url)! as URL) 33 | 34 | self.webView.scrollView.es.addPullToRefresh { 35 | [weak self] in 36 | self?.webView.loadRequest(request as URLRequest) 37 | } 38 | self.webView.scrollView.es.startPullToRefresh() 39 | } 40 | 41 | func webViewDidFinishLoad(_ webView: UIWebView) { 42 | self.webView.scrollView.es.stopPullToRefresh() 43 | self.webView.scrollView.bounces = true 44 | self.webView.scrollView.alwaysBounceVertical = true 45 | } 46 | 47 | func webView(_ webView: UIWebView, didFailLoadWithError error: Error) { 48 | self.webView.scrollView.es.stopPullToRefresh(ignoreDate: true) 49 | self.networkTipsButton.isHidden = false 50 | } 51 | 52 | @IBAction func networkRetryAction(_ sender: AnyObject) { 53 | self.networkTipsButton.isHidden = true 54 | UIView.performWithoutAnimation { 55 | self.webView.scrollView.es.startPullToRefresh() 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /ESPullToRefreshExample/ESPullToRefreshExample/WebViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 lihao 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 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | // 3 | // Package.swift 4 | // 5 | // Created by egg swift on 16/4/7. 6 | // Copyright (c) 2013-2016 ESPullToRefresh 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | import PackageDescription 27 | 28 | let package = Package( 29 | name: "ESPullToRefresh", 30 | platforms: [.iOS(.v8)], 31 | products: [ 32 | .library( 33 | name: "ESPullToRefresh", 34 | targets: ["ESPullToRefresh"]) 35 | ], 36 | targets: [ 37 | .target( 38 | name: "ESPullToRefresh", 39 | dependencies: [], 40 | path: "./Sources") 41 | ] 42 | ) 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | [![Travis](https://travis-ci.org/eggswift/pull-to-refresh.svg?branch=master)](https://travis-ci.org/eggswift/pull-to-refresh) 5 | [![CocoaPods](https://img.shields.io/cocoapods/v/ESPullToRefresh.svg)](http://cocoapods.org/pods/pull-to-refresh) 6 | [![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 7 | [![Swift v3](https://img.shields.io/badge/Swift-v3-orange.svg?style=flat)](https://developer.apple.com/swift/) 8 | [![Twitter](https://img.shields.io/badge/Twitter-@lihao_iOS-blue.svg?style=flat)](https://twitter.com/lihao_iOS) 9 | [![Twitter](https://img.shields.io/badge/Weibo-@李昊_____-orange.svg?style=flat)](http://weibo.com/5120522686/profile?rightmod=1&wvr=6&mod=personinfo&is_all=1) 10 | 11 | ### [中文介绍](README_CN.md) 12 | 13 | **ESPullToRefresh** is an easy-to-use component that give **pull-to-refresh** and **infinite-scrolling** implemention for developers. By extension to UIScrollView, you can easily add pull-to-refresh and infinite-scrolling for any subclass of UIScrollView. If you want to customize its UI style, you just need conform the specified protocol. 14 | 15 | 16 | ## Requirements 17 | 18 | * Xcode 8 or later 19 | * iOS 8.0 or later 20 | * ARC 21 | * Swift 5.0 or later 22 | 23 | ## Features 24 | 25 | * Support `UIScrollView` and its subclasses `UICollectionView` `UITableView` `UITextView` 26 | * Pull-Down to refresh and Pull-Up to load more 27 | * Support customize your own style(s) 28 | 29 | ## Demo 30 | 31 | Download and run the ESPullToRefreshExample project in Xcode to see ESPullToRefresh in action. 32 | 33 | 34 | ## Installation 35 | 36 | ### CocoaPods 37 | 38 | ``` ruby 39 | pod "ESPullToRefresh" 40 | ``` 41 | 42 | ### Carthage 43 | 44 | ```ruby 45 | github "eggswift/pull-to-refresh" 46 | ``` 47 | 48 | ### Manually 49 | 50 | ``` ruby 51 | git clone https://github.com/eggswift/pull-to-refresh.git 52 | open ESPullToRefresh 53 | ``` 54 | 55 | ## Usage 56 | 57 | ### Default style: 58 | 59 | 60 | ![](example_default.gif) 61 | 62 | 63 | 64 | Add `ESPullToRefresh` to your project 65 | 66 | ```swift 67 | import ESPullToRefresh 68 | ``` 69 | 70 | Add default pull-to-refresh 71 | 72 | ``` swift 73 | self.tableView.es.addPullToRefresh { 74 | [unowned self] in 75 | /// Do anything you want... 76 | /// ... 77 | /// Stop refresh when your job finished, it will reset refresh footer if completion is true 78 | self.tableView.es.stopPullToRefresh(completion: true) 79 | /// Set ignore footer or not 80 | self.tableView.es.stopPullToRefresh(completion: true, ignoreFooter: false) 81 | } 82 | ``` 83 | 84 | Add default infinite-scrolling 85 | ``` swift 86 | self.tableView.es.addInfiniteScrolling { 87 | [unowned self] in 88 | /// Do anything you want... 89 | /// ... 90 | /// If common end 91 | self.tableView.es.stopLoadingMore() 92 | /// If no more data 93 | self.tableView.es.noticeNoMoreData() 94 | } 95 | ``` 96 | 97 | 98 | ### Customize Style 99 | 100 | #### As effect: 101 | 102 | ![](example_meituan.gif) 103 | 104 | **PS: Load effect is from MeiTuan iOS app.** 105 | 106 | ![](example_wechat.gif) 107 | 108 | 109 | Customize refresh need conform the **ESRefreshProtocol** and **ESRefreshAnimatorProtocol** protocol. 110 | 111 | Add customize pull-to-refresh 112 | 113 | ``` swift 114 | func es.addPullToRefresh(animator animator: protocol, handler: ESRefreshHandler) 115 | ``` 116 | 117 | Add customize infinite-scrolling 118 | 119 | ``` swift 120 | func es.addInfiniteScrolling(animator animator: protocol, handler: ESRefreshHandler) 121 | ``` 122 | 123 | ### Espried and auto refresh 124 | 125 | ESPullToRefresh support for the latest expiration time and the cache refresh time, You need set an `refreshIdentifier` to your UIScrollView. 126 | ``` swift 127 | scrollView.refreshIdentifier = "Your Identifier" // Set refresh identifier 128 | scrollView.expriedTimeInterval = 20.0 // Set the expiration interval 129 | ``` 130 | You can use `es.autoPullToRefresh()` method, when the time over the last refresh interval expires automatically refreshed. 131 | ``` swift 132 | scrollView.es.autoPullToRefresh() 133 | 134 | let expried = scrollView.espried // expired or not 135 | ``` 136 | 137 | 138 | ### Remove 139 | 140 | ``` swift 141 | func es.removeRefreshHeader() 142 | func es.removeRefreshFooter() 143 | ``` 144 | 145 | 146 | ## Sponsor 147 | 148 | You can support the project by checking out our sponsor page. It takes only one click: 149 | 150 | git-ad 151 |
This advert was placed by GitAds 152 | 153 | 154 | ## About 155 | 156 | ESPullToRefresh is developed and maintained by [Vincent Li](mailto:lihao_iOS@hotmail.com). If you have any questions or issues in using ESPullToRefresh, welcome to [issue](https://github.com/eggswift/pull-to-refresh/issues).
157 | If you want to contribute to ESPullToRefresh, Please submit [Pull Request](https://github.com/eggswift/pull-to-refresh/pulls), I will deal with it as soon as possible.
158 | 159 | [![Twitter URL](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=https://github.com/eggswift/pull-to-refresh) 160 | [![Twitter Follow](https://img.shields.io/twitter/follow/lihao_ios.svg?style=social)](https://twitter.com/lihao_iOS) 161 | 162 | 163 | ## License 164 | 165 | The MIT License (MIT) 166 | 167 | Copyright (c) 2013-2020 eggswift 168 | 169 | Permission is hereby granted, free of charge, to any person obtaining a copy 170 | of this software and associated documentation files (the "Software"), to deal 171 | in the Software without restriction, including without limitation the rights 172 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 173 | copies of the Software, and to permit persons to whom the Software is 174 | furnished to do so, subject to the following conditions: 175 | 176 | The above copyright notice and this permission notice shall be included in all 177 | copies or substantial portions of the Software. 178 | 179 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 180 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 181 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 182 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 183 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 184 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 185 | SOFTWARE. 186 | 187 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | [![Travis](https://travis-ci.org/eggswift/pull-to-refresh.svg?branch=master)](https://travis-ci.org/eggswift/pull-to-refresh) 5 | [![CocoaPods](https://img.shields.io/cocoapods/v/ESPullToRefresh.svg)](http://cocoapods.org/pods/pull-to-refresh) 6 | [![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 7 | [![Swift v3](https://img.shields.io/badge/Swift-v3-orange.svg?style=flat)](https://developer.apple.com/swift/) 8 | [![Twitter](https://img.shields.io/badge/Twitter-@lihao_iOS-blue.svg?style=flat)](https://twitter.com/lihao_iOS) 9 | [![Twitter](https://img.shields.io/badge/Weibo-@李昊_____-orange.svg?style=flat)](http://weibo.com/5120522686/profile?rightmod=1&wvr=6&mod=personinfo&is_all=1) 10 | 11 | ### [For English](README.md) 12 | 13 | **ESPullToRefresh**是一个非常易于开发者使用的下拉刷新和加载更多组件。通过一个UIScrollView的扩展,可以轻松为UIScrollView的所有子类添加下拉刷新功能。 如果你想定制组件的UI样式,只要实现指定的协议方法即可。 14 | 15 | 16 | ## 支持环境 17 | 18 | * Xcode 8 or later 19 | * iOS 8.0 or later 20 | * ARC 21 | * Swift 5.0 or later 22 | 23 | ## 特性 24 | 25 | * 支持UIScrollView及其子类UICollectionView、UITableView、UIWebView等; 26 | * 支持下拉刷新和上拉加载更多; 27 | * 支持定制自己所需的样式; 28 | * 支持刷新时间缓存,设置过期时间并策略刷新。 29 | 30 | ## Demo 31 | 32 | 下载后运行ESPullToRefreshExample工程,你可以看到一些使用ESPullToRefresh实现的自定义下拉刷新和加载更多例子。 33 | 34 | 35 | ## 如何安装 36 | 37 | ### CocoaPods 38 | 39 | ``` ruby 40 | pod "ESPullToRefresh" 41 | ``` 42 | 43 | ### Carthage 44 | 45 | ```ruby 46 | github "eggswift/pull-to-refresh" 47 | ``` 48 | 49 | ### 手动安装 50 | 51 | ``` ruby 52 | git clone https://github.com/eggswift/pull-to-refresh.git 53 | open ESPullToRefresh 54 | ``` 55 | 56 | ## 开始使用 57 | 58 | ### 使用默认样式: 59 | 60 | #### 效果如下: 61 | 62 | ![](example_default.gif) 63 | 64 | 65 | 66 | 将ESPullToRefresh导入至你的工程 67 | 68 | ```swift 69 | import ESPullToRefresh 70 | ``` 71 | 72 | 设置默认下拉刷新组件 73 | 74 | ```swift 75 | self.tableView.es.addPullToRefresh { 76 | [unowned self] in 77 | /// 在这里做刷新相关事件 78 | /// ... 79 | /// 如果你的刷新事件成功,设置completion自动重置footer的状态 80 |    self.tableView.es.stopPullToRefresh(completion: true) 81 | /// 设置ignoreFooter来处理不需要显示footer的情况 82 |    self.tableView.es.stopPullToRefresh(completion: true, ignoreFooter: false) 83 | } 84 | ``` 85 | 86 | 设置默认加载更多组件 87 | ``` swift 88 | self.tableView.es.addInfiniteScrolling { 89 | [unowned self] in 90 | /// 在这里做加载更多相关事件 91 | /// ... 92 | /// 如果你的加载更多事件成功,调用es_stopLoadingMore()重置footer状态 93 |    self.tableView.es.stopLoadingMore() 94 | /// 通过es_noticeNoMoreData()设置footer暂无数据状态 95 |    self.tableView.es.noticeNoMoreData() 96 | } 97 | ``` 98 | 99 | 100 | ### 使用自定义样式 101 | 102 | #### 效果如下: 103 | 104 | ![](example_meituan.gif) 105 | 106 | 注: 加载动画资源来自美团 iOS app。 107 | 108 | ![](example_wechat.gif) 109 | 110 | 111 | **ESPullToRefresh**通过**ESRefreshProtocol**和**ESRefreshAnimatorProtocol**来约束刷新组件的使用,自定义的组件必须遵守这两个协议,并实现协议中的方法。 112 | 113 | 设置自定义下拉刷新组件 114 | ``` swift 115 | func es.addPullToRefresh(animator animator: protocol, handler: ESRefreshHandler) 116 | ``` 117 | 118 | 设置自定义加载更多组件 119 | ``` swift 120 | func es.addInfiniteScrolling(animator animator: protocol, handler: ESRefreshHandler) 121 | ``` 122 | 123 | ### 设置过期时间和自动刷新 124 | 125 | ESPullToRefresh支持最近刷新时间和过期时间缓存,您需要为UIScrollView子类设置`refreshIdentifier`标示。 126 | ``` swift 127 | scrollView.refreshIdentifier = "Your Identifier" // 设置当前ScrollView的标识 128 | scrollView.expriedTimeInterval = 20.0 // 设置过期时间间隔 129 | ``` 130 | 你可以通过`es.autoPullToRefresh()` 方法,当上次刷新时间超过过期时间间隔时自动刷新。 131 | ``` swift 132 | scrollView.es.autoPullToRefresh() 133 | 134 | let expried = scrollView.espried // 获取是否过期 135 | ``` 136 | 137 | 138 | ### 移除方法 139 | 140 | ``` swift 141 | func es.removeRefreshHeader() 142 | func es.removeRefreshFooter() 143 | ``` 144 | 145 | 146 | ## 赞助 147 | 148 | 您可以通过查看我们的赞助商页面来支持该项目。 只需单击一下即可: 149 | 150 | git-ad 151 |
这则广告来自 GitAds 152 | 153 | 154 | ## 关于 155 | 156 | ESPullToRefresh[lihao](mailto:lihao_iOS@hotmail.com)开发和维护。如果你在使用过程中遇到什么疑问或任何问题,欢迎提交 [issue](https://github.com/eggswift/pull-to-refresh/issues) 随时交流。
157 | 如果你想为ESPullToRefresh输出代码,请提交 [Pull Request](https://github.com/eggswift/pull-to-refresh/pulls),我会尽可能快的去处理。
158 | 159 | [![Twitter URL](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=https://github.com/eggswift/pull-to-refresh) 160 | [![Twitter Follow](https://img.shields.io/twitter/follow/lihao_ios.svg?style=social)](https://twitter.com/lihao_iOS) 161 | 162 | 163 | ## 使用许可 164 | 165 | The MIT License (MIT) 166 | 167 | Copyright (c) 2013-2020 eggswift (https://github.com/eggswift/pull-to-refresh) 168 | 169 | Permission is hereby granted, free of charge, to any person obtaining a copy 170 | of this software and associated documentation files (the "Software"), to deal 171 | in the Software without restriction, including without limitation the rights 172 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 173 | copies of the Software, and to permit persons to whom the Software is 174 | furnished to do so, subject to the following conditions: 175 | 176 | The above copyright notice and this permission notice shall be included in all 177 | copies or substantial portions of the Software. 178 | 179 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 180 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 181 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 182 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 183 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 184 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 185 | SOFTWARE. 186 | 187 | -------------------------------------------------------------------------------- /Sources/Animator/Base.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "Pull to refresh" = "Pull to refresh"; 2 | "Release to refresh" = "Release to refresh"; 3 | "Loading..." = "Loading..."; 4 | "Loading more" = "Loading more"; 5 | "No more data" = "No more data"; 6 | -------------------------------------------------------------------------------- /Sources/Animator/ESRefreshFooterAnimator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ESRefreshFooterAnimator.swift 3 | // 4 | // Created by egg swift on 16/4/7. 5 | // Copyright (c) 2013-2016 ESPullToRefresh (https://github.com/eggswift/pull-to-refresh) 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | import UIKit 27 | 28 | open class ESRefreshFooterAnimator: UIView, ESRefreshProtocol, ESRefreshAnimatorProtocol { 29 | 30 | open var loadingMoreDescription: String = NSLocalizedString("Loading more", comment: "") 31 | open var noMoreDataDescription: String = NSLocalizedString("No more data", comment: "") 32 | open var loadingDescription: String = NSLocalizedString("Loading...", comment: "") 33 | 34 | open var view: UIView { return self } 35 | open var duration: TimeInterval = 0.3 36 | open var insets: UIEdgeInsets = UIEdgeInsets.zero 37 | open var trigger: CGFloat = 42.0 38 | open var executeIncremental: CGFloat = 42.0 39 | open var state: ESRefreshViewState = .pullToRefresh 40 | 41 | fileprivate let titleLabel: UILabel = { 42 | let label = UILabel.init(frame: CGRect.zero) 43 | label.font = UIFont.systemFont(ofSize: 14.0) 44 | label.textColor = UIColor.init(white: 160.0 / 255.0, alpha: 1.0) 45 | label.textAlignment = .center 46 | return label 47 | }() 48 | 49 | fileprivate let indicatorView: UIActivityIndicatorView = { 50 | let indicatorView = UIActivityIndicatorView.init(style: .gray) 51 | indicatorView.isHidden = true 52 | return indicatorView 53 | }() 54 | 55 | public override init(frame: CGRect) { 56 | super.init(frame: frame) 57 | titleLabel.text = loadingMoreDescription 58 | addSubview(titleLabel) 59 | addSubview(indicatorView) 60 | } 61 | 62 | public required init(coder aDecoder: NSCoder) { 63 | fatalError("init(coder:) has not been implemented") 64 | } 65 | 66 | open func refreshAnimationBegin(view: ESRefreshComponent) { 67 | indicatorView.startAnimating() 68 | titleLabel.text = loadingDescription 69 | indicatorView.isHidden = false 70 | } 71 | 72 | open func refreshAnimationEnd(view: ESRefreshComponent) { 73 | indicatorView.stopAnimating() 74 | titleLabel.text = loadingMoreDescription 75 | indicatorView.isHidden = true 76 | } 77 | 78 | open func refresh(view: ESRefreshComponent, progressDidChange progress: CGFloat) { 79 | // do nothing 80 | } 81 | 82 | open func refresh(view: ESRefreshComponent, stateDidChange state: ESRefreshViewState) { 83 | guard self.state != state else { 84 | return 85 | } 86 | self.state = state 87 | 88 | switch state { 89 | case .refreshing, .autoRefreshing : 90 | titleLabel.text = loadingDescription 91 | break 92 | case .noMoreData: 93 | titleLabel.text = noMoreDataDescription 94 | break 95 | case .pullToRefresh: 96 | titleLabel.text = loadingMoreDescription 97 | break 98 | default: 99 | break 100 | } 101 | self.setNeedsLayout() 102 | } 103 | 104 | open override func layoutSubviews() { 105 | super.layoutSubviews() 106 | let s = self.bounds.size 107 | let w = s.width 108 | let h = s.height 109 | 110 | titleLabel.sizeToFit() 111 | titleLabel.center = CGPoint.init(x: w / 2.0, y: h / 2.0 - 5.0) 112 | indicatorView.center = CGPoint.init(x: titleLabel.frame.origin.x - 18.0, y: titleLabel.center.y) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Sources/Animator/ESRefreshHeaderAnimator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ESRefreshHeaderView.swift 3 | // 4 | // Created by egg swift on 16/4/7. 5 | // Copyright (c) 2013-2016 ESPullToRefresh (https://github.com/eggswift/pull-to-refresh) 6 | // Icon from http://www.iconfont.cn 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | import Foundation 28 | import QuartzCore 29 | import UIKit 30 | 31 | open class ESRefreshHeaderAnimator: UIView, ESRefreshProtocol, ESRefreshAnimatorProtocol, ESRefreshImpactProtocol { 32 | open var pullToRefreshDescription = NSLocalizedString("Pull to refresh", comment: "") { 33 | didSet { 34 | if pullToRefreshDescription != oldValue { 35 | titleLabel.text = pullToRefreshDescription; 36 | } 37 | } 38 | } 39 | open var releaseToRefreshDescription = NSLocalizedString("Release to refresh", comment: "") 40 | open var loadingDescription = NSLocalizedString("Loading...", comment: "") 41 | 42 | open var view: UIView { return self } 43 | open var insets: UIEdgeInsets = UIEdgeInsets.zero 44 | open var trigger: CGFloat = 60.0 45 | open var executeIncremental: CGFloat = 60.0 46 | open var state: ESRefreshViewState = .pullToRefresh 47 | 48 | fileprivate let imageView: UIImageView = { 49 | let imageView = UIImageView.init() 50 | let frameworkBundle = Bundle(for: ESRefreshAnimator.self) 51 | if /* CocoaPods static */ let path = frameworkBundle.path(forResource: "ESPullToRefresh", ofType: "bundle"),let bundle = Bundle(path: path) { 52 | imageView.image = UIImage(named: "icon_pull_to_refresh_arrow", in: bundle, compatibleWith: nil) 53 | }else if /* Carthage */ let bundle = Bundle.init(identifier: "com.eggswift.ESPullToRefresh") { 54 | imageView.image = UIImage(named: "icon_pull_to_refresh_arrow", in: bundle, compatibleWith: nil) 55 | } else if /* CocoaPods */ let bundle = Bundle.init(identifier: "org.cocoapods.ESPullToRefresh") { 56 | imageView.image = UIImage(named: "ESPullToRefresh.bundle/icon_pull_to_refresh_arrow", in: bundle, compatibleWith: nil) 57 | } else /* Manual */ { 58 | imageView.image = UIImage(named: "icon_pull_to_refresh_arrow") 59 | } 60 | return imageView 61 | }() 62 | 63 | fileprivate let titleLabel: UILabel = { 64 | let label = UILabel.init(frame: CGRect.zero) 65 | label.font = UIFont.systemFont(ofSize: 14.0) 66 | label.textColor = UIColor.init(white: 0.625, alpha: 1.0) 67 | label.textAlignment = .left 68 | return label 69 | }() 70 | 71 | fileprivate let indicatorView: UIActivityIndicatorView = { 72 | let indicatorView = UIActivityIndicatorView.init(style: .gray) 73 | indicatorView.isHidden = true 74 | return indicatorView 75 | }() 76 | 77 | public override init(frame: CGRect) { 78 | super.init(frame: frame) 79 | titleLabel.text = pullToRefreshDescription 80 | self.addSubview(imageView) 81 | self.addSubview(titleLabel) 82 | self.addSubview(indicatorView) 83 | } 84 | 85 | public required init(coder aDecoder: NSCoder) { 86 | fatalError("init(coder:) has not been implemented") 87 | } 88 | 89 | open func refreshAnimationBegin(view: ESRefreshComponent) { 90 | indicatorView.startAnimating() 91 | indicatorView.isHidden = false 92 | imageView.isHidden = true 93 | titleLabel.text = loadingDescription 94 | imageView.transform = CGAffineTransform(rotationAngle: 0.000001 - CGFloat.pi) 95 | } 96 | 97 | open func refreshAnimationEnd(view: ESRefreshComponent) { 98 | indicatorView.stopAnimating() 99 | indicatorView.isHidden = true 100 | imageView.isHidden = false 101 | titleLabel.text = pullToRefreshDescription 102 | imageView.transform = CGAffineTransform.identity 103 | } 104 | 105 | open func refresh(view: ESRefreshComponent, progressDidChange progress: CGFloat) { 106 | // Do nothing 107 | 108 | } 109 | 110 | open func refresh(view: ESRefreshComponent, stateDidChange state: ESRefreshViewState) { 111 | guard self.state != state else { 112 | return 113 | } 114 | self.state = state 115 | 116 | switch state { 117 | case .refreshing, .autoRefreshing: 118 | titleLabel.text = loadingDescription 119 | self.setNeedsLayout() 120 | break 121 | case .releaseToRefresh: 122 | titleLabel.text = releaseToRefreshDescription 123 | self.setNeedsLayout() 124 | self.impact() 125 | UIView.animate(withDuration: 0.2, delay: 0.0, options: UIView.AnimationOptions(), animations: { 126 | [weak self] in 127 | self?.imageView.transform = CGAffineTransform(rotationAngle: 0.000001 - CGFloat.pi) 128 | }) { (animated) in } 129 | break 130 | case .pullToRefresh: 131 | titleLabel.text = pullToRefreshDescription 132 | self.setNeedsLayout() 133 | UIView.animate(withDuration: 0.2, delay: 0.0, options: UIView.AnimationOptions(), animations: { 134 | [weak self] in 135 | self?.imageView.transform = CGAffineTransform.identity 136 | }) { (animated) in } 137 | break 138 | default: 139 | break 140 | } 141 | } 142 | 143 | open override func layoutSubviews() { 144 | super.layoutSubviews() 145 | let s = self.bounds.size 146 | let w = s.width 147 | let h = s.height 148 | 149 | UIView.performWithoutAnimation { 150 | titleLabel.sizeToFit() 151 | titleLabel.center = CGPoint.init(x: w / 2.0, y: h / 2.0) 152 | indicatorView.center = CGPoint.init(x: titleLabel.frame.origin.x - 16.0, y: h / 2.0) 153 | imageView.frame = CGRect.init(x: titleLabel.frame.origin.x - 28.0, y: (h - 18.0) / 2.0, width: 18.0, height: 18.0) 154 | } 155 | } 156 | 157 | } 158 | -------------------------------------------------------------------------------- /Sources/Animator/icon_pull_to_refresh_arrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/Sources/Animator/icon_pull_to_refresh_arrow@2x.png -------------------------------------------------------------------------------- /Sources/Animator/zh-Hans.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "Pull to refresh" = "下拉刷新"; 2 | "Release to refresh" = "松开刷新"; 3 | "Loading..." = "加载中"; 4 | "Loading more" = "正在加载更多"; 5 | "No more data" = "没有更多的了"; 6 | -------------------------------------------------------------------------------- /Sources/Animator/zh-Hant.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "Pull to refresh" = "下拉刷新"; 2 | "Release to refresh" = "松開刷新"; 3 | "Loading..." = "載入中"; 4 | "Loading more" = "正在載入更多"; 5 | "No more data" = "沒有更多的了"; 6 | -------------------------------------------------------------------------------- /Sources/ES.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ES.swift 3 | // ESPullToRefreshExample 4 | // 5 | // Created by 木瓜 on 2017/4/25. 6 | // Copyright © 2017年 egg swift. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | public protocol ESExtensionsProvider: class { 13 | associatedtype CompatibleType 14 | var es: CompatibleType { get } 15 | } 16 | 17 | extension ESExtensionsProvider { 18 | /// A proxy which hosts reactive extensions for `self`. 19 | public var es: ES { 20 | return ES(self) 21 | } 22 | 23 | } 24 | 25 | public struct ES { 26 | public let base: Base 27 | 28 | // Construct a proxy. 29 | // 30 | // - parameters: 31 | // - base: The object to be proxied. 32 | fileprivate init(_ base: Base) { 33 | self.base = base 34 | } 35 | } 36 | 37 | // 38 | extension UIScrollView: ESExtensionsProvider {} 39 | 40 | 41 | -------------------------------------------------------------------------------- /Sources/ESPullToRefresh+Manager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ESPullToRefresh+Manager.swift 3 | // 4 | // Created by egg swift on 16/4/7. 5 | // Copyright (c) 2013-2016 ESPullToRefresh (https://github.com/eggswift/pull-to-refresh) 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | import Foundation 27 | 28 | open class ESRefreshDataManager { 29 | 30 | static let sharedManager = ESRefreshDataManager.init() 31 | 32 | static let lastRefreshKey: String = "com.espulltorefresh.lastRefreshKey" 33 | static let expiredTimeIntervalKey: String = "com.espulltorefresh.expiredTimeIntervalKey" 34 | open var lastRefreshInfo = [String: Date]() 35 | open var expiredTimeIntervalInfo = [String: TimeInterval]() 36 | 37 | public required init() { 38 | if let lastRefreshInfo = UserDefaults.standard.dictionary(forKey: ESRefreshDataManager.lastRefreshKey) as? [String: Date] { 39 | self.lastRefreshInfo = lastRefreshInfo 40 | } 41 | if let expiredTimeIntervalInfo = UserDefaults.standard.dictionary(forKey: ESRefreshDataManager.expiredTimeIntervalKey) as? [String: TimeInterval] { 42 | self.expiredTimeIntervalInfo = expiredTimeIntervalInfo 43 | } 44 | } 45 | 46 | open func date(forKey key: String) -> Date? { 47 | let date = lastRefreshInfo[key] 48 | return date 49 | } 50 | 51 | open func setDate(_ date: Date?, forKey key: String) { 52 | lastRefreshInfo[key] = date 53 | UserDefaults.standard.set(lastRefreshInfo, forKey: ESRefreshDataManager.lastRefreshKey) 54 | UserDefaults.standard.synchronize() 55 | } 56 | 57 | open func expiredTimeInterval(forKey key: String) -> TimeInterval? { 58 | let interval = expiredTimeIntervalInfo[key] 59 | return interval 60 | } 61 | 62 | open func setExpiredTimeInterval(_ interval: TimeInterval?, forKey key: String) { 63 | expiredTimeIntervalInfo[key] = interval 64 | UserDefaults.standard.set(expiredTimeIntervalInfo, forKey: ESRefreshDataManager.expiredTimeIntervalKey) 65 | UserDefaults.standard.synchronize() 66 | } 67 | 68 | open func isExpired(forKey key: String) -> Bool { 69 | guard let date = date(forKey: key) else { 70 | return true 71 | } 72 | guard let interval = expiredTimeInterval(forKey: key) else { 73 | return false 74 | } 75 | if date.timeIntervalSinceNow < -interval { 76 | return true // Expired 77 | } 78 | return false 79 | } 80 | 81 | open func isExpired(forKey key: String, block: ((Bool) -> ())?) { 82 | DispatchQueue.global().async { 83 | [weak self] in 84 | let result = self?.isExpired(forKey: key) ?? true 85 | DispatchQueue.main.async(execute: { 86 | block?(result) 87 | }) 88 | } 89 | } 90 | 91 | public static func clearAll() { 92 | self.clearLastRefreshInfo() 93 | self.clearExpiredTimeIntervalInfo() 94 | } 95 | 96 | public static func clearLastRefreshInfo() { 97 | UserDefaults.standard.set(nil, forKey: ESRefreshDataManager.lastRefreshKey) 98 | } 99 | 100 | public static func clearExpiredTimeIntervalInfo() { 101 | UserDefaults.standard.set(nil, forKey: ESRefreshDataManager.expiredTimeIntervalKey) 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /Sources/ESRefreshAnimator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ESRefreshAnimator.swift 3 | // 4 | // Created by egg swift on 16/4/7. 5 | // Copyright (c) 2013-2016 ESPullToRefresh (https://github.com/eggswift/pull-to-refresh) 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | import Foundation 27 | import UIKit 28 | 29 | open class ESRefreshAnimator: ESRefreshProtocol, ESRefreshAnimatorProtocol { 30 | // The view that called when component refresh, returns a custom view or self if 'self' is the customized views. 31 | open var view: UIView 32 | // Customized inset. 33 | open var insets: UIEdgeInsets 34 | // Refresh event is executed threshold required y offset, set a value greater than 0.0, the default is 60.0 35 | open var trigger: CGFloat = 60.0 36 | // Offset y refresh event executed by this parameter you can customize the animation to perform when you refresh the view of reservations height 37 | open var executeIncremental: CGFloat = 60.0 38 | // Current refresh state, default is .pullToRefresh 39 | open var state: ESRefreshViewState = .pullToRefresh 40 | 41 | public init() { 42 | view = UIView() 43 | insets = UIEdgeInsets.zero 44 | } 45 | 46 | open func refreshAnimationBegin(view: ESRefreshComponent) { 47 | /// Do nothing! 48 | } 49 | 50 | open func refreshAnimationWillEnd(view: ESRefreshComponent) { 51 | /// Do nothing! 52 | } 53 | 54 | open func refreshAnimationEnd(view: ESRefreshComponent) { 55 | /// Do nothing! 56 | } 57 | 58 | open func refresh(view: ESRefreshComponent, progressDidChange progress: CGFloat) { 59 | /// Do nothing! 60 | } 61 | 62 | open func refresh(view: ESRefreshComponent, stateDidChange state: ESRefreshViewState) { 63 | /// Do nothing! 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Sources/ESRefreshComponent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ESRefreshComponent.swift 3 | // 4 | // Created by egg swift on 16/4/7. 5 | // Copyright (c) 2013-2016 ESPullToRefresh (https://github.com/eggswift/pull-to-refresh) 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | import Foundation 27 | import UIKit 28 | 29 | public typealias ESRefreshHandler = (() -> ()) 30 | 31 | open class ESRefreshComponent: UIView { 32 | 33 | open weak var scrollView: UIScrollView? 34 | 35 | /// @param handler Refresh callback method 36 | open var handler: ESRefreshHandler? 37 | 38 | /// @param animator Animated view refresh controls, custom must comply with the following two protocol 39 | open var animator: (ESRefreshProtocol & ESRefreshAnimatorProtocol)! 40 | 41 | /// @param refreshing or not 42 | fileprivate var _isRefreshing = false 43 | open var isRefreshing: Bool { 44 | get { 45 | return self._isRefreshing 46 | } 47 | } 48 | 49 | /// @param auto refreshing or not 50 | fileprivate var _isAutoRefreshing = false 51 | open var isAutoRefreshing: Bool { 52 | get { 53 | return self._isAutoRefreshing 54 | } 55 | } 56 | 57 | /// @param tag observing 58 | fileprivate var isObservingScrollView = false 59 | fileprivate var isIgnoreObserving = false 60 | 61 | public override init(frame: CGRect) { 62 | super.init(frame: frame) 63 | autoresizingMask = [.flexibleLeftMargin, .flexibleWidth, .flexibleRightMargin] 64 | } 65 | 66 | public convenience init(frame: CGRect, handler: @escaping ESRefreshHandler) { 67 | self.init(frame: frame) 68 | self.handler = handler 69 | self.animator = ESRefreshAnimator.init() 70 | } 71 | 72 | public convenience init(frame: CGRect, handler: @escaping ESRefreshHandler, animator: ESRefreshProtocol & ESRefreshAnimatorProtocol) { 73 | self.init(frame: frame) 74 | self.handler = handler 75 | self.animator = animator 76 | } 77 | 78 | public required init?(coder aDecoder: NSCoder) { 79 | fatalError("init(coder:) has not been implemented") 80 | } 81 | 82 | deinit { 83 | removeObserver() 84 | } 85 | 86 | open override func willMove(toSuperview newSuperview: UIView?) { 87 | super.willMove(toSuperview: newSuperview) 88 | /// Remove observer from superview immediately 89 | self.removeObserver() 90 | DispatchQueue.main.async { [weak self, newSuperview] in 91 | /// Add observer to new superview in next runloop 92 | self?.addObserver(newSuperview) 93 | } 94 | } 95 | 96 | open override func didMoveToSuperview() { 97 | super.didMoveToSuperview() 98 | self.scrollView = self.superview as? UIScrollView 99 | if let _ = animator { 100 | let v = animator.view 101 | if v.superview == nil { 102 | let inset = animator.insets 103 | self.addSubview(v) 104 | v.frame = CGRect.init(x: inset.left, 105 | y: inset.right, 106 | width: self.bounds.size.width - inset.left - inset.right, 107 | height: self.bounds.size.height - inset.top - inset.bottom) 108 | v.autoresizingMask = [ 109 | .flexibleWidth, 110 | .flexibleTopMargin, 111 | .flexibleHeight, 112 | .flexibleBottomMargin 113 | ] 114 | } 115 | } 116 | } 117 | 118 | // MARK: - Action 119 | 120 | public final func startRefreshing(isAuto: Bool = false) -> Void { 121 | guard isRefreshing == false && isAutoRefreshing == false else { 122 | return 123 | } 124 | 125 | _isRefreshing = !isAuto 126 | _isAutoRefreshing = isAuto 127 | 128 | self.start() 129 | } 130 | 131 | public final func stopRefreshing() -> Void { 132 | guard isRefreshing == true || isAutoRefreshing == true else { 133 | return 134 | } 135 | 136 | self.stop() 137 | } 138 | 139 | public func start() { 140 | 141 | } 142 | 143 | public func stop() { 144 | _isRefreshing = false 145 | _isAutoRefreshing = false 146 | } 147 | 148 | // ScrollView contentSize change action 149 | public func sizeChangeAction(object: AnyObject?, change: [NSKeyValueChangeKey : Any]?) { 150 | 151 | } 152 | 153 | // ScrollView offset change action 154 | public func offsetChangeAction(object: AnyObject?, change: [NSKeyValueChangeKey : Any]?) { 155 | 156 | } 157 | 158 | } 159 | 160 | extension ESRefreshComponent /* KVO methods */ { 161 | 162 | fileprivate static var context = "ESRefreshKVOContext" 163 | fileprivate static let offsetKeyPath = "contentOffset" 164 | fileprivate static let contentSizeKeyPath = "contentSize" 165 | 166 | public func ignoreObserver(_ ignore: Bool = false) { 167 | if let scrollView = scrollView { 168 | scrollView.isScrollEnabled = !ignore 169 | } 170 | isIgnoreObserving = ignore 171 | } 172 | 173 | fileprivate func addObserver(_ view: UIView?) { 174 | if let scrollView = view as? UIScrollView, !isObservingScrollView { 175 | scrollView.addObserver(self, forKeyPath: ESRefreshComponent.offsetKeyPath, options: [.initial, .new], context: &ESRefreshComponent.context) 176 | scrollView.addObserver(self, forKeyPath: ESRefreshComponent.contentSizeKeyPath, options: [.initial, .new], context: &ESRefreshComponent.context) 177 | isObservingScrollView = true 178 | } 179 | } 180 | 181 | fileprivate func removeObserver() { 182 | if let scrollView = superview as? UIScrollView, isObservingScrollView { 183 | scrollView.removeObserver(self, forKeyPath: ESRefreshComponent.offsetKeyPath, context: &ESRefreshComponent.context) 184 | scrollView.removeObserver(self, forKeyPath: ESRefreshComponent.contentSizeKeyPath, context: &ESRefreshComponent.context) 185 | isObservingScrollView = false 186 | } 187 | } 188 | 189 | override open func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 190 | if context == &ESRefreshComponent.context { 191 | guard isUserInteractionEnabled == true && isHidden == false else { 192 | return 193 | } 194 | if keyPath == ESRefreshComponent.contentSizeKeyPath { 195 | if isIgnoreObserving == false { 196 | sizeChangeAction(object: object as AnyObject?, change: change) 197 | } 198 | } else if keyPath == ESRefreshComponent.offsetKeyPath { 199 | if isIgnoreObserving == false { 200 | offsetChangeAction(object: object as AnyObject?, change: change) 201 | } 202 | } 203 | } else { 204 | 205 | } 206 | } 207 | 208 | } 209 | 210 | -------------------------------------------------------------------------------- /Sources/ESRefreshProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ESRefreshProtocol.swift 3 | // 4 | // Created by egg swift on 16/4/7. 5 | // Copyright (c) 2013-2016 ESPullToRefresh (https://github.com/eggswift/pull-to-refresh) 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | import Foundation 27 | import UIKit 28 | 29 | public enum ESRefreshViewState { 30 | case pullToRefresh 31 | case releaseToRefresh 32 | case refreshing 33 | case autoRefreshing 34 | case noMoreData 35 | } 36 | 37 | /** 38 | * ESRefreshProtocol 39 | * Animation event handling callback protocol 40 | * You can customize the refresh or custom animation effects 41 | * Mutating is to be able to modify or enum struct variable in the method - http://swifter.tips/protocol-mutation/ by ONEVCAT 42 | */ 43 | public protocol ESRefreshProtocol { 44 | 45 | /** 46 | Refresh operation begins execution method 47 | You can refresh your animation logic here, it will need to start the animation each time a refresh 48 | */ 49 | mutating func refreshAnimationBegin(view: ESRefreshComponent) 50 | 51 | /** 52 | Refresh operation stop execution method 53 | Here you can reset your refresh control UI, such as a Stop UIImageView animations or some opened Timer refresh, etc., it will be executed once each time the need to end the animation 54 | */ 55 | mutating func refreshAnimationEnd(view: ESRefreshComponent) 56 | 57 | /** 58 | Pulling status callback , progress is the percentage of the current offset with trigger, and avoid doing too many tasks in this process so as not to affect the fluency. 59 | */ 60 | mutating func refresh(view: ESRefreshComponent, progressDidChange progress: CGFloat) 61 | 62 | mutating func refresh(view: ESRefreshComponent, stateDidChange state: ESRefreshViewState) 63 | } 64 | 65 | 66 | public protocol ESRefreshAnimatorProtocol { 67 | 68 | // The view that called when component refresh, returns a custom view or self if 'self' is the customized views. 69 | var view: UIView {get} 70 | 71 | // Customized inset. 72 | var insets: UIEdgeInsets {set get} 73 | 74 | // Refresh event is executed threshold required y offset, set a value greater than 0.0, the default is 60.0 75 | var trigger: CGFloat {set get} 76 | 77 | // Offset y refresh event executed by this parameter you can customize the animation to perform when you refresh the view of reservations height 78 | var executeIncremental: CGFloat {set get} 79 | 80 | // Current refresh state, default is .pullToRefresh 81 | var state: ESRefreshViewState {set get} 82 | 83 | } 84 | 85 | /** 86 | * ESRefreshImpacter 87 | * Support iPhone7/iPhone7 Plus or later feedback impact 88 | * You can confirm the ESRefreshImpactProtocol 89 | */ 90 | fileprivate class ESRefreshImpacter { 91 | static private var impacter: AnyObject? = { 92 | if #available(iOS 10.0, *) { 93 | if NSClassFromString("UIFeedbackGenerator") != nil { 94 | let generator = UIImpactFeedbackGenerator.init(style: .light) 95 | generator.prepare() 96 | return generator 97 | } 98 | } 99 | return nil 100 | }() 101 | 102 | static public func impact() -> Void { 103 | if #available(iOS 10.0, *) { 104 | if let impacter = impacter as? UIImpactFeedbackGenerator { 105 | impacter.impactOccurred() 106 | } 107 | } 108 | } 109 | } 110 | 111 | public protocol ESRefreshImpactProtocol {} 112 | public extension ESRefreshImpactProtocol { 113 | 114 | func impact() -> Void { 115 | ESRefreshImpacter.impact() 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /example_default.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/example_default.gif -------------------------------------------------------------------------------- /example_meituan.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/example_meituan.gif -------------------------------------------------------------------------------- /example_wechat.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/example_wechat.gif -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggswift/pull-to-refresh/6bfa290dcb98e0bb5e43bd9bcf51cafca1640cef/logo.png --------------------------------------------------------------------------------