├── .gitignore ├── EasyRefresher ├── Assets │ ├── .gitkeep │ ├── EasyRefresher.xcassets │ │ ├── Contents.json │ │ └── refresh_arrow_down.imageset │ │ │ ├── refresh_arrow_down.png │ │ │ └── Contents.json │ ├── zh-Hans.lproj │ │ └── Localizable.strings │ ├── zh-Hant.lproj │ │ └── Localizable.strings │ └── en.lproj │ │ └── Localizable.strings └── Classes │ ├── .gitkeep │ ├── RefreshStateful.swift │ ├── Refresh.swift │ ├── UserInterfacable.swift │ ├── Refreshable.swift │ ├── ScrollViewObservation.swift │ ├── Localized.swift │ ├── AppearanceRefreshFooter.swift │ ├── HasStateTitle.swift │ ├── RefreshStatefulView.swift │ ├── AutoRefreshFooter.swift │ ├── RefreshView.swift │ ├── RefreshFooter.swift │ ├── RefreshTrailer.swift │ ├── UIScrollView+Refresh.swift │ ├── RefreshHeader.swift │ └── RefreshComponent.swift ├── _Pods.xcodeproj ├── image.gif ├── image1.gif ├── Example ├── Pods │ ├── Target Support Files │ │ ├── EasyRefresher │ │ │ ├── EasyRefresher.modulemap │ │ │ ├── EasyRefresher-dummy.m │ │ │ ├── EasyRefresher-prefix.pch │ │ │ ├── EasyRefresher-umbrella.h │ │ │ ├── EasyRefresher.xcconfig │ │ │ ├── ResourceBundle-EasyRefresher-EasyRefresher-Info.plist │ │ │ └── EasyRefresher-Info.plist │ │ ├── Pods-EasyRefresher_Tests │ │ │ ├── Pods-EasyRefresher_Tests-acknowledgements.markdown │ │ │ ├── Pods-EasyRefresher_Tests.modulemap │ │ │ ├── Pods-EasyRefresher_Tests-dummy.m │ │ │ ├── Pods-EasyRefresher_Tests-umbrella.h │ │ │ ├── Pods-EasyRefresher_Tests.debug.xcconfig │ │ │ ├── Pods-EasyRefresher_Tests.release.xcconfig │ │ │ ├── Pods-EasyRefresher_Tests-Info.plist │ │ │ └── Pods-EasyRefresher_Tests-acknowledgements.plist │ │ └── Pods-EasyRefresher_Example │ │ │ ├── Pods-EasyRefresher_Example.modulemap │ │ │ ├── Pods-EasyRefresher_Example-dummy.m │ │ │ ├── Pods-EasyRefresher_Example-umbrella.h │ │ │ ├── Pods-EasyRefresher_Example.debug.xcconfig │ │ │ ├── Pods-EasyRefresher_Example.release.xcconfig │ │ │ ├── Pods-EasyRefresher_Example-Info.plist │ │ │ ├── Pods-EasyRefresher_Example-acknowledgements.markdown │ │ │ ├── Pods-EasyRefresher_Example-acknowledgements.plist │ │ │ └── Pods-EasyRefresher_Example-frameworks.sh │ ├── Manifest.lock │ ├── Local Podspecs │ │ └── EasyRefresher.podspec.json │ └── Pods.xcodeproj │ │ └── xcuserdata │ │ └── pircate.xcuserdatad │ │ └── xcschemes │ │ ├── EasyRefresher-EasyRefresher.xcscheme │ │ ├── Pods-EasyRefresher_Tests.xcscheme │ │ ├── Pods-EasyRefresher_Example.xcscheme │ │ └── EasyRefresher.xcscheme ├── EasyRefresher.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── EasyRefresher-Example.xcscheme ├── EasyRefresher │ ├── Images.xcassets │ │ ├── DianPing │ │ │ ├── dropdown_anim__0001.imageset │ │ │ │ ├── dropdown_anim__0001@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__0002.imageset │ │ │ │ ├── dropdown_anim__0002@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__0003.imageset │ │ │ │ ├── dropdown_anim__0003@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__0004.imageset │ │ │ │ ├── dropdown_anim__0004@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__0005.imageset │ │ │ │ ├── dropdown_anim__0005@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__0006.imageset │ │ │ │ ├── dropdown_anim__0006@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__0007.imageset │ │ │ │ ├── dropdown_anim__0007@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__0008.imageset │ │ │ │ ├── dropdown_anim__0008@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__0009.imageset │ │ │ │ ├── dropdown_anim__0009@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_loading_01.imageset │ │ │ │ ├── dropdown_loading_01@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_loading_02.imageset │ │ │ │ ├── dropdown_loading_02@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_loading_03.imageset │ │ │ │ ├── dropdown_loading_03@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00010.imageset │ │ │ │ ├── dropdown_anim__00010@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00011.imageset │ │ │ │ ├── dropdown_anim__00011@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00012.imageset │ │ │ │ ├── dropdown_anim__00012@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00013.imageset │ │ │ │ ├── dropdown_anim__00013@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00014.imageset │ │ │ │ ├── dropdown_anim__00014@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00015.imageset │ │ │ │ ├── dropdown_anim__00015@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00016.imageset │ │ │ │ ├── dropdown_anim__00016@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00017.imageset │ │ │ │ ├── dropdown_anim__00017@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00018.imageset │ │ │ │ ├── dropdown_anim__00018@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00019.imageset │ │ │ │ ├── dropdown_anim__00019@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00020.imageset │ │ │ │ ├── dropdown_anim__00020@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00021.imageset │ │ │ │ ├── dropdown_anim__00021@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00022.imageset │ │ │ │ ├── dropdown_anim__00022@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00023.imageset │ │ │ │ ├── dropdown_anim__00023@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00024.imageset │ │ │ │ ├── dropdown_anim__00024@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00025.imageset │ │ │ │ ├── dropdown_anim__00025@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00026.imageset │ │ │ │ ├── dropdown_anim__00026@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00027.imageset │ │ │ │ ├── dropdown_anim__00027@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00028.imageset │ │ │ │ ├── dropdown_anim__00028@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00029.imageset │ │ │ │ ├── dropdown_anim__00029@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00030.imageset │ │ │ │ ├── dropdown_anim__00030@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00031.imageset │ │ │ │ ├── dropdown_anim__00031@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00032.imageset │ │ │ │ ├── dropdown_anim__00032@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00033.imageset │ │ │ │ ├── dropdown_anim__00033@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00034.imageset │ │ │ │ ├── dropdown_anim__00034@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00035.imageset │ │ │ │ ├── dropdown_anim__00035@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00036.imageset │ │ │ │ ├── dropdown_anim__00036@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00037.imageset │ │ │ │ ├── dropdown_anim__00037@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00038.imageset │ │ │ │ ├── dropdown_anim__00038@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00039.imageset │ │ │ │ ├── dropdown_anim__00039@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00040.imageset │ │ │ │ ├── dropdown_anim__00040@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00041.imageset │ │ │ │ ├── dropdown_anim__00041@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00042.imageset │ │ │ │ ├── dropdown_anim__00042@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00043.imageset │ │ │ │ ├── dropdown_anim__00043@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00044.imageset │ │ │ │ ├── dropdown_anim__00044@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00045.imageset │ │ │ │ ├── dropdown_anim__00045@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00046.imageset │ │ │ │ ├── dropdown_anim__00046@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00047.imageset │ │ │ │ ├── dropdown_anim__00047@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00048.imageset │ │ │ │ ├── dropdown_anim__00048@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00049.imageset │ │ │ │ ├── dropdown_anim__00049@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00050.imageset │ │ │ │ ├── dropdown_anim__00050@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00051.imageset │ │ │ │ ├── dropdown_anim__00051@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00052.imageset │ │ │ │ ├── dropdown_anim__00052@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00053.imageset │ │ │ │ ├── dropdown_anim__00053@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00054.imageset │ │ │ │ ├── dropdown_anim__00054@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00055.imageset │ │ │ │ ├── dropdown_anim__00055@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00056.imageset │ │ │ │ ├── dropdown_anim__00056@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00057.imageset │ │ │ │ ├── dropdown_anim__00057@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00058.imageset │ │ │ │ ├── dropdown_anim__00058@2x.png │ │ │ │ └── Contents.json │ │ │ ├── dropdown_anim__00059.imageset │ │ │ │ ├── dropdown_anim__00059@2x.png │ │ │ │ └── Contents.json │ │ │ └── dropdown_anim__00060.imageset │ │ │ │ ├── dropdown_anim__00060@2x.png │ │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ ├── AutoRefreshFooterViewController.swift │ ├── AppDelegate.swift │ ├── AppearanceRefreshFooterViewController.swift │ ├── ViewController.swift │ ├── AutoRefreshFooterViewController.xib │ ├── GIFRefreshHeaderViewController.xib │ ├── AppearanceRefreshFooterViewController.xib │ ├── Base.lproj │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard │ └── GIFRefreshHeaderViewController.swift ├── Podfile ├── EasyRefresher.xcworkspace │ └── contents.xcworkspacedata ├── Podfile.lock └── Tests │ ├── Info.plist │ └── Tests.swift ├── fastlane ├── Appfile └── Fastfile ├── .travis.yml ├── EasyRefresher.podspec ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | Pods/ 2 | -------------------------------------------------------------------------------- /EasyRefresher/Assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /EasyRefresher/Classes/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_Pods.xcodeproj: -------------------------------------------------------------------------------- 1 | Example/Pods/Pods.xcodeproj -------------------------------------------------------------------------------- /image.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/image.gif -------------------------------------------------------------------------------- /image1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/image1.gif -------------------------------------------------------------------------------- /EasyRefresher/Assets/EasyRefresher.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/EasyRefresher/EasyRefresher.modulemap: -------------------------------------------------------------------------------- 1 | framework module EasyRefresher { 2 | umbrella header "EasyRefresher-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/EasyRefresher/EasyRefresher-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_EasyRefresher : NSObject 3 | @end 4 | @implementation PodsDummy_EasyRefresher 5 | @end 6 | -------------------------------------------------------------------------------- /Example/EasyRefresher.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /EasyRefresher/Assets/EasyRefresher.xcassets/refresh_arrow_down.imageset/refresh_arrow_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/EasyRefresher/Assets/EasyRefresher.xcassets/refresh_arrow_down.imageset/refresh_arrow_down.png -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-EasyRefresher_Tests/Pods-EasyRefresher_Tests-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | Generated by CocoaPods - https://cocoapods.org 4 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-EasyRefresher_Tests/Pods-EasyRefresher_Tests.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_EasyRefresher_Tests { 2 | umbrella header "Pods-EasyRefresher_Tests-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__0001.imageset/dropdown_anim__0001@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__0001.imageset/dropdown_anim__0001@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__0002.imageset/dropdown_anim__0002@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__0002.imageset/dropdown_anim__0002@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__0003.imageset/dropdown_anim__0003@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__0003.imageset/dropdown_anim__0003@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__0004.imageset/dropdown_anim__0004@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__0004.imageset/dropdown_anim__0004@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__0005.imageset/dropdown_anim__0005@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__0005.imageset/dropdown_anim__0005@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__0006.imageset/dropdown_anim__0006@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__0006.imageset/dropdown_anim__0006@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__0007.imageset/dropdown_anim__0007@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__0007.imageset/dropdown_anim__0007@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__0008.imageset/dropdown_anim__0008@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__0008.imageset/dropdown_anim__0008@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__0009.imageset/dropdown_anim__0009@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__0009.imageset/dropdown_anim__0009@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_loading_01.imageset/dropdown_loading_01@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_loading_01.imageset/dropdown_loading_01@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_loading_02.imageset/dropdown_loading_02@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_loading_02.imageset/dropdown_loading_02@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_loading_03.imageset/dropdown_loading_03@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_loading_03.imageset/dropdown_loading_03@2x.png -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-EasyRefresher_Example/Pods-EasyRefresher_Example.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_EasyRefresher_Example { 2 | umbrella header "Pods-EasyRefresher_Example-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-EasyRefresher_Tests/Pods-EasyRefresher_Tests-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_EasyRefresher_Tests : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_EasyRefresher_Tests 5 | @end 6 | -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00010.imageset/dropdown_anim__00010@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00010.imageset/dropdown_anim__00010@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00011.imageset/dropdown_anim__00011@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00011.imageset/dropdown_anim__00011@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00012.imageset/dropdown_anim__00012@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00012.imageset/dropdown_anim__00012@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00013.imageset/dropdown_anim__00013@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00013.imageset/dropdown_anim__00013@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00014.imageset/dropdown_anim__00014@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00014.imageset/dropdown_anim__00014@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00015.imageset/dropdown_anim__00015@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00015.imageset/dropdown_anim__00015@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00016.imageset/dropdown_anim__00016@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00016.imageset/dropdown_anim__00016@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00017.imageset/dropdown_anim__00017@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00017.imageset/dropdown_anim__00017@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00018.imageset/dropdown_anim__00018@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00018.imageset/dropdown_anim__00018@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00019.imageset/dropdown_anim__00019@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00019.imageset/dropdown_anim__00019@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00020.imageset/dropdown_anim__00020@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00020.imageset/dropdown_anim__00020@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00021.imageset/dropdown_anim__00021@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00021.imageset/dropdown_anim__00021@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00022.imageset/dropdown_anim__00022@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00022.imageset/dropdown_anim__00022@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00023.imageset/dropdown_anim__00023@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00023.imageset/dropdown_anim__00023@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00024.imageset/dropdown_anim__00024@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00024.imageset/dropdown_anim__00024@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00025.imageset/dropdown_anim__00025@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00025.imageset/dropdown_anim__00025@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00026.imageset/dropdown_anim__00026@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00026.imageset/dropdown_anim__00026@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00027.imageset/dropdown_anim__00027@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00027.imageset/dropdown_anim__00027@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00028.imageset/dropdown_anim__00028@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00028.imageset/dropdown_anim__00028@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00029.imageset/dropdown_anim__00029@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00029.imageset/dropdown_anim__00029@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00030.imageset/dropdown_anim__00030@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00030.imageset/dropdown_anim__00030@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00031.imageset/dropdown_anim__00031@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00031.imageset/dropdown_anim__00031@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00032.imageset/dropdown_anim__00032@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00032.imageset/dropdown_anim__00032@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00033.imageset/dropdown_anim__00033@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00033.imageset/dropdown_anim__00033@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00034.imageset/dropdown_anim__00034@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00034.imageset/dropdown_anim__00034@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00035.imageset/dropdown_anim__00035@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00035.imageset/dropdown_anim__00035@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00036.imageset/dropdown_anim__00036@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00036.imageset/dropdown_anim__00036@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00037.imageset/dropdown_anim__00037@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00037.imageset/dropdown_anim__00037@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00038.imageset/dropdown_anim__00038@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00038.imageset/dropdown_anim__00038@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00039.imageset/dropdown_anim__00039@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00039.imageset/dropdown_anim__00039@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00040.imageset/dropdown_anim__00040@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00040.imageset/dropdown_anim__00040@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00041.imageset/dropdown_anim__00041@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00041.imageset/dropdown_anim__00041@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00042.imageset/dropdown_anim__00042@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00042.imageset/dropdown_anim__00042@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00043.imageset/dropdown_anim__00043@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00043.imageset/dropdown_anim__00043@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00044.imageset/dropdown_anim__00044@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00044.imageset/dropdown_anim__00044@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00045.imageset/dropdown_anim__00045@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00045.imageset/dropdown_anim__00045@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00046.imageset/dropdown_anim__00046@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00046.imageset/dropdown_anim__00046@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00047.imageset/dropdown_anim__00047@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00047.imageset/dropdown_anim__00047@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00048.imageset/dropdown_anim__00048@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00048.imageset/dropdown_anim__00048@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00049.imageset/dropdown_anim__00049@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00049.imageset/dropdown_anim__00049@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00050.imageset/dropdown_anim__00050@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00050.imageset/dropdown_anim__00050@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00051.imageset/dropdown_anim__00051@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00051.imageset/dropdown_anim__00051@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00052.imageset/dropdown_anim__00052@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00052.imageset/dropdown_anim__00052@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00053.imageset/dropdown_anim__00053@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00053.imageset/dropdown_anim__00053@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00054.imageset/dropdown_anim__00054@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00054.imageset/dropdown_anim__00054@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00055.imageset/dropdown_anim__00055@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00055.imageset/dropdown_anim__00055@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00056.imageset/dropdown_anim__00056@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00056.imageset/dropdown_anim__00056@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00057.imageset/dropdown_anim__00057@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00057.imageset/dropdown_anim__00057@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00058.imageset/dropdown_anim__00058@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00058.imageset/dropdown_anim__00058@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00059.imageset/dropdown_anim__00059@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00059.imageset/dropdown_anim__00059@2x.png -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00060.imageset/dropdown_anim__00060@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pircate/EasyRefresher/HEAD/Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00060.imageset/dropdown_anim__00060@2x.png -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-EasyRefresher_Example/Pods-EasyRefresher_Example-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_EasyRefresher_Example : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_EasyRefresher_Example 5 | @end 6 | -------------------------------------------------------------------------------- /fastlane/Appfile: -------------------------------------------------------------------------------- 1 | # app_identifier("[[APP_IDENTIFIER]]") # The bundle identifier of your app 2 | # apple_id("[[APPLE_ID]]") # Your Apple email address 3 | 4 | 5 | # For more information about the Appfile, see: 6 | # https://docs.fastlane.tools/advanced/#appfile 7 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '10.0' 2 | use_frameworks! 3 | install! 'cocoapods', :disable_input_output_paths => true 4 | 5 | target 'EasyRefresher_Example' do 6 | pod 'EasyRefresher', :path => '../' 7 | 8 | target 'EasyRefresher_Tests' do 9 | inherit! :search_paths 10 | 11 | 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/EasyRefresher/EasyRefresher-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Example/EasyRefresher.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - EasyRefresher (1.2.2) 3 | 4 | DEPENDENCIES: 5 | - EasyRefresher (from `../`) 6 | 7 | EXTERNAL SOURCES: 8 | EasyRefresher: 9 | :path: "../" 10 | 11 | SPEC CHECKSUMS: 12 | EasyRefresher: 8d9eaceeb27684290444e85edd97b4f314e8ba80 13 | 14 | PODFILE CHECKSUM: ea8a27b447133e05e1f7f1fa64c73effd0f2f51b 15 | 16 | COCOAPODS: 1.9.1 17 | -------------------------------------------------------------------------------- /Example/Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - EasyRefresher (1.2.2) 3 | 4 | DEPENDENCIES: 5 | - EasyRefresher (from `../`) 6 | 7 | EXTERNAL SOURCES: 8 | EasyRefresher: 9 | :path: "../" 10 | 11 | SPEC CHECKSUMS: 12 | EasyRefresher: 8d9eaceeb27684290444e85edd97b4f314e8ba80 13 | 14 | PODFILE CHECKSUM: ea8a27b447133e05e1f7f1fa64c73effd0f2f51b 15 | 16 | COCOAPODS: 1.9.1 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # references: 2 | # * https://www.objc.io/issues/6-build-tools/travis-ci/ 3 | # * https://github.com/supermarin/xcpretty#usage 4 | 5 | osx_image: xcode11.3 6 | language: swift 7 | cache: cocoapods 8 | podfile: Example/Podfile 9 | before_install: 10 | - gem install cocoapods --pre # Since Travis is not always on latest version 11 | - pod install --project-directory=Example 12 | script: 13 | - pod lib lint --allow-warnings 14 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/EasyRefresher/EasyRefresher-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double EasyRefresherVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char EasyRefresherVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /EasyRefresher/Assets/EasyRefresher.xcassets/refresh_arrow_down.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "refresh_arrow_down.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__0001.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__0001@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00010.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00010@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00011.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00011@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00012.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00012@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00013.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00013@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00014.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00014@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00015.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00015@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00016.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00016@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00017.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00017@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00018.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00018@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00019.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00019@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__0002.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__0002@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00020.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00020@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00021.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00021@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00022.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00022@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00023.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00023@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00024.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00024@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00025.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00025@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00026.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00026@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00027.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00027@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00028.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00028@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00029.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00029@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__0003.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__0003@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00030.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00030@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00031.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00031@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00032.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00032@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00033.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00033@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00034.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00034@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00035.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00035@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00036.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00036@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00037.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00037@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00038.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00038@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00039.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00039@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__0004.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__0004@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00040.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00040@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00041.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00041@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00042.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00042@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00043.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00043@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00044.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00044@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00045.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00045@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00046.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00046@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00047.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00047@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00048.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00048@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00049.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00049@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__0005.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__0005@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00050.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00050@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00051.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00051@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00052.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00052@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00053.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00053@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00054.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00054@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00055.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00055@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00056.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00056@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00057.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00057@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00058.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00058@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00059.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00059@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__0006.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__0006@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__00060.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__00060@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__0007.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__0007@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__0008.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__0008@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_anim__0009.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_anim__0009@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_loading_01.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_loading_01@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_loading_02.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_loading_02@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/DianPing/dropdown_loading_03.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "dropdown_loading_03@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-EasyRefresher_Tests/Pods-EasyRefresher_Tests-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_EasyRefresher_TestsVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_EasyRefresher_TestsVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-EasyRefresher_Example/Pods-EasyRefresher_Example-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_EasyRefresher_ExampleVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_EasyRefresher_ExampleVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /EasyRefresher/Assets/zh-Hans.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | EasyRefresher.strings 3 | Pods 4 | 5 | Created by Pircate(swifter.dev@gmail.com) on 2019/5/16. 6 | 7 | */ 8 | 9 | "pull_down_to_refresh" = "下拉可以刷新"; 10 | "release_to_refresh" = "松开立即刷新"; 11 | "loading" = "正在刷新数据中..."; 12 | 13 | "tap_or_pull_up_to_load_more" = "点击或上拉加载更多"; 14 | "pull_up_to_load_more" = "上拉可以加载更多"; 15 | "release_to_load_more" = "松开立即加载更多"; 16 | "no_more_data" = "没有更多数据"; 17 | 18 | "pull_left_to_refresh" = "左拉可以加载更多"; 19 | 20 | "last_update_time" = "最后更新:"; 21 | "today" = "今天"; 22 | "no_record" = "无记录"; 23 | -------------------------------------------------------------------------------- /EasyRefresher/Assets/zh-Hant.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | EasyRefresher.strings 3 | Pods 4 | 5 | Created by Pircate(swifter.dev@gmail.com) on 2019/5/16. 6 | 7 | */ 8 | 9 | "pull_down_to_refresh" = "下拉可以刷新"; 10 | "release_to_refresh" = "鬆開立即刷新"; 11 | "loading" = "正在刷新數據中..."; 12 | 13 | "tap_or_pull_up_to_load_more" = "點擊或上拉加載更多"; 14 | "pull_up_to_load_more" = "上拉可以加載更多"; 15 | "release_to_load_more" = "鬆開立即加載更多"; 16 | "no_more_data" = "沒有更多數據"; 17 | 18 | "pull_left_to_refresh" = "左拉可以加載更多"; 19 | 20 | "last_update_time" = "最後更新:"; 21 | "today" = "今天"; 22 | "no_record" = "無記錄"; 23 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/EasyRefresher/EasyRefresher.xcconfig: -------------------------------------------------------------------------------- 1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/EasyRefresher 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 4 | PODS_BUILD_DIR = ${BUILD_DIR} 5 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 6 | PODS_ROOT = ${SRCROOT} 7 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. 8 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 9 | SKIP_INSTALL = YES 10 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 11 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-EasyRefresher_Tests/Pods-EasyRefresher_Tests.debug.xcconfig: -------------------------------------------------------------------------------- 1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/EasyRefresher" 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/EasyRefresher/EasyRefresher.framework/Headers" 4 | OTHER_LDFLAGS = $(inherited) -framework "EasyRefresher" 5 | PODS_BUILD_DIR = ${BUILD_DIR} 6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 8 | PODS_ROOT = ${SRCROOT}/Pods 9 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 10 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-EasyRefresher_Tests/Pods-EasyRefresher_Tests.release.xcconfig: -------------------------------------------------------------------------------- 1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/EasyRefresher" 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/EasyRefresher/EasyRefresher.framework/Headers" 4 | OTHER_LDFLAGS = $(inherited) -framework "EasyRefresher" 5 | PODS_BUILD_DIR = ${BUILD_DIR} 6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 8 | PODS_ROOT = ${SRCROOT}/Pods 9 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 10 | -------------------------------------------------------------------------------- /EasyRefresher/Assets/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | EasyRefresher.strings 3 | Pods 4 | 5 | Created by Pircate(swifter.dev@gmail.com) on 2019/5/16. 6 | 7 | */ 8 | 9 | "pull_down_to_refresh" = "Pull down to refresh"; 10 | "release_to_refresh" = "Release to refresh"; 11 | "loading" = "Loading..."; 12 | 13 | "tap_or_pull_up_to_load_more" = "Tap or pull up to load more"; 14 | "pull_up_to_load_more" = "Pull up to load more"; 15 | "release_to_load_more" = "Release to load more"; 16 | "no_more_data" = "No more data"; 17 | 18 | "pull_left_to_refresh" = "Pull left to refresh"; 19 | 20 | "last_update_time" = "Last update: "; 21 | "today" = "Today"; 22 | "no_record" = "No record"; 23 | -------------------------------------------------------------------------------- /EasyRefresher.podspec: -------------------------------------------------------------------------------- 1 | 2 | Pod::Spec.new do |s| 3 | s.name = 'EasyRefresher' 4 | s.version = '1.3.1' 5 | s.summary = 'A refresh control for UIScrollView.' 6 | s.homepage = 'https://github.com/Pircate/EasyRefresher' 7 | s.license = { :type => 'MIT', :file => 'LICENSE' } 8 | s.author = { 'Pircate' => 'gao497868860@gmail.com' } 9 | s.source = { :git => 'https://github.com/Pircate/EasyRefresher.git', :tag => s.version.to_s } 10 | s.source_files = 'EasyRefresher/Classes/**/*' 11 | s.resource_bundles = { 12 | 'EasyRefresher' => ['EasyRefresher/Assets/*.xcassets', 'EasyRefresher/Assets/**/*.strings'] 13 | } 14 | s.ios.deployment_target = '10.0' 15 | s.swift_versions = ['5.0'] 16 | end 17 | -------------------------------------------------------------------------------- /EasyRefresher/Classes/RefreshStateful.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RefreshStateView.swift 3 | // EasyRefresher 4 | // 5 | // Created by Pircate(swifter.dev@gmail.com) on 2019/5/12 6 | // Copyright © 2019 Pircate. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public protocol RefreshStateful: class { 12 | 13 | /// refresher state did change 14 | /// - Parameters: 15 | /// - refresher: refresher 16 | /// - state: state of refresh. 17 | func refresher(_ refresher: Refresher, didChangeState state: RefreshState) 18 | 19 | /// refresher offset did change 20 | /// - Parameters: 21 | /// - refresher: refresher 22 | /// - offset: offset of refresher when pulling. 23 | func refresher(_ refresher: Refresher, didChangeOffset offset: CGFloat) 24 | } 25 | -------------------------------------------------------------------------------- /Example/Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/Pods/Local Podspecs/EasyRefresher.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "EasyRefresher", 3 | "version": "1.2.2", 4 | "summary": "A refresh control for UIScrollView.", 5 | "homepage": "https://github.com/Pircate/EasyRefresher", 6 | "license": { 7 | "type": "MIT", 8 | "file": "LICENSE" 9 | }, 10 | "authors": { 11 | "Pircate": "gao497868860@gmail.com" 12 | }, 13 | "source": { 14 | "git": "https://github.com/Pircate/EasyRefresher.git", 15 | "tag": "1.2.2" 16 | }, 17 | "source_files": "EasyRefresher/Classes/**/*", 18 | "resource_bundles": { 19 | "EasyRefresher": [ 20 | "EasyRefresher/Assets/*.xcassets", 21 | "EasyRefresher/Assets/**/*.strings" 22 | ] 23 | }, 24 | "platforms": { 25 | "ios": "10.0" 26 | }, 27 | "swift_versions": [ 28 | "5.0" 29 | ], 30 | "swift_version": "5.0" 31 | } 32 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-EasyRefresher_Example/Pods-EasyRefresher_Example.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/EasyRefresher" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/EasyRefresher/EasyRefresher.framework/Headers" 5 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 6 | OTHER_LDFLAGS = $(inherited) -framework "EasyRefresher" 7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 13 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-EasyRefresher_Example/Pods-EasyRefresher_Example.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/EasyRefresher" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/EasyRefresher/EasyRefresher.framework/Headers" 5 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 6 | OTHER_LDFLAGS = $(inherited) -framework "EasyRefresher" 7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 13 | -------------------------------------------------------------------------------- /Example/Tests/Tests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import EasyRefresher 3 | 4 | class Tests: XCTestCase { 5 | 6 | override func setUp() { 7 | super.setUp() 8 | // Put setup code here. This method is called before the invocation of each test method in the class. 9 | } 10 | 11 | override func tearDown() { 12 | // Put teardown code here. This method is called after the invocation of each test method in the class. 13 | super.tearDown() 14 | } 15 | 16 | func testExample() { 17 | // This is an example of a functional test case. 18 | XCTAssert(true, "Pass") 19 | } 20 | 21 | func testPerformanceExample() { 22 | // This is an example of a performance test case. 23 | self.measure() { 24 | // Put the code you want to measure the time of here. 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/EasyRefresher/ResourceBundle-EasyRefresher-EasyRefresher-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleIdentifier 8 | ${PRODUCT_BUNDLE_IDENTIFIER} 9 | CFBundleInfoDictionaryVersion 10 | 6.0 11 | CFBundleName 12 | ${PRODUCT_NAME} 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.2.2 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/EasyRefresher/EasyRefresher-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.2.2 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-EasyRefresher_Tests/Pods-EasyRefresher_Tests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-EasyRefresher_Example/Pods-EasyRefresher_Example-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-EasyRefresher_Tests/Pods-EasyRefresher_Tests-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Generated by CocoaPods - https://cocoapods.org 18 | Title 19 | 20 | Type 21 | PSGroupSpecifier 22 | 23 | 24 | StringsTable 25 | Acknowledgements 26 | Title 27 | Acknowledgements 28 | 29 | 30 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | # This file contains the fastlane.tools configuration 2 | # You can find the documentation at https://docs.fastlane.tools 3 | # 4 | # For a list of all available actions, check out 5 | # 6 | # https://docs.fastlane.tools/actions 7 | # 8 | # For a list of all available plugins, check out 9 | # 10 | # https://docs.fastlane.tools/plugins/available-plugins 11 | # 12 | 13 | # Uncomment the line if you want fastlane to automatically update itself 14 | # update_fastlane 15 | 16 | default_platform(:ios) 17 | 18 | platform :ios do 19 | desc "release new version" 20 | lane :release do |options| 21 | target_version = options[:version] 22 | raise "The version is missed." if target_version.nil? 23 | 24 | version_bump_podspec( 25 | path: "EasyRefresher.podspec", 26 | version_number: target_version) 27 | git_add(path: "./EasyRefresher.podspec") 28 | git_commit(path: "./EasyRefresher.podspec", message: "release #{target_version} version") 29 | add_git_tag tag: target_version 30 | push_to_git_remote 31 | pod_push(allow_warnings: true) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Pircate 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Example/EasyRefresher/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "size" : "1024x1024", 46 | "scale" : "1x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /EasyRefresher/Classes/Refresh.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Refresh.swift 3 | // EasyRefresher 4 | // 5 | // Created by Pircate(swifter.dev@gmail.com) on 2019/4/26 6 | // Copyright © 2019 Pircate. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public struct Refresh { 12 | 13 | let base: Base 14 | 15 | init(_ base: Base) { 16 | self.base = base 17 | } 18 | } 19 | 20 | public protocol RefreshCompatible: class { 21 | 22 | associatedtype CompatibleType 23 | 24 | var refresh: CompatibleType { get set } 25 | } 26 | 27 | public extension RefreshCompatible { 28 | 29 | var refresh: Refresh { 30 | get { Refresh(self) } 31 | set {} 32 | } 33 | } 34 | 35 | extension UIScrollView: RefreshCompatible {} 36 | 37 | public extension Refresh where Base: UIScrollView { 38 | 39 | var header: HeaderRefresher { 40 | get { base.refresh_header } 41 | set { base.refresh_header = newValue } 42 | } 43 | 44 | var footer: FooterRefresher { 45 | get { base.refresh_footer } 46 | set { base.refresh_footer = newValue } 47 | } 48 | 49 | var trailer: TrailerRefresher { 50 | get { base.refresh_trailer } 51 | set { base.refresh_trailer = newValue } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Example/EasyRefresher/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /EasyRefresher/Classes/UserInterfacable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserInterfacable.swift 3 | // EasyRefresher 4 | // 5 | // Created by Pircate(swifter.dev@gmail.com) on 2019/5/9 6 | // Copyright © 2019 Pircate. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public enum ImpactFeedbackMode { 12 | case off 13 | case on(style: UIImpactFeedbackGenerator.FeedbackStyle) 14 | } 15 | 16 | public protocol UserInterfacable { 17 | 18 | // The height of refresher's view. 19 | var height: CGFloat { get } 20 | 21 | /// The alpha value of refresher's view. 22 | var alpha: CGFloat { get set } 23 | 24 | /// A Boolean value indicating whether the refresher is hidden, default is false. 25 | var isHidden: Bool { get set } 26 | 27 | /// The background color of refresher's view. 28 | var backgroundColor: UIColor? { get set } 29 | 30 | /// A Boolean value indicating whether the refresher automatically change view's alpha value when pulling. 31 | var automaticallyChangeAlpha: Bool { get set } 32 | 33 | /// The basic appearance of the refresher's activity indicator. 34 | var activityIndicatorStyle: UIActivityIndicatorView.Style { get set } 35 | 36 | /// A enum value indicating whether the refresher impact occurred when will refresh. 37 | var impactFeedbackMode: ImpactFeedbackMode { get set } 38 | } 39 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-EasyRefresher_Example/Pods-EasyRefresher_Example-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## EasyRefresher 5 | 6 | Copyright (c) 2019 Pircate 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 | Generated by CocoaPods - https://cocoapods.org 27 | -------------------------------------------------------------------------------- /EasyRefresher/Classes/Refreshable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Refreshable.swift 3 | // EasyRefresher 4 | // 5 | // Created by Pircate(swifter.dev@gmail.com) on 2019/4/26 6 | // Copyright © 2019 Pircate. All rights reserved. 7 | // 8 | 9 | public enum RefreshState { 10 | case idle 11 | case pulling 12 | case willRefresh 13 | case refreshing 14 | case disabled 15 | } 16 | 17 | public protocol Refreshable: class { 18 | 19 | /// The state of refresher. 20 | var state: RefreshState { get } 21 | 22 | /// A Boolean value indicating whether the refresher's state is refreshing. 23 | var isRefreshing: Bool { get } 24 | 25 | /// Default is true. If false, end refreshing and set state to disabled. 26 | var isEnabled: Bool { get set } 27 | 28 | /// The callback in refresh. 29 | var refreshClosure: (() -> Void)? { get set } 30 | 31 | /// Add a refresh callback to refresher. 32 | /// - Parameter refreshClosure: The callback in refresh. 33 | func addRefreshClosure(_ refreshClosure: @escaping () -> Void) 34 | 35 | /// Begin refreshing and set state to refreshing. 36 | func beginRefreshing() 37 | 38 | /// End Refreshing and set state to idle. 39 | func endRefreshing() 40 | 41 | /// Remove refresher from scroll view. 42 | func removeFromScrollView() 43 | } 44 | 45 | public extension Refreshable { 46 | 47 | var isRefreshing: Bool { 48 | return state == .refreshing 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /EasyRefresher/Classes/ScrollViewObservation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScrollViewObservation.swift 3 | // EasyRefresher 4 | // 5 | // Created by Pircate(swifter.dev@gmail.com) on 2019/5/16 6 | // Copyright © 2019 Pircate. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class ScrollViewObservation { 12 | 13 | enum KeyPath { 14 | case contentOffset 15 | case contentSize 16 | case panGestureState 17 | } 18 | 19 | private var contentOffsetObservation: NSKeyValueObservation? 20 | 21 | private var contentSizeObservation: NSKeyValueObservation? 22 | 23 | private var panGestureStateObservation: NSKeyValueObservation? 24 | 25 | func observe(_ scrollView: UIScrollView, changeHandler: @escaping (UIScrollView, KeyPath) -> Void) { 26 | contentOffsetObservation = scrollView.observe(\.contentOffset) { this, _ in 27 | changeHandler(this, .contentOffset) 28 | } 29 | 30 | contentSizeObservation = scrollView.observe(\.contentSize) { this, _ in 31 | changeHandler(this, .contentSize) 32 | } 33 | 34 | panGestureStateObservation = scrollView.observe(\.panGestureRecognizer.state) { this, _ in 35 | changeHandler(this, .panGestureState) 36 | } 37 | } 38 | 39 | func invalidate() { 40 | contentOffsetObservation?.invalidate() 41 | contentSizeObservation?.invalidate() 42 | panGestureStateObservation?.invalidate() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /EasyRefresher/Classes/Localized.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Localized.swift 3 | // EasyRefresher 4 | // 5 | // Created by Pircate(swifter.dev@gmail.com) on 2019/5/16 6 | // Copyright © 2019 Pircate. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension String { 12 | 13 | func localized( 14 | _ language: Language = .current, 15 | value: String? = nil, 16 | table: String = "Localizable" 17 | ) -> String { 18 | guard let path = Bundle.current?.path(forResource: language.rawValue, ofType: "lproj") else { 19 | return self 20 | } 21 | 22 | return Bundle(path: path)?.localizedString(forKey: self, value: value, table: table) ?? self 23 | } 24 | } 25 | 26 | public enum Language: String { 27 | case en 28 | case zhHans = "zh-Hans" 29 | case zhHant = "zh-Hant" 30 | 31 | public static var current: Language = { 32 | guard let language = Locale.preferredLanguages.first else { return .en } 33 | 34 | if language.contains("zh-HK") { return .zhHant } 35 | 36 | if language.contains("zh-Hans") { return .zhHans } 37 | 38 | return Language(rawValue: language) ?? .en 39 | }() 40 | } 41 | 42 | extension Bundle { 43 | 44 | static let current: Bundle? = { 45 | guard let resourcePath = Bundle(for: RefreshComponent.self).resourcePath else { return nil } 46 | 47 | return Bundle(path: "\(resourcePath)/EasyRefresher.bundle") 48 | }() 49 | } 50 | -------------------------------------------------------------------------------- /EasyRefresher/Classes/AppearanceRefreshFooter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppearanceRefreshFooter.swift 3 | // EasyRefresher 4 | // 5 | // Created by Pircate(swifter.dev@gmail.com) on 2019/5/11 6 | // Copyright © 2019 Pircate. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | open class AppearanceRefreshFooter: RefreshFooter { 12 | 13 | public override var automaticallyChangeAlpha: Bool { 14 | get { false } 15 | set { fatalError("AppearanceRefreshFooter is always displayed, unsupport set this property.") } 16 | } 17 | 18 | internal(set) public override var state: RefreshState { 19 | get { super.state } 20 | set { 21 | guard newValue == .idle else { 22 | super.state = newValue 23 | return 24 | } 25 | 26 | super.state = .pulling 27 | } 28 | } 29 | 30 | override func prepare() { 31 | super.prepare() 32 | 33 | alpha = 1 34 | addTapGestureRecognizer() 35 | setTitle("tap_or_pull_up_to_load_more".localized(), for: .pulling) 36 | } 37 | 38 | override func add(to scrollView: UIScrollView) { 39 | super.add(to: scrollView) 40 | 41 | willBeginRefreshing {} 42 | } 43 | 44 | override func didEndRefreshing(completion: @escaping () -> Void) { completion() } 45 | 46 | override func didChangeState(by offset: CGFloat) { 47 | switch offset { 48 | case ..<(-height): 49 | state = .willRefresh 50 | default: 51 | state = .pulling 52 | } 53 | } 54 | } 55 | 56 | private extension AppearanceRefreshFooter { 57 | 58 | func addTapGestureRecognizer() { 59 | let tapGesture = UITapGestureRecognizer( 60 | target: self, 61 | action: #selector(tapGestureAction(sender:)) 62 | ) 63 | addGestureRecognizer(tapGesture) 64 | } 65 | 66 | @objc func tapGestureAction(sender: UITapGestureRecognizer) { 67 | beginRefreshing() 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Example/EasyRefresher/AutoRefreshFooterViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AutoRefreshFooterViewController.swift 3 | // EasyRefresher_Example 4 | // 5 | // Created by Pircate(swifter.dev@gmail.com) on 2019/5/13 6 | // Copyright © 2019 CocoaPods. All rights reserved. 7 | // 8 | 9 | import EasyRefresher 10 | 11 | class AutoRefreshFooterViewController: UIViewController { 12 | 13 | @IBOutlet weak var tableView: UITableView! 14 | 15 | var dataArray: [String] = ["", "", "", "", ""] 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cellID") 21 | 22 | tableView.refresh.header.addRefreshClosure { 23 | self.reqeust { 24 | self.dataArray = ["", "", "", "", ""] 25 | self.tableView.refresh.header.endRefreshing() 26 | self.tableView.reloadData() 27 | } 28 | } 29 | 30 | tableView.refresh.header.beginRefreshing() 31 | 32 | tableView.refresh.footer = AutoRefreshFooter(triggerMode: .percent(0.5)) { 33 | self.reqeust { 34 | self.dataArray.append(contentsOf: ["", "", "", "", ""]) 35 | self.tableView.refresh.footer.endRefreshing() 36 | self.tableView.reloadData() 37 | } 38 | } 39 | } 40 | 41 | private func reqeust(completion: @escaping () -> Void) { 42 | DispatchQueue.global().asyncAfter(deadline: .now() + 2) { 43 | DispatchQueue.main.async { 44 | completion() 45 | } 46 | } 47 | } 48 | } 49 | 50 | extension AutoRefreshFooterViewController: UITableViewDataSource { 51 | 52 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 53 | return dataArray.count 54 | } 55 | 56 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 57 | let cell = tableView.dequeueReusableCell(withIdentifier: "cellID")! 58 | cell.textLabel?.text = "\(indexPath.row)" 59 | return cell 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /EasyRefresher/Classes/HasStateTitle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HasStateTitle.swift 3 | // EasyRefresher 4 | // 5 | // Created by Pircate(swifter.dev@gmail.com) on 2019/5/8 6 | // Copyright © 2019 Pircate. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol HasStateTitle: class { 12 | 13 | var stateTitles: [RefreshState: String] { get set } 14 | 15 | var stateAttributedTitles: [RefreshState: NSAttributedString] { get set } 16 | 17 | /// Sets the title to use for the specified state. 18 | /// - Parameters: 19 | /// - title: The title to use for the specified state. 20 | /// - state: The state that uses the specified title. The possible values are described in RefreshState. 21 | func setTitle(_ title: String?, for state: RefreshState) 22 | 23 | /// Sets the styled title to use for the specified state. 24 | /// - Parameters: 25 | /// - title: The styled text string to use for the title. 26 | /// - state: The state that uses the specified title. The possible values are described in RefreshState. 27 | func setAttributedTitle(_ title: NSAttributedString?, for state: RefreshState) 28 | 29 | /// Returns the title associated with the specified state. 30 | /// - Parameter state: The state that uses the title. The possible values are described in RefreshState. 31 | func title(for state: RefreshState) -> String? 32 | 33 | /// Returns the styled title associated with the specified state. 34 | /// - Parameter state: The state that uses the styled title. The possible values are described in RefreshState. 35 | func attributedTitle(for state: RefreshState) -> NSAttributedString? 36 | } 37 | 38 | public extension HasStateTitle { 39 | 40 | func setTitle(_ title: String?, for state: RefreshState) { 41 | stateTitles[state] = title 42 | } 43 | 44 | func setAttributedTitle(_ title: NSAttributedString?, for state: RefreshState) { 45 | stateAttributedTitles[state] = title 46 | } 47 | 48 | func title(for state: RefreshState) -> String? { 49 | return stateTitles[state] 50 | } 51 | 52 | func attributedTitle(for state: RefreshState) -> NSAttributedString? { 53 | return stateAttributedTitles[state] 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Example/EasyRefresher/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // EasyRefresher 4 | // 5 | // Created by Pircate on 05/07/2019. 6 | // Copyright (c) 2019 Pircate. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Example/Pods/Pods.xcodeproj/xcuserdata/pircate.xcuserdatad/xcschemes/EasyRefresher-EasyRefresher.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 53 | 54 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Example/Pods/Pods.xcodeproj/xcuserdata/pircate.xcuserdatad/xcschemes/Pods-EasyRefresher_Tests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 53 | 54 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Example/Pods/Pods.xcodeproj/xcuserdata/pircate.xcuserdatad/xcschemes/Pods-EasyRefresher_Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 53 | 54 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Example/Pods/Pods.xcodeproj/xcuserdata/pircate.xcuserdatad/xcschemes/EasyRefresher.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 45 | 46 | 52 | 53 | 55 | 56 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-EasyRefresher_Example/Pods-EasyRefresher_Example-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Copyright (c) 2019 Pircate <gao497868860@gmail.com> 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy 20 | of this software and associated documentation files (the "Software"), to deal 21 | in the Software without restriction, including without limitation the rights 22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | copies of the Software, and to permit persons to whom the Software is 24 | furnished to do so, subject to the following conditions: 25 | 26 | The above copyright notice and this permission notice shall be included in 27 | all copies or substantial portions of the Software. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 35 | THE SOFTWARE. 36 | 37 | License 38 | MIT 39 | Title 40 | EasyRefresher 41 | Type 42 | PSGroupSpecifier 43 | 44 | 45 | FooterText 46 | Generated by CocoaPods - https://cocoapods.org 47 | Title 48 | 49 | Type 50 | PSGroupSpecifier 51 | 52 | 53 | StringsTable 54 | Acknowledgements 55 | Title 56 | Acknowledgements 57 | 58 | 59 | -------------------------------------------------------------------------------- /EasyRefresher/Classes/RefreshStatefulView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StatefulRefreshComponent.swift 3 | // EasyRefresher 4 | // 5 | // Created by Pircate(swifter.dev@gmail.com) on 2020/6/7 6 | // Copyright © 2020 Pircate. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | open class RefreshStatefulView: RefreshView { 12 | 13 | var stateChanged: ((RefreshState) -> Void)? 14 | 15 | var offsetChanged: ((CGFloat) -> Void)? 16 | 17 | func stateDidChange(_ state: RefreshState) { 18 | stateChanged?(state) 19 | 20 | startAnimating(when: state == .refreshing) 21 | impactOccurred(when: state == .willRefresh) 22 | rotateArrow(for: state) 23 | didChangeStateTitle(for: state) 24 | } 25 | 26 | func offsetDidChange(_ offset: CGFloat) { 27 | guard offset < 0, offset >= -height else { return } 28 | 29 | offsetChanged?(-offset) 30 | } 31 | } 32 | 33 | private extension RefreshStatefulView { 34 | 35 | func startAnimating(when refreshing: Bool) { 36 | if refreshing { 37 | activityIndicator.startAnimating() 38 | } else { 39 | activityIndicator.stopAnimating() 40 | } 41 | } 42 | 43 | func impactOccurred(when willRefresh: Bool) { 44 | guard willRefresh, case let .on(style) = impactFeedbackMode else { return } 45 | 46 | let feedbackGenerator = UIImpactFeedbackGenerator(style: style) 47 | feedbackGenerator.prepare() 48 | feedbackGenerator.impactOccurred() 49 | } 50 | 51 | func rotateArrow(for state: RefreshState) { 52 | arrowImageView.isHidden = state == .idle || state == .refreshing || state == .disabled 53 | 54 | UIView.animate(withDuration: 0.25) { 55 | self.arrowImageView.transform = self.arrowDirection.reversedTransform(when: state == .willRefresh) 56 | } 57 | } 58 | 59 | func didChangeStateTitle(for state: RefreshState) { 60 | if let attributedTitle = attributedTitle(for: state) { 61 | stateLabel.isHidden = false 62 | stateLabel.attributedText = attributedTitle 63 | } else if let title = title(for: state) { 64 | stateLabel.isHidden = false 65 | stateLabel.attributedText = nil 66 | stateLabel.text = title 67 | } else { 68 | stateLabel.isHidden = true 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Example/EasyRefresher/AppearanceRefreshFooterViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppearanceRefreshFooterViewController.swift 3 | // EasyRefresher_Example 4 | // 5 | // Created by Pircate(swifter.dev@gmail.com) on 2019/5/13 6 | // Copyright © 2019 CocoaPods. All rights reserved. 7 | // 8 | 9 | import EasyRefresher 10 | 11 | class AppearanceRefreshFooterViewController: UIViewController { 12 | 13 | @IBOutlet weak var tableView: UITableView! 14 | 15 | var dataArray: [String] = ["", "", "", "", ""] 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cellID") 21 | 22 | tableView.refresh.header.addRefreshClosure { 23 | self.reqeust { 24 | self.dataArray = ["", "", "", "", ""] 25 | self.tableView.refresh.footer.isEnabled = true 26 | self.tableView.refresh.header.endRefreshing() 27 | self.tableView.reloadData() 28 | } 29 | } 30 | 31 | tableView.refresh.header.beginRefreshing() 32 | 33 | tableView.refresh.footer = AppearanceRefreshFooter { 34 | self.reqeust { 35 | self.dataArray.append(contentsOf: ["", "", "", "", ""]) 36 | self.tableView.refresh.footer.isEnabled = false 37 | self.tableView.reloadData() 38 | } 39 | } 40 | 41 | tableView.refresh.footer.setAttributedTitle( 42 | NSAttributedString(string: "已到最后一页", attributes: [.foregroundColor: UIColor.red]), for: .disabled 43 | ) 44 | } 45 | 46 | private func reqeust(completion: @escaping () -> Void) { 47 | DispatchQueue.global().asyncAfter(deadline: .now() + 2) { 48 | DispatchQueue.main.async { 49 | completion() 50 | } 51 | } 52 | } 53 | } 54 | 55 | extension AppearanceRefreshFooterViewController: UITableViewDataSource { 56 | 57 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 58 | return dataArray.count 59 | } 60 | 61 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 62 | let cell = tableView.dequeueReusableCell(withIdentifier: "cellID")! 63 | cell.textLabel?.text = "\(indexPath.row)" 64 | return cell 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /EasyRefresher/Classes/AutoRefreshFooter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AutoRefreshFooter.swift 3 | // EasyRefresher 4 | // 5 | // Created by Pircate(swifter.dev@gmail.com) on 2019/5/8 6 | // Copyright © 2019 Pircate. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// The trigger mode of automatic refresh 12 | public enum TriggerMode { 13 | case percent(CGFloat) 14 | case offset(CGFloat) 15 | } 16 | 17 | open class AutoRefreshFooter: RefreshFooter { 18 | 19 | private let triggerMode: TriggerMode 20 | 21 | public init( 22 | triggerMode: TriggerMode = .percent(0), 23 | height: CGFloat = 54, 24 | refreshClosure: @escaping () -> Void 25 | ) { 26 | self.triggerMode = triggerMode 27 | 28 | super.init(height: height, refreshClosure: refreshClosure) 29 | } 30 | 31 | public init( 32 | stateView: T, 33 | triggerMode: TriggerMode = .percent(0), 34 | height: CGFloat = 54, 35 | refreshClosure: @escaping () -> Void 36 | ) where T : UIView, T : RefreshStateful { 37 | self.triggerMode = triggerMode 38 | 39 | super.init(stateView: stateView, height: height, refreshClosure: refreshClosure) 40 | } 41 | 42 | public init( 43 | triggerMode: TriggerMode = .percent(0), 44 | height: CGFloat = 54, 45 | delegate: RefreshDelegate 46 | ) { 47 | self.triggerMode = triggerMode 48 | 49 | super.init(height: height, delegate: delegate) 50 | } 51 | 52 | public init( 53 | stateView: T, 54 | triggerMode: TriggerMode = .percent(0), 55 | height: CGFloat = 54, 56 | delegate: RefreshDelegate 57 | ) where T : UIView, T : RefreshStateful { 58 | self.triggerMode = triggerMode 59 | 60 | super.init(stateView: stateView, height: height, delegate: delegate) 61 | } 62 | 63 | public required init?(coder aDecoder: NSCoder) { 64 | self.triggerMode = .percent(0) 65 | 66 | super.init(coder: aDecoder) 67 | } 68 | 69 | override func scrollViewPanGestureStateDidChange(_ scrollView: UIScrollView) {} 70 | 71 | override func triggerAutoRefresh(by offset: CGFloat) -> Bool { 72 | switch triggerMode { 73 | case .percent(let value): 74 | return offset > 0 && offset / height > (0...1).clamp(value) 75 | case .offset(let value): 76 | return value < height ? offset > value : offset > height 77 | } 78 | } 79 | } 80 | 81 | private extension ClosedRange { 82 | 83 | func clamp(_ value : Bound) -> Bound { 84 | return lowerBound > value ? lowerBound 85 | : upperBound < value ? upperBound 86 | : value 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Example/EasyRefresher/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // EasyRefresher 4 | // 5 | // Created by Pircate on 05/07/2019. 6 | // Copyright (c) 2019 Pircate. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import EasyRefresher 11 | 12 | class ViewController: UIViewController { 13 | 14 | @IBOutlet weak var tableView: UITableView! 15 | 16 | var dataArray: [String] = [ 17 | "AutoRefreshFooter", 18 | "AppearanceRefreshFooter", 19 | "GIFRefreshHeader"] 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | 24 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cellID") 25 | 26 | tableView.refresh.header = RefreshHeader(delegate: self) 27 | 28 | tableView.refresh.header.automaticallyChangeAlpha = false 29 | tableView.refresh.header.impactFeedbackMode = .on(style: .medium) 30 | 31 | tableView.refresh.footer = RefreshFooter { 32 | self.reqeust { 33 | self.tableView.refresh.footer.endRefreshing() 34 | } 35 | } 36 | } 37 | 38 | private func reqeust(completion: @escaping () -> Void) { 39 | DispatchQueue.global().asyncAfter(deadline: .now() + 2) { 40 | DispatchQueue.main.async { 41 | completion() 42 | } 43 | } 44 | } 45 | } 46 | 47 | extension ViewController: RefreshDelegate { 48 | func refresherDidRefresh(_ refresher: Refresher) { 49 | reqeust { 50 | self.tableView.refresh.header.endRefreshing() 51 | } 52 | } 53 | } 54 | 55 | extension ViewController: UITableViewDataSource { 56 | 57 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 58 | return dataArray.count 59 | } 60 | 61 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 62 | let cell = tableView.dequeueReusableCell(withIdentifier: "cellID")! 63 | cell.textLabel?.text = dataArray[indexPath.row] 64 | return cell 65 | } 66 | } 67 | 68 | extension UIViewController: UITableViewDelegate { 69 | 70 | public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 71 | tableView.deselectRow(at: indexPath, animated: true) 72 | 73 | switch indexPath.row { 74 | case 0: 75 | navigationController?.pushViewController( 76 | AutoRefreshFooterViewController(), 77 | animated: true) 78 | case 1: 79 | navigationController?.pushViewController( 80 | AppearanceRefreshFooterViewController(), 81 | animated: true) 82 | case 2: 83 | navigationController?.pushViewController( 84 | GIFRefreshHeaderViewController(), 85 | animated: true) 86 | default: 87 | break 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Example/EasyRefresher/AutoRefreshFooterViewController.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 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Example/EasyRefresher/GIFRefreshHeaderViewController.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 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Example/EasyRefresher/AppearanceRefreshFooterViewController.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 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EasyRefresher 2 | 3 | [![CI Status](https://img.shields.io/travis/Pircate/EasyRefresher.svg?style=flat)](https://travis-ci.org/Pircate/EasyRefresher) 4 | [![Version](https://img.shields.io/cocoapods/v/EasyRefresher.svg?style=flat)](https://cocoapods.org/pods/EasyRefresher) 5 | [![License](https://img.shields.io/cocoapods/l/EasyRefresher.svg?style=flat)](https://cocoapods.org/pods/EasyRefresher) 6 | [![Platform](https://img.shields.io/cocoapods/p/EasyRefresher.svg?style=flat)](https://cocoapods.org/pods/EasyRefresher) 7 | 8 | ## Example 9 | 10 | To run the example project, clone the repo, and run `pod install` from the Example directory first. 11 | 12 | ## Requirements 13 | 14 | * iOS 10.0 15 | * Swift 5.0 16 | 17 | ## Installation 18 | 19 | EasyRefresher is available through [CocoaPods](https://cocoapods.org). To install 20 | it, simply add the following line to your Podfile: 21 | 22 | ```ruby 23 | pod 'EasyRefresher' 24 | ``` 25 | 26 | ## Preview 27 | 28 | ![](https://github.com/Pircate/EasyRefresher/blob/master/image.gif) 29 | ![](https://github.com/Pircate/EasyRefresher/blob/master/image1.gif) 30 | 31 | ## Usage 32 | 33 | * Add Refresher 34 | 35 | ```swift 36 | tableView.refresh.header.addRefreshClosure { 37 | self.reqeust { 38 | self.tableView.refresh.header.endRefreshing() 39 | } 40 | } 41 | 42 | tableView.refresh.footer = AutoRefreshFooter(triggerMode: .percent(0.5)) { 43 | self.reqeust { 44 | self.tableView.refresh.footer.endRefreshing() 45 | } 46 | } 47 | 48 | tableView.refresh.header = RefreshHeader(delegate: self) 49 | 50 | ``` 51 | 52 | * Manual Trigger 53 | 54 | ```swift 55 | tableView.refresh.header.beginRefreshing() 56 | ``` 57 | 58 | * State Title 59 | 60 | ```swift 61 | tableView.refresh.header.setTitle("loading...", for: .refreshing) 62 | 63 | tableView.refresh.footer.setAttributedTitle( 64 | NSAttributedString(string: "已到最后一页", attributes: [.foregroundColor: UIColor.red]), for: .disabled 65 | ) 66 | ``` 67 | 68 | * Last updated time 69 | 70 | ```swift 71 | tableView.refresh.header.lastUpdatedTimeText = { date in 72 | guard let date = date else { return "暂无更新记录" } 73 | 74 | return "上次刷新时间:\(date)" 75 | } 76 | 77 | ``` 78 | 79 | * UIActivityIndicatorView Style 80 | 81 | ```swift 82 | tableView.refresh.header.activityIndicatorStyle = .white 83 | ``` 84 | 85 | * Disabled 86 | 87 | ```swift 88 | // End refreshing and set state to disabled 89 | tableView.refresh.footer.isEnabled = false 90 | ``` 91 | 92 | * Remove 93 | 94 | ```swift 95 | tableView.refresh.footer.removeFromScrollView() 96 | ``` 97 | 98 | * Impact feedback 99 | 100 | ```swift 101 | tableView.refresh.header.impactFeedbackMode = .on(style: .medium) 102 | ``` 103 | 104 | * Custom State View 105 | 106 | ```swift 107 | extension CustomStateView: RefreshStateful { 108 | 109 | public func refresher(_ refresher: Refresher, didChangeState state: RefreshState) { 110 | 111 | } 112 | 113 | public func refresher(_ refresher: Refresher, didChangeOffset offset: CGFloat) { 114 | 115 | } 116 | } 117 | 118 | tableView.refresh.footer = AppearanceRefreshFooter(stateView: CustomStateView()) { 119 | self.reqeust { 120 | self.tableView.refresh.footer.endRefreshing() 121 | } 122 | } 123 | ``` 124 | 125 | ## Author 126 | 127 | Pircate, swifter.dev@gmail.com 128 | 129 | ## License 130 | 131 | EasyRefresher is available under the MIT license. See the LICENSE file for more info. 132 | -------------------------------------------------------------------------------- /EasyRefresher/Classes/RefreshView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RefreshView.swift 3 | // EasyRefresher 4 | // 5 | // Created by Pircate(swifter.dev@gmail.com) on 2020/6/7 6 | // Copyright © 2020 Pircate. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | open class RefreshView: UIView, HasStateTitle, UserInterfacable { 12 | 13 | open var activityIndicatorStyle: UIActivityIndicatorView.Style { 14 | get { activityIndicator.style } 15 | set { activityIndicator.style = newValue } 16 | } 17 | 18 | open var automaticallyChangeAlpha: Bool = true 19 | 20 | open var impactFeedbackMode: ImpactFeedbackMode = .off 21 | 22 | public var stateTitles: [RefreshState : String] = [:] 23 | 24 | public var stateAttributedTitles: [RefreshState : NSAttributedString] = [:] 25 | 26 | var arrowDirection: ArrowDirection { .down } 27 | 28 | lazy var stackView: UIStackView = { buildStackView() }() 29 | 30 | lazy var arrowImageView: UIImageView = { 31 | let image = UIImage(named: "refresh_arrow_down", in: .current, compatibleWith: nil) 32 | let arrowImageView = UIImageView(image: image) 33 | arrowImageView.isHidden = true 34 | arrowImageView.transform = arrowDirection.reversedTransform(when: false) 35 | return arrowImageView 36 | }() 37 | 38 | lazy var activityIndicator: UIActivityIndicatorView = { 39 | if #available(iOS 13.0, *) { 40 | return UIActivityIndicatorView(style: .medium) 41 | } else { 42 | return UIActivityIndicatorView(style: .gray) 43 | } 44 | }() 45 | 46 | lazy var stateLabel: UILabel = { 47 | let stateLabel = UILabel() 48 | stateLabel.font = UIFont.systemFont(ofSize: 14) 49 | stateLabel.textAlignment = .center 50 | return stateLabel 51 | }() 52 | 53 | public let height: CGFloat 54 | 55 | public init(height: CGFloat) { 56 | self.height = height 57 | 58 | super.init(frame: .zero) 59 | 60 | layoutStackView() 61 | } 62 | 63 | public init(empty height: CGFloat) { 64 | self.height = height 65 | 66 | super.init(frame: .zero) 67 | } 68 | 69 | public required init?(coder aDecoder: NSCoder) { 70 | self.height = 54 71 | 72 | super.init(coder: aDecoder) 73 | 74 | layoutStackView() 75 | } 76 | 77 | func buildStackView() -> UIStackView { 78 | let stackView = UIStackView(arrangedSubviews: [activityIndicator, arrowImageView, stateLabel]) 79 | stackView.spacing = 8 80 | stackView.alignment = .center 81 | return stackView 82 | } 83 | 84 | private func layoutStackView() { 85 | addSubview(stackView) 86 | 87 | stackView.translatesAutoresizingMaskIntoConstraints = false 88 | stackView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true 89 | stackView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true 90 | } 91 | } 92 | 93 | extension RefreshView { 94 | 95 | enum ArrowDirection { 96 | case up 97 | case down 98 | } 99 | } 100 | 101 | extension RefreshView.ArrowDirection { 102 | 103 | func reversedTransform(when willRefresh: Bool) -> CGAffineTransform { 104 | switch self { 105 | case .up: 106 | return willRefresh ? .identity : CGAffineTransform(rotationAngle: .pi) 107 | case .down: 108 | return willRefresh ? CGAffineTransform(rotationAngle: .pi) : .identity 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /EasyRefresher/Classes/RefreshFooter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RefreshFooter.swift 3 | // EasyRefresher 4 | // 5 | // Created by Pircate(swifter.dev@gmail.com) on 2019/4/26 6 | // Copyright © 2019 Pircate. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public protocol FooterRefresher: Refresher {} 12 | 13 | open class RefreshFooter: RefreshComponent, FooterRefresher { 14 | 15 | override var arrowDirection: ArrowDirection { .up } 16 | 17 | private lazy var constraintOfTopAnchor: NSLayoutConstraint? = { 18 | guard let scrollView = scrollView, isDescendant(of: scrollView) else { return nil } 19 | 20 | let constraint = topAnchor.constraint(equalTo: scrollView.topAnchor) 21 | constraint.isActive = true 22 | 23 | return constraint 24 | }() 25 | 26 | open override func updateConstraints() { 27 | super.updateConstraints() 28 | 29 | guard let scrollView = scrollView else { return } 30 | 31 | constraintOfTopAnchor?.constant = scrollView.contentSize.height 32 | } 33 | 34 | override func prepare() { 35 | super.prepare() 36 | 37 | alpha = 0 38 | setTitle("pull_up_to_load_more".localized(), for: .pulling) 39 | setTitle("release_to_load_more".localized(), for: .willRefresh) 40 | } 41 | 42 | override func willBeginRefreshing(completion: @escaping () -> Void) { 43 | guard let scrollView = scrollView else { return } 44 | 45 | alpha = 1 46 | 47 | UIView.animate(withDuration: 0.25, animations: { 48 | scrollView.contentInset.bottom = self.originalInset.bottom + self.height 49 | scrollView.changed_inset.bottom = self.height 50 | }, completion: { _ in completion() }) 51 | } 52 | 53 | override func didEndRefreshing(completion: @escaping () -> Void) { 54 | guard let scrollView = scrollView else { return } 55 | 56 | UIView.animate(withDuration: 0.25, animations: { 57 | self.alpha = 0 58 | scrollView.contentInset.bottom -= scrollView.changed_inset.bottom 59 | scrollView.changed_inset.bottom = 0 60 | }, completion: { _ in completion() }) 61 | } 62 | 63 | override func scrollViewContentInsetDidReset(_ scrollView: UIScrollView) { 64 | scrollView.contentInset.bottom -= scrollView.changed_inset.bottom 65 | scrollView.changed_inset.bottom = 0 66 | } 67 | 68 | override func scrollViewContentOffsetDidChange(_ scrollView: UIScrollView) { 69 | guard !isHidden else { return } 70 | 71 | let offset: CGFloat 72 | 73 | if scrollView.refreshInset.top + scrollView.contentSize.height >= scrollView.bounds.height { 74 | offset = scrollView.contentOffset.y 75 | + scrollView.bounds.height 76 | - scrollView.contentSize.height 77 | - scrollView.refreshInset.bottom 78 | } else { 79 | offset = scrollView.contentOffset.y + scrollView.refreshInset.top 80 | } 81 | 82 | offsetDidChange(-offset) 83 | 84 | didChangeAlpha(by: -offset) 85 | 86 | guard isEnabled else { return } 87 | 88 | if scrollView.isDragging, triggerAutoRefresh(by: offset) { 89 | scrollView.contentOffset.y -= offset 90 | defer { scrollView.contentOffset.y += offset } 91 | beginRefreshing() 92 | return 93 | } 94 | 95 | didChangeState(by: -offset) 96 | } 97 | 98 | override func scrollViewContentSizeDidChange(_ scrollView: UIScrollView) { 99 | super.scrollViewContentSizeDidChange(scrollView) 100 | 101 | setNeedsUpdateConstraints() 102 | } 103 | 104 | func triggerAutoRefresh(by offset: CGFloat) -> Bool { false } 105 | } 106 | -------------------------------------------------------------------------------- /Example/EasyRefresher/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /EasyRefresher/Classes/RefreshTrailer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RefreshTrailer.swift 3 | // EasyRefresher 4 | // 5 | // Created by Pircate(swifter.dev@gmail.com) on 2020/6/14 6 | // Copyright © 2020 Pircate. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public protocol TrailerRefresher: Refresher {} 12 | 13 | open class RefreshTrailer: RefreshComponent, TrailerRefresher { 14 | 15 | override var scrollView: UIScrollView? { 16 | didSet { scrollView?.alwaysBounceHorizontal = true } 17 | } 18 | 19 | private lazy var constraintOfLeftAnchor: NSLayoutConstraint? = { 20 | guard let scrollView = scrollView, isDescendant(of: scrollView) else { return nil } 21 | 22 | let constraint = leftAnchor.constraint(equalTo: scrollView.leftAnchor) 23 | constraint.isActive = true 24 | 25 | return constraint 26 | }() 27 | 28 | open override func updateConstraints() { 29 | super.updateConstraints() 30 | 31 | guard let scrollView = scrollView else { return } 32 | 33 | constraintOfLeftAnchor?.constant = scrollView.contentSize.width 34 | } 35 | 36 | override func buildStackView() -> UIStackView { 37 | let stackView = super.buildStackView() 38 | stackView.transform = .init(rotationAngle: -.pi / 2) 39 | return stackView 40 | } 41 | 42 | override func prepare() { 43 | super.prepare() 44 | 45 | alpha = 0 46 | setTitle("pull_left_to_refresh".localized(), for: .pulling) 47 | setTitle("release_to_load_more".localized(), for: .willRefresh) 48 | } 49 | 50 | override func add(to scrollView: UIScrollView) { 51 | guard !scrollView.subviews.contains(self) else { return } 52 | 53 | scrollView.addSubview(self) 54 | 55 | translatesAutoresizingMaskIntoConstraints = false 56 | topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true 57 | heightAnchor.constraint(equalTo: scrollView.heightAnchor).isActive = true 58 | widthAnchor.constraint(equalToConstant: height).isActive = true 59 | } 60 | 61 | override func willBeginRefreshing(completion: @escaping () -> Void) { 62 | guard let scrollView = scrollView else { return } 63 | 64 | alpha = 1 65 | 66 | UIView.animate(withDuration: 0.25, animations: { 67 | scrollView.contentInset.right = self.originalInset.right + self.height 68 | scrollView.changed_inset.right = self.height 69 | }, completion: { _ in completion() }) 70 | } 71 | 72 | override func didEndRefreshing(completion: @escaping () -> Void) { 73 | guard let scrollView = scrollView else { return } 74 | 75 | UIView.animate(withDuration: 0.25, animations: { 76 | self.alpha = 0 77 | scrollView.contentInset.right -= scrollView.changed_inset.right 78 | scrollView.changed_inset.right = 0 79 | }, completion: { _ in completion() }) 80 | } 81 | 82 | override func scrollViewContentInsetDidReset(_ scrollView: UIScrollView) { 83 | scrollView.contentInset.right -= scrollView.changed_inset.right 84 | scrollView.changed_inset.right = 0 85 | } 86 | 87 | override func scrollViewContentOffsetDidChange(_ scrollView: UIScrollView) { 88 | guard !isHidden else { return } 89 | 90 | let offset: CGFloat 91 | 92 | if scrollView.refreshInset.left + scrollView.contentSize.width >= scrollView.bounds.width { 93 | offset = scrollView.contentOffset.x 94 | + scrollView.bounds.width 95 | - scrollView.contentSize.width 96 | - scrollView.refreshInset.right 97 | } else { 98 | offset = scrollView.contentOffset.x + scrollView.refreshInset.left 99 | } 100 | 101 | offsetDidChange(-offset) 102 | 103 | didChangeAlpha(by: -offset) 104 | 105 | guard isEnabled else { return } 106 | 107 | didChangeState(by: -offset) 108 | } 109 | 110 | override func scrollViewContentSizeDidChange(_ scrollView: UIScrollView) { 111 | super.scrollViewContentSizeDidChange(scrollView) 112 | 113 | setNeedsUpdateConstraints() 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Example/EasyRefresher/GIFRefreshHeaderViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GIFRefreshHeaderViewController.swift 3 | // EasyRefresher_Example 4 | // 5 | // Created by Pircate(swifter.dev@gmail.com) on 2019/5/13 6 | // Copyright © 2019 CocoaPods. All rights reserved. 7 | // 8 | 9 | import EasyRefresher 10 | 11 | class GIFRefreshHeaderViewController: UIViewController { 12 | 13 | @IBOutlet weak var tableView: UITableView! 14 | 15 | var dataArray: [String] = ["", "", "", "", ""] 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cellID") 21 | 22 | let images = (1...60).compactMap { UIImage(named: "dropdown_anim__000\($0)") } 23 | 24 | tableView.refresh.header = RefreshHeader(animationImages: images) { 25 | self.reqeust { 26 | self.dataArray = ["", "", "", "", ""] 27 | self.tableView.refresh.header.endRefreshing() 28 | self.tableView.reloadData() 29 | } 30 | } 31 | 32 | tableView.refresh.header.automaticallyChangeAlpha = false 33 | 34 | tableView.refresh.header.beginRefreshing() 35 | 36 | tableView.refresh.footer.addRefreshClosure { 37 | self.reqeust { 38 | self.dataArray.append(contentsOf: ["", "", "", "", ""]) 39 | self.tableView.refresh.footer.endRefreshing() 40 | self.tableView.reloadData() 41 | } 42 | } 43 | } 44 | 45 | private func reqeust(completion: @escaping () -> Void) { 46 | DispatchQueue.global().asyncAfter(deadline: .now() + 3) { 47 | DispatchQueue.main.async { 48 | completion() 49 | } 50 | } 51 | } 52 | } 53 | 54 | extension GIFRefreshHeaderViewController: UITableViewDataSource { 55 | 56 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 57 | return dataArray.count 58 | } 59 | 60 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 61 | let cell = tableView.dequeueReusableCell(withIdentifier: "cellID")! 62 | cell.textLabel?.text = "\(indexPath.row)" 63 | return cell 64 | } 65 | } 66 | 67 | extension RefreshHeader { 68 | 69 | convenience init(animationImages: [UIImage], refreshClosure: @escaping () -> Void) { 70 | self.init( 71 | stateView: AnimatedStateView(animationImages: animationImages), 72 | refreshClosure: refreshClosure 73 | ) 74 | } 75 | } 76 | 77 | public class AnimatedStateView: UIView { 78 | 79 | private lazy var gifImageView: UIImageView = { 80 | return UIImageView() 81 | }() 82 | 83 | public init(animationImages: [UIImage]) { 84 | super.init(frame: .zero) 85 | 86 | self.gifImageView.animationImages = animationImages 87 | 88 | addSubview(gifImageView) 89 | 90 | gifImageView.translatesAutoresizingMaskIntoConstraints = false 91 | gifImageView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true 92 | gifImageView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true 93 | gifImageView.widthAnchor.constraint(equalToConstant: 54).isActive = true 94 | gifImageView.heightAnchor.constraint(equalToConstant: 54).isActive = true 95 | } 96 | 97 | required init?(coder aDecoder: NSCoder) { 98 | fatalError("init(coder:) has not been implemented") 99 | } 100 | } 101 | 102 | extension AnimatedStateView: RefreshStateful { 103 | 104 | public func refresher(_ refresher: Refresher, didChangeState state: RefreshState) { 105 | switch state { 106 | case .idle: 107 | gifImageView.isHidden = true 108 | gifImageView.stopAnimating() 109 | case .pulling: 110 | gifImageView.isHidden = false 111 | gifImageView.stopAnimating() 112 | case .willRefresh: 113 | gifImageView.isHidden = false 114 | gifImageView.stopAnimating() 115 | gifImageView.image = gifImageView.animationImages?.last 116 | case .refreshing: 117 | gifImageView.isHidden = false 118 | gifImageView.startAnimating() 119 | case .disabled: 120 | break 121 | } 122 | } 123 | 124 | public func refresher(_ refresher: Refresher, didChangeOffset offset: CGFloat) { 125 | guard let animationImages = gifImageView.animationImages else { return } 126 | 127 | let index = Int(offset / refresher.height * CGFloat(animationImages.count) - 1) 128 | gifImageView.image = animationImages[index] 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Example/EasyRefresher.xcodeproj/xcshareddata/xcschemes/EasyRefresher-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 51 | 52 | 53 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 77 | 79 | 85 | 86 | 87 | 88 | 94 | 96 | 102 | 103 | 104 | 105 | 107 | 108 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /EasyRefresher/Classes/UIScrollView+Refresh.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIScrollView+Refresh.swift 3 | // EasyRefresher 4 | // 5 | // Created by Pircate(swifter.dev@gmail.com) on 2019/4/26 6 | // Copyright © 2019 Pircate. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ObjectiveC 11 | 12 | public typealias Refresher = Refreshable & HasStateTitle & UserInterfacable 13 | 14 | extension UIScrollView { 15 | 16 | var refresh_header: HeaderRefresher { 17 | get { 18 | if let obj = objcGetAssociatedObject(for: &AssociatedKeys.header) as? HeaderRefresher { 19 | return obj 20 | } 21 | 22 | let header = RefreshHeader() 23 | header.scrollView = self 24 | 25 | objcSetAssociatedObject(header, for: &AssociatedKeys.header) 26 | 27 | return header 28 | } 29 | set { 30 | if let obj = objcGetAssociatedObject(for: &AssociatedKeys.header) as? RefreshHeader { 31 | // 结构体属性的属性调用 setter 方法会一层层传递到上层属性的 setter。 32 | // 若 newValue 是自身则不重复赋值,否则需要移除掉旧的 refresher。 33 | if obj === newValue { return } 34 | 35 | obj.removeFromScrollView() 36 | } 37 | 38 | objcSetAssociatedObject(newValue, for: &AssociatedKeys.header) 39 | 40 | guard let header = newValue as? RefreshHeader else { 41 | fatalError("Please use RefreshHeader or it's subclass.") 42 | } 43 | 44 | header.scrollView = self 45 | header.add(to: self) 46 | } 47 | } 48 | 49 | var refresh_footer: FooterRefresher { 50 | get { 51 | if let obj = objcGetAssociatedObject(for: &AssociatedKeys.footer) as? FooterRefresher { 52 | return obj 53 | } 54 | 55 | let footer = RefreshFooter() 56 | footer.scrollView = self 57 | 58 | objcSetAssociatedObject(footer, for: &AssociatedKeys.footer) 59 | 60 | return footer 61 | } 62 | set { 63 | if let obj = objcGetAssociatedObject(for: &AssociatedKeys.footer) as? RefreshFooter { 64 | if obj === newValue { return } 65 | 66 | obj.removeFromScrollView() 67 | } 68 | 69 | objcSetAssociatedObject(newValue, for: &AssociatedKeys.footer) 70 | 71 | guard let footer = newValue as? RefreshFooter else { 72 | fatalError("Please use RefreshFooter or it's subclass.") 73 | } 74 | 75 | footer.scrollView = self 76 | footer.add(to: self) 77 | } 78 | } 79 | 80 | var refresh_trailer: TrailerRefresher { 81 | get { 82 | if let obj = objcGetAssociatedObject(for: &AssociatedKeys.trailer) as? TrailerRefresher { 83 | return obj 84 | } 85 | 86 | let trailer = RefreshTrailer() 87 | trailer.scrollView = self 88 | 89 | objcSetAssociatedObject(trailer, for: &AssociatedKeys.trailer) 90 | 91 | return trailer 92 | } 93 | set { 94 | if let obj = objcGetAssociatedObject(for: &AssociatedKeys.trailer) as? RefreshTrailer { 95 | if obj === newValue { return } 96 | 97 | obj.removeFromScrollView() 98 | } 99 | 100 | objcSetAssociatedObject(newValue, for: &AssociatedKeys.trailer) 101 | 102 | guard let trailer = newValue as? RefreshTrailer else { 103 | fatalError("Please use RefreshTrailer or it's subclass.") 104 | } 105 | 106 | trailer.scrollView = self 107 | trailer.add(to: self) 108 | } 109 | } 110 | 111 | var changed_inset: UIEdgeInsets { 112 | get { 113 | if let obj = objcGetAssociatedObject(for: &AssociatedKeys.changedInset) as? UIEdgeInsets { 114 | return obj 115 | } 116 | 117 | objcSetAssociatedObject(UIEdgeInsets.zero, for: &AssociatedKeys.changedInset) 118 | 119 | return .zero 120 | } 121 | set { 122 | objcSetAssociatedObject(newValue, for: &AssociatedKeys.changedInset) 123 | } 124 | } 125 | } 126 | 127 | private extension UIScrollView { 128 | 129 | func objcGetAssociatedObject(for key: UnsafeRawPointer) -> Any? { 130 | return objc_getAssociatedObject(self, key) 131 | } 132 | 133 | func objcSetAssociatedObject(_ value: Any?, for key: UnsafeRawPointer) { 134 | objc_setAssociatedObject(self, key, value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 135 | } 136 | } 137 | 138 | struct AssociatedKeys { 139 | 140 | static var header = "com.pircate.github.refresh.header" 141 | 142 | static var footer = "com.pircate.github.refresh.footer" 143 | 144 | static var trailer = "com.pircate.github.refresh.trailer" 145 | 146 | static var changedInset = "com.pircate.github.changed.inset" 147 | } 148 | 149 | extension UIScrollView { 150 | 151 | var refreshInset: UIEdgeInsets { 152 | guard #available(iOS 11.0, *) else { 153 | return contentInset 154 | } 155 | 156 | return adjustedContentInset 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /EasyRefresher/Classes/RefreshHeader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RefreshHeader.swift 3 | // EasyRefresher 4 | // 5 | // Created by Pircate(swifter.dev@gmail.com) on 2019/4/26 6 | // Copyright © 2019 Pircate. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public enum LastUpdatedTimeStrategy { 12 | case none 13 | case `default` 14 | case custom((Date?) -> String) 15 | } 16 | 17 | public protocol HeaderRefresher: Refresher { 18 | 19 | /// The text of time when refresher last updated. 20 | var lastUpdatedTimeStrategy: LastUpdatedTimeStrategy { get set } 21 | } 22 | 23 | open class RefreshHeader: RefreshComponent, HeaderRefresher { 24 | 25 | public var lastUpdatedTimeStrategy: LastUpdatedTimeStrategy = .default 26 | 27 | private lazy var lastUpdatedLabel: UILabel = { 28 | let lastUpdatedLabel = UILabel() 29 | lastUpdatedLabel.font = UIFont.systemFont(ofSize: 12) 30 | lastUpdatedLabel.textAlignment = .center 31 | lastUpdatedLabel.textColor = .darkGray 32 | return lastUpdatedLabel 33 | }() 34 | 35 | private let lastUpdatedTimekey = "com.pircate.github.lastUpdatedTime" 36 | 37 | private var lastUpdatedTime: Date? { 38 | get { UserDefaults.standard.object(forKey: lastUpdatedTimekey) as? Date } 39 | set { UserDefaults.standard.set(newValue, forKey: lastUpdatedTimekey) } 40 | } 41 | 42 | override func buildStackView() -> UIStackView { 43 | let vStackView = UIStackView(arrangedSubviews: [stateLabel, lastUpdatedLabel]) 44 | vStackView.axis = .vertical 45 | vStackView.spacing = 5 46 | vStackView.alignment = .center 47 | 48 | let hStackView = UIStackView(arrangedSubviews: [activityIndicator, arrowImageView, vStackView]) 49 | hStackView.spacing = 8 50 | hStackView.alignment = .center 51 | return hStackView 52 | } 53 | 54 | override func add(to scrollView: UIScrollView) { 55 | super.add(to: scrollView) 56 | 57 | bottomAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true 58 | } 59 | 60 | override func prepare() { 61 | super.prepare() 62 | 63 | alpha = 0 64 | setTitle("pull_down_to_refresh".localized(), for: .pulling) 65 | setTitle("release_to_refresh".localized(), for: .willRefresh) 66 | } 67 | 68 | override func stateDidChange(_ state: RefreshState) { 69 | super.stateDidChange(state) 70 | 71 | updateLastUpdatedTime(for: state) 72 | } 73 | 74 | override func willBeginRefreshing(completion: @escaping () -> Void) { 75 | guard let scrollView = scrollView else { return } 76 | 77 | alpha = 1 78 | 79 | UIView.animate(withDuration: 0.25, animations: { 80 | scrollView.contentInset.top = self.originalInset.top + self.height 81 | scrollView.changed_inset.top = self.height 82 | }, completion: { _ in completion() }) 83 | } 84 | 85 | override func didEndRefreshing(completion: @escaping () -> Void) { 86 | guard let scrollView = scrollView else { return } 87 | 88 | UIView.animate(withDuration: 0.25, animations: { 89 | self.alpha = 0 90 | scrollView.contentInset.top -= scrollView.changed_inset.top 91 | scrollView.changed_inset.top = 0 92 | }, completion: { _ in 93 | self.lastUpdatedTime = Date() 94 | completion() 95 | }) 96 | } 97 | 98 | override func scrollViewContentInsetDidReset(_ scrollView: UIScrollView) { 99 | scrollView.contentInset.top -= scrollView.changed_inset.top 100 | scrollView.changed_inset.top = 0 101 | } 102 | 103 | override func scrollViewContentOffsetDidChange(_ scrollView: UIScrollView) { 104 | guard !isHidden else { return } 105 | 106 | let offset = scrollView.contentOffset.y + scrollView.refreshInset.top 107 | 108 | offsetDidChange(offset) 109 | 110 | didChangeAlpha(by: offset) 111 | 112 | guard isEnabled else { return } 113 | 114 | didChangeState(by: offset) 115 | } 116 | 117 | private func updateLastUpdatedTime(for state: RefreshState) { 118 | guard state != .idle, state != .disabled else { 119 | lastUpdatedLabel.isHidden = true 120 | return 121 | } 122 | 123 | switch lastUpdatedTimeStrategy { 124 | case .none: 125 | lastUpdatedLabel.isHidden = true 126 | case .default: 127 | lastUpdatedLabel.isHidden = false 128 | guard let lastUpdatedTime = lastUpdatedTime else { 129 | lastUpdatedLabel.text = "\("last_update_time".localized())\("no_record".localized())" 130 | return 131 | } 132 | 133 | lastUpdatedLabel.text = "\("last_update_time".localized())\(lastUpdatedTime.lastUpdatedTimeString)" 134 | case .custom(let closure): 135 | lastUpdatedLabel.isHidden = false 136 | lastUpdatedLabel.text = closure(lastUpdatedTime) 137 | } 138 | } 139 | } 140 | 141 | private extension Date { 142 | 143 | var lastUpdatedTimeString: String { 144 | let isToday = Calendar.refresh.isDateInToday(self) 145 | DateFormatter.refresh.dateFormat = isToday ? "HH:mm" : "yyyy-MM-dd HH:mm" 146 | 147 | let dateString = DateFormatter.refresh.string(from: self) 148 | return isToday ? "\("today".localized()) \(dateString)" : dateString 149 | } 150 | } 151 | 152 | private extension DateFormatter { 153 | 154 | static let refresh: DateFormatter = { 155 | let dateFormatter = DateFormatter() 156 | dateFormatter.dateFormat = "yyyy-MM-dd HH:mm" 157 | return dateFormatter 158 | }() 159 | } 160 | 161 | private extension Calendar { 162 | 163 | static let refresh: Calendar = { Calendar(identifier: .gregorian) }() 164 | } 165 | -------------------------------------------------------------------------------- /Example/EasyRefresher/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 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-EasyRefresher_Example/Pods-EasyRefresher_Example-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | set -u 4 | set -o pipefail 5 | 6 | function on_error { 7 | echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" 8 | } 9 | trap 'on_error $LINENO' ERR 10 | 11 | if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then 12 | # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy 13 | # frameworks to, so exit 0 (signalling the script phase was successful). 14 | exit 0 15 | fi 16 | 17 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 18 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 19 | 20 | COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}" 21 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 22 | 23 | # Used as a return value for each invocation of `strip_invalid_archs` function. 24 | STRIP_BINARY_RETVAL=0 25 | 26 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 27 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 28 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 29 | 30 | # Copies and strips a vendored framework 31 | install_framework() 32 | { 33 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 34 | local source="${BUILT_PRODUCTS_DIR}/$1" 35 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 36 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 37 | elif [ -r "$1" ]; then 38 | local source="$1" 39 | fi 40 | 41 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 42 | 43 | if [ -L "${source}" ]; then 44 | echo "Symlinked..." 45 | source="$(readlink "${source}")" 46 | fi 47 | 48 | # Use filter instead of exclude so missing patterns don't throw errors. 49 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 50 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 51 | 52 | local basename 53 | basename="$(basename -s .framework "$1")" 54 | binary="${destination}/${basename}.framework/${basename}" 55 | 56 | if ! [ -r "$binary" ]; then 57 | binary="${destination}/${basename}" 58 | elif [ -L "${binary}" ]; then 59 | echo "Destination binary is symlinked..." 60 | dirname="$(dirname "${binary}")" 61 | binary="${dirname}/$(readlink "${binary}")" 62 | fi 63 | 64 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 65 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 66 | strip_invalid_archs "$binary" 67 | fi 68 | 69 | # Resign the code if required by the build settings to avoid unstable apps 70 | code_sign_if_enabled "${destination}/$(basename "$1")" 71 | 72 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 73 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 74 | local swift_runtime_libs 75 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u) 76 | for lib in $swift_runtime_libs; do 77 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 78 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 79 | code_sign_if_enabled "${destination}/${lib}" 80 | done 81 | fi 82 | } 83 | 84 | # Copies and strips a vendored dSYM 85 | install_dsym() { 86 | local source="$1" 87 | warn_missing_arch=${2:-true} 88 | if [ -r "$source" ]; then 89 | # Copy the dSYM into the targets temp dir. 90 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" 91 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" 92 | 93 | local basename 94 | basename="$(basename -s .dSYM "$source")" 95 | binary_name="$(ls "$source/Contents/Resources/DWARF")" 96 | binary="${DERIVED_FILES_DIR}/${basename}.dSYM/Contents/Resources/DWARF/${binary_name}" 97 | 98 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 99 | if [[ "$(file "$binary")" == *"Mach-O "*"dSYM companion"* ]]; then 100 | strip_invalid_archs "$binary" "$warn_missing_arch" 101 | fi 102 | 103 | if [[ $STRIP_BINARY_RETVAL == 1 ]]; then 104 | # Move the stripped file into its final destination. 105 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" 106 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.dSYM" "${DWARF_DSYM_FOLDER_PATH}" 107 | else 108 | # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. 109 | touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.dSYM" 110 | fi 111 | fi 112 | } 113 | 114 | # Copies the bcsymbolmap files of a vendored framework 115 | install_bcsymbolmap() { 116 | local bcsymbolmap_path="$1" 117 | local destination="${BUILT_PRODUCTS_DIR}" 118 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"" 119 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}" 120 | } 121 | 122 | # Signs a framework with the provided identity 123 | code_sign_if_enabled() { 124 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 125 | # Use the current code_sign_identity 126 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 127 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'" 128 | 129 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 130 | code_sign_cmd="$code_sign_cmd &" 131 | fi 132 | echo "$code_sign_cmd" 133 | eval "$code_sign_cmd" 134 | fi 135 | } 136 | 137 | # Strip invalid architectures 138 | strip_invalid_archs() { 139 | binary="$1" 140 | warn_missing_arch=${2:-true} 141 | # Get architectures for current target binary 142 | binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" 143 | # Intersect them with the architectures we are building for 144 | intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" 145 | # If there are no archs supported by this binary then warn the user 146 | if [[ -z "$intersected_archs" ]]; then 147 | if [[ "$warn_missing_arch" == "true" ]]; then 148 | echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." 149 | fi 150 | STRIP_BINARY_RETVAL=0 151 | return 152 | fi 153 | stripped="" 154 | for arch in $binary_archs; do 155 | if ! [[ "${ARCHS}" == *"$arch"* ]]; then 156 | # Strip non-valid architectures in-place 157 | lipo -remove "$arch" -output "$binary" "$binary" 158 | stripped="$stripped $arch" 159 | fi 160 | done 161 | if [[ "$stripped" ]]; then 162 | echo "Stripped $binary of architectures:$stripped" 163 | fi 164 | STRIP_BINARY_RETVAL=1 165 | } 166 | 167 | install_artifact() { 168 | artifact="$1" 169 | base="$(basename "$artifact")" 170 | case $base in 171 | *.framework) 172 | install_framework "$artifact" 173 | ;; 174 | *.dSYM) 175 | # Suppress arch warnings since XCFrameworks will include many dSYM files 176 | install_dsym "$artifact" "false" 177 | ;; 178 | *.bcsymbolmap) 179 | install_bcsymbolmap "$artifact" 180 | ;; 181 | *) 182 | echo "error: Unrecognized artifact "$artifact"" 183 | ;; 184 | esac 185 | } 186 | 187 | copy_artifacts() { 188 | file_list="$1" 189 | while read artifact; do 190 | install_artifact "$artifact" 191 | done <$file_list 192 | } 193 | 194 | ARTIFACT_LIST_FILE="${BUILT_PRODUCTS_DIR}/cocoapods-artifacts-${CONFIGURATION}.txt" 195 | if [ -r "${ARTIFACT_LIST_FILE}" ]; then 196 | copy_artifacts "${ARTIFACT_LIST_FILE}" 197 | fi 198 | 199 | if [[ "$CONFIGURATION" == "Debug" ]]; then 200 | install_framework "${BUILT_PRODUCTS_DIR}/EasyRefresher/EasyRefresher.framework" 201 | fi 202 | if [[ "$CONFIGURATION" == "Release" ]]; then 203 | install_framework "${BUILT_PRODUCTS_DIR}/EasyRefresher/EasyRefresher.framework" 204 | fi 205 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 206 | wait 207 | fi 208 | -------------------------------------------------------------------------------- /EasyRefresher/Classes/RefreshComponent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RefreshComponent.swift 3 | // EasyRefresher 4 | // 5 | // Created by Pircate(swifter.dev@gmail.com) on 2019/5/8 6 | // Copyright © 2019 Pircate. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public protocol RefreshDelegate: class { 12 | func refresherDidRefresh(_ refresher: Refresher) 13 | } 14 | 15 | open class RefreshComponent: RefreshStatefulView { 16 | 17 | internal(set) public var state: RefreshState = .idle { 18 | didSet { 19 | guard state != oldValue else { return } 20 | 21 | stateDidChange(state) 22 | } 23 | } 24 | 25 | public var refreshClosure: (() -> Void)? 26 | 27 | weak var scrollView: UIScrollView? { 28 | didSet { scrollView?.alwaysBounceVertical = true } 29 | } 30 | 31 | lazy var originalInset: UIEdgeInsets = { 32 | guard let scrollView = scrollView else { return .zero } 33 | 34 | return scrollView.contentInset 35 | }() 36 | 37 | private weak var delegate: RefreshDelegate? 38 | 39 | private var isEnding: Bool = false 40 | 41 | private lazy var observation: ScrollViewObservation = { ScrollViewObservation() }() 42 | 43 | // MARK: - life cycle 44 | public init(height: CGFloat = 54, refreshClosure: @escaping () -> Void) { 45 | self.refreshClosure = refreshClosure 46 | 47 | super.init(height: height) 48 | 49 | prepare() 50 | } 51 | 52 | public init( 53 | stateView: T, 54 | height: CGFloat = 54, 55 | refreshClosure: @escaping () -> Void 56 | ) where T: UIView, T: RefreshStateful { 57 | self.refreshClosure = refreshClosure 58 | 59 | super.init(empty: height) 60 | 61 | prepare() 62 | 63 | addStateView(stateView) 64 | didChangeStateView(stateView) 65 | } 66 | 67 | public init(height: CGFloat = 54, delegate: RefreshDelegate) { 68 | self.delegate = delegate 69 | 70 | super.init(height: height) 71 | 72 | prepare() 73 | } 74 | 75 | public init( 76 | stateView: T, 77 | height: CGFloat = 54, 78 | delegate: RefreshDelegate 79 | ) where T: UIView, T: RefreshStateful { 80 | self.delegate = delegate 81 | 82 | super.init(empty: height) 83 | 84 | prepare() 85 | 86 | addStateView(stateView) 87 | didChangeStateView(stateView) 88 | } 89 | 90 | public required init?(coder aDecoder: NSCoder) { 91 | super.init(coder: aDecoder) 92 | 93 | prepare() 94 | } 95 | 96 | override init(height: CGFloat = 54) { 97 | super.init(height: height) 98 | 99 | prepare() 100 | } 101 | 102 | // MARK: - override 103 | open override func willMove(toSuperview newSuperview: UIView?) { 104 | super.willMove(toSuperview: newSuperview) 105 | 106 | observation.invalidate() 107 | 108 | guard let scrollView = newSuperview as? UIScrollView else { return } 109 | 110 | observe(scrollView) 111 | } 112 | 113 | func add(to scrollView: UIScrollView) { 114 | guard !scrollView.subviews.contains(self) else { return } 115 | 116 | scrollView.addSubview(self) 117 | 118 | translatesAutoresizingMaskIntoConstraints = false 119 | leftAnchor.constraint(equalTo: scrollView.leftAnchor).isActive = true 120 | widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true 121 | heightAnchor.constraint(equalToConstant: height).isActive = true 122 | } 123 | 124 | func prepare() { 125 | setTitle("loading".localized(), for: .refreshing) 126 | setTitle("no_more_data".localized(), for: .disabled) 127 | } 128 | 129 | func didChangeState(by offset: CGFloat) { 130 | switch offset { 131 | case 0...: 132 | state = .idle 133 | case -height..<0: 134 | state = .pulling 135 | default: 136 | state = .willRefresh 137 | } 138 | } 139 | 140 | func willBeginRefreshing(completion: @escaping () -> Void) {} 141 | 142 | func didEndRefreshing(completion: @escaping () -> Void) {} 143 | 144 | func scrollViewContentInsetDidReset(_ scrollView: UIScrollView) {} 145 | 146 | func scrollViewContentOffsetDidChange(_ scrollView: UIScrollView) {} 147 | 148 | func scrollViewContentSizeDidChange(_ scrollView: UIScrollView) {} 149 | 150 | func scrollViewPanGestureStateDidChange(_ scrollView: UIScrollView) { 151 | guard scrollView.panGestureRecognizer.state == .ended, state == .willRefresh else { return } 152 | 153 | beginRefreshing() 154 | } 155 | } 156 | 157 | extension RefreshComponent { 158 | 159 | func didChangeAlpha(by offset: CGFloat) { 160 | guard automaticallyChangeAlpha else { 161 | alpha = 1 162 | return 163 | } 164 | 165 | switch offset { 166 | case 0...: 167 | alpha = 0 168 | case -height..<0: 169 | alpha = -offset / height 170 | default: 171 | alpha = 1 172 | } 173 | } 174 | } 175 | 176 | // MARK: - private 177 | private extension RefreshComponent { 178 | 179 | var isDescendantOfScrollView: Bool { 180 | guard let scrollView = scrollView else { return false } 181 | 182 | return isDescendant(of: scrollView) 183 | } 184 | 185 | func observe(_ scrollView: UIScrollView) { 186 | observation.observe(scrollView) { [weak self] this, keyPath in 187 | guard let `self` = self else { return } 188 | 189 | switch keyPath { 190 | case .contentOffset: 191 | this.bringSubviewToFront(self) 192 | 193 | guard !self.isRefreshing else { return } 194 | 195 | self.scrollViewContentOffsetDidChange(this) 196 | case .contentSize: 197 | self.scrollViewContentSizeDidChange(this) 198 | case .panGestureState: 199 | self.scrollViewPanGestureStateDidChange(this) 200 | } 201 | } 202 | } 203 | 204 | func addStateView(_ stateView: UIView) { 205 | addSubview(stateView) 206 | 207 | stateView.translatesAutoresizingMaskIntoConstraints = false 208 | stateView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true 209 | stateView.topAnchor.constraint(equalTo: topAnchor).isActive = true 210 | stateView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true 211 | stateView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true 212 | } 213 | 214 | func didChangeStateView(_ stateView: RefreshStateful) { 215 | stateChanged = { [weak self] in 216 | guard let self = self else { return } 217 | 218 | stateView.refresher(self, didChangeState: $0) 219 | } 220 | 221 | offsetChanged = { [weak self] in 222 | guard let self = self else { return } 223 | 224 | stateView.refresher(self, didChangeOffset: $0) 225 | } 226 | } 227 | 228 | func prepareForRefreshing() { 229 | guard let scrollView = scrollView else { return } 230 | 231 | var contentInset = scrollView.contentInset 232 | contentInset.top -= scrollView.changed_inset.top 233 | contentInset.left -= scrollView.changed_inset.left 234 | contentInset.bottom -= scrollView.changed_inset.bottom 235 | contentInset.right -= scrollView.changed_inset.right 236 | 237 | originalInset = contentInset 238 | } 239 | 240 | func endRefreshing(to state: RefreshState) { 241 | assert(isDescendantOfScrollView, "Please add refresher to UIScrollView before end refreshing.") 242 | 243 | guard isRefreshing, !isEnding else { return } 244 | 245 | isEnding = true 246 | 247 | didEndRefreshing { 248 | self.state = state 249 | self.isEnding = false 250 | } 251 | } 252 | 253 | func shouldRefreshing() -> Bool { 254 | return !isRefreshing && isEnabled && !isHidden 255 | } 256 | } 257 | 258 | // MARK: - Refresher 259 | extension RefreshComponent: Refreshable { 260 | 261 | public var isEnabled: Bool { 262 | get { state != .disabled } 263 | set { 264 | if newValue { 265 | guard state == .disabled else { return } 266 | 267 | state = .idle 268 | } else { 269 | endRefreshing(to: .disabled) 270 | } 271 | } 272 | } 273 | 274 | open override var isHidden: Bool { 275 | didSet { 276 | guard oldValue != isHidden, isHidden else { return } 277 | 278 | endRefreshing() 279 | } 280 | } 281 | 282 | public func addRefreshClosure(_ refreshClosure: @escaping () -> Void) { 283 | self.refreshClosure = refreshClosure 284 | 285 | guard let scrollView = scrollView else { return } 286 | 287 | add(to: scrollView) 288 | } 289 | 290 | public func beginRefreshing() { 291 | assert(isDescendantOfScrollView, "Please add refresher to UIScrollView before begin refreshing.") 292 | 293 | guard shouldRefreshing() else { return } 294 | 295 | prepareForRefreshing() 296 | state = .refreshing 297 | willBeginRefreshing { 298 | self.refreshClosure?() 299 | 300 | self.delegate?.refresherDidRefresh(self) 301 | } 302 | } 303 | 304 | public func endRefreshing() { 305 | endRefreshing(to: .idle) 306 | } 307 | 308 | public func removeFromScrollView() { 309 | guard let scrollView = superview as? UIScrollView else { return } 310 | 311 | scrollViewContentInsetDidReset(scrollView) 312 | 313 | removeFromSuperview() 314 | } 315 | } 316 | --------------------------------------------------------------------------------