├── .github └── issue_template.md ├── .gitignore ├── .swift-version ├── .travis.yml ├── Assets ├── Gif │ ├── _allen.gif │ ├── _arc.gif │ ├── _arrow.gif │ ├── _circle.gif │ ├── _dot.gif │ ├── _native.gif │ ├── _ring.gif │ ├── _triangle.gif │ ├── _woody.gif │ ├── allen.gif │ ├── arc.gif │ ├── arrow.gif │ ├── circle.gif │ ├── dot.gif │ ├── native.gif │ ├── ring.gif │ ├── triangle.gif │ └── woody.gif └── titleView.png ├── CREADME.md ├── Demo ├── .DS_Store ├── KafkaExample.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── KafkaExample.xcscheme ├── KafkaExample │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── logoiPadApp_76pt.png │ │ │ ├── logoiPadApp_76pt@2x.png │ │ │ ├── logoiPadNotifications_20pt.png │ │ │ ├── logoiPadNotifications_20pt@2x.png │ │ │ ├── logoiPadProApp_83.5pt@2x.png │ │ │ ├── logoiPadSpootlight5_29pt.png │ │ │ ├── logoiPadSpootlight5_29pt@2x.png │ │ │ ├── logoiPadSpootlight7_40pt.png │ │ │ ├── logoiPadSpootlight7_40pt@2x.png │ │ │ ├── logoiPhoneApp_60pt@2x.png │ │ │ ├── logoiPhoneApp_60pt@3x.png │ │ │ ├── logoiPhoneNotification_20pt@2x.png │ │ │ ├── logoiPhoneNotification_20pt@3x.png │ │ │ ├── logoiPhoneSpootlight5_29pt@2x.png │ │ │ ├── logoiPhoneSpootlight5_29pt@3x.png │ │ │ ├── logoiPhoneSpootlight7_40pt@2x.png │ │ │ ├── logoiPhoneSpootlight7_40pt@3x.png │ │ │ └── logostore_1024pt.png │ │ ├── Contents.json │ │ ├── LaunchImage.launchimage │ │ │ ├── Contents.json │ │ │ ├── launchiPadPortraitWOSBiOS56_768x1004pt.png │ │ │ ├── launchiPadPortraitWOSBiOS56_768x1004pt@2x.png │ │ │ ├── launchiPadPortraitiOS56_768x1024pt.png │ │ │ ├── launchiPadPortraitiOS56_768x1024pt@2x.png │ │ │ ├── launchiPadPortraitiOS789_768x1024pt.png │ │ │ ├── launchiPadPortraitiOS789_768x1024pt@2x.png │ │ │ ├── launchiPhonePortraitiOS56_320x480pt.png │ │ │ ├── launchiPhonePortraitiOS56_320x480pt@2x.png │ │ │ ├── launchiPhonePortraitiOS56_320x568pt@2x.png │ │ │ ├── launchiPhonePortraitiOS789_320x480pt@2x.png │ │ │ ├── launchiPhonePortraitiOS789_320x568pt@2x.png │ │ │ ├── launchiPhonePortraitiOS89_375x667pt@2x.png │ │ │ ├── launchiPhonePortraitiOS89_414x736pt@3x.png │ │ │ └── launchiPhoneXPortraitiOS11_375x812pt@3x.png │ │ └── icon.imageset │ │ │ ├── Contents.json │ │ │ └── icon.png │ ├── Base.lproj │ │ └── Main.storyboard │ ├── Controllers │ │ ├── KafkaCollectionViewController.h │ │ ├── KafkaCollectionViewController.m │ │ ├── KafkaCustomTableViewController.h │ │ ├── KafkaCustomTableViewController.m │ │ ├── KafkaMainListController.h │ │ ├── KafkaMainListController.m │ │ ├── KafkaScrollViewController.h │ │ ├── KafkaScrollViewController.m │ │ ├── KafkaTableViewController.h │ │ └── KafkaTableViewController.m │ ├── Info.plist │ ├── KafkaAppDelegate.h │ ├── KafkaAppDelegate.m │ ├── KafkaExample.entitlements │ ├── en.lproj │ │ └── InfoPlist.strings │ ├── main.m │ └── zh-Hans.lproj │ │ └── InfoPlist.strings ├── KafkaExampleTests │ ├── Info.plist │ └── KafkaExampleTests.m └── KafkaExampleUITests │ ├── Info.plist │ └── KafkaExampleUITests.m ├── KafkaRefresh.podspec ├── KafkaRefresh ├── Category │ ├── KafkaCategories.h │ └── KafkaCategories.m ├── Configuration │ ├── UIScrollView+KafkaConfiguration.h │ └── UIScrollView+KafkaConfiguration.m ├── Core │ ├── KafkaFootRefreshControl.h │ ├── KafkaFootRefreshControl.m │ ├── KafkaHeadRefreshControl.h │ ├── KafkaHeadRefreshControl.m │ ├── KafkaRefreshControl.h │ ├── KafkaRefreshControl.m │ ├── KafkaRefreshProtocol.h │ ├── UIScrollView+KafkaRefreshControl.h │ └── UIScrollView+KafkaRefreshControl.m ├── Default │ ├── KafkaRefreshDefaults.h │ └── KafkaRefreshDefaults.m ├── KafkaRefresh.h ├── Resource │ └── Image.bundle │ │ └── arrow48.png ├── Style │ └── KafkaRefreshStyle.h └── UIKit │ ├── FootKit │ ├── KafkaArrowFooter.h │ ├── KafkaArrowFooter.m │ ├── KafkaNativeFooter.h │ ├── KafkaNativeFooter.m │ ├── KafkaReplicatorFooter.h │ ├── KafkaReplicatorFooter.m │ ├── KafkaRingIndicatorFooter.h │ └── KafkaRingIndicatorFooter.m │ ├── HeadKit │ ├── KafkaArrowHeader.h │ ├── KafkaArrowHeader.m │ ├── KafkaNativeHeader.h │ ├── KafkaNativeHeader.m │ ├── KafkaReplicatorHeader.h │ ├── KafkaReplicatorHeader.m │ ├── KafkaRingIndicatorHeader.h │ └── KafkaRingIndicatorHeader.m │ └── LayerKit │ ├── KafkaAnimatableProtocol.h │ ├── KafkaArcLayer.h │ ├── KafkaArcLayer.m │ ├── KafkaReplicatorLayer.h │ └── KafkaReplicatorLayer.m ├── LICENSE ├── LICENSE.txt ├── README.md └── _config.yml /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | !!! 提交bug前: 2 | 3 | - 请定位该bug是否由自身代码引发,当该bug模糊不清时,您应该继续思索; 4 | - 如果您发现该bug由KafkaRefresh引发,且您能修改源码解决,欢迎您提交解决方案或提交一个request,在此先表示感谢。 5 | 6 | 提交bug必须包含下列要点: 7 | 8 | - iOS版本; 9 | - KafkaRefresh版本(可查看cocoapods中提示的版本信息); 10 | - 请您务必给出详细的重现步骤。 11 | 12 | 13 | # ☕️ [赞助](https://github.com/OpenFeyn/KafkaRefresh/blob/master/sponsor.jpg) 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | # CocoaPods 32 | # 33 | # We recommend against adding the Pods directory to your .gitignore. However 34 | # you should judge for yourself, the pros and cons are mentioned at: 35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 36 | # 37 | # Pods/ 38 | 39 | # Carthage 40 | # 41 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 42 | # Carthage/Checkouts 43 | 44 | Carthage/Build 45 | 46 | # fastlane 47 | # 48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 49 | # screenshots whenever they are needed. 50 | # For more information about the recommended setup visit: 51 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 52 | 53 | fastlane/report.xml 54 | fastlane/Preview.html 55 | fastlane/screenshots 56 | fastlane/test_output 57 | 58 | # Code Injection 59 | # 60 | # After new code Injection tools there's a generated folder /iOSInjectionProject 61 | # https://github.com/johnno1962/injectionforxcode 62 | 63 | iOSInjectionProject/ 64 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 2.3 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | osx_image: xcode9 2 | language: objective-c 3 | xcode_project: Demo/KafkaExample.xcodeproj 4 | xcode_scheme: KafkaExample 5 | 6 | 7 | before_install: 8 | - env 9 | - xcodebuild -version 10 | - xcodebuild -showsdks 11 | - xcpretty --version 12 | 13 | script: 14 | - xcodebuild clean build -project "$TRAVIS_XCODE_PROJECT" -scheme "$TRAVIS_XCODE_SCHEME" | xcpretty 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Assets/Gif/_allen.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Assets/Gif/_allen.gif -------------------------------------------------------------------------------- /Assets/Gif/_arc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Assets/Gif/_arc.gif -------------------------------------------------------------------------------- /Assets/Gif/_arrow.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Assets/Gif/_arrow.gif -------------------------------------------------------------------------------- /Assets/Gif/_circle.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Assets/Gif/_circle.gif -------------------------------------------------------------------------------- /Assets/Gif/_dot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Assets/Gif/_dot.gif -------------------------------------------------------------------------------- /Assets/Gif/_native.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Assets/Gif/_native.gif -------------------------------------------------------------------------------- /Assets/Gif/_ring.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Assets/Gif/_ring.gif -------------------------------------------------------------------------------- /Assets/Gif/_triangle.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Assets/Gif/_triangle.gif -------------------------------------------------------------------------------- /Assets/Gif/_woody.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Assets/Gif/_woody.gif -------------------------------------------------------------------------------- /Assets/Gif/allen.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Assets/Gif/allen.gif -------------------------------------------------------------------------------- /Assets/Gif/arc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Assets/Gif/arc.gif -------------------------------------------------------------------------------- /Assets/Gif/arrow.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Assets/Gif/arrow.gif -------------------------------------------------------------------------------- /Assets/Gif/circle.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Assets/Gif/circle.gif -------------------------------------------------------------------------------- /Assets/Gif/dot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Assets/Gif/dot.gif -------------------------------------------------------------------------------- /Assets/Gif/native.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Assets/Gif/native.gif -------------------------------------------------------------------------------- /Assets/Gif/ring.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Assets/Gif/ring.gif -------------------------------------------------------------------------------- /Assets/Gif/triangle.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Assets/Gif/triangle.gif -------------------------------------------------------------------------------- /Assets/Gif/woody.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Assets/Gif/woody.gif -------------------------------------------------------------------------------- /Assets/titleView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Assets/titleView.png -------------------------------------------------------------------------------- /CREADME.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 | 6 |

KafkaRefresh

7 |

8 | 内置多种动画、可自定义和灵活的iOS下拉刷新框架。 9 |
10 |
11 | Bug提交 12 | · 13 | 需求提交 14 |

15 |

16 |
17 | 18 | ### Status 19 | 20 | [![GitHub license](https://img.shields.io/github/license/xorshine/KafkaRefresh.svg)](https://github.com/xorshine/KafkaRefresh/blob/master/LICENSE) 21 | [![CocoaPods Compatible](https://img.shields.io/cocoapods/v/KafkaRefresh.svg)](https://img.shields.io/cocoapods/v/KafkaRefresh.svg) 22 | ![platform](https://img.shields.io/badge/platform-ios-lightgrey.svg) 23 | ![language](https://img.shields.io/badge/language-objc-orange.svg) 24 | [![Email](https://img.shields.io/badge/e--mail-xorshine%40icloud.com-blue.svg)](mailto:xorshine@icloud.com) 25 | 26 | ### Screenshots 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 |
KafkaRefreshStyleTop ScreenshotsBottom Screenshots
Native
ReplicatorWoody
ReplicatorAllen
ReplicatorCircle
ReplicatorDot
ReplicatorArc
ReplicatorTriangle
AnimatableRing
AnimatableArrow
79 | 80 | 81 | ### 关于新版2.0 82 | 83 | * 开源KafkaRefresh纯属自娱自乐。那时较闲。KafkaRefresh有很大不足,如:更细粒度的视觉动画、更规范的API命名、支持Swift、支持更多的自定义...等等。无奈的是,手头事情之多,使之暂无闲心更新2.0。怕是要很久之后吧。 84 | 85 | * 有人问为何要以Kafka命名,Apache基金会开源过一款分布式消息系统也叫做Kafka,KafkaRefresh跟Apache Kafka没任何关系。当时不知道,即使知道,还是会叫KafkaRefresh。其实是个人喜欢的一个作家叫Kafka,KafkaRefresh的内置样式也用了一些我喜欢的人的名字命名,这没什么意义,或许是为了当时的方便罢了。 86 | 87 | ### 不支持嵌套的ScrollView 88 | 89 | ### 特点 90 | * 支持多样式选择与自定义 91 | 92 | > 内置9种动画样式 93 | 94 | * 非刷新状态自动隐藏 95 | 96 | > 即使手动调整过contentInset,依然能够在非刷新状态自动隐藏影。最常见的情况是:当数据量过少,UITableView停止刷新后,用户依旧能看到刷新控件的存在,从而影响的视觉体验。KafkaRefresh首次解决了该问题。 97 | 98 | * 刷新结束时抗抖动 99 | 100 | > 当UIScrollView处于刷新状态,且用户滑动UIScrollView,当刷新结束时,KafkaRefresh不会调整UIScrollView的内容,从而导致页面跳动; 101 | 102 | * 支持设置控件高度 103 | 104 | > `stretchOffsetYAxisThreshold`是根据刷新控件的高度进行的比例调整。如:当设置`stretchOffsetYAxisThreshold`为1.5时,触发刷新的偏移距离将调整为原来的1.5倍。 105 | 106 | * 支持全局配置 107 | 108 | > `KafkaRefreshDefaults`类似一个配置表,通过该配置表配置全局的刷新样式,而无需在每一个页面初始化或者绑定刷新控件。 109 | 110 | * 支持进度回调 111 | 112 | > 实时回调拖拽的偏移比例,对于扩展接口,可根据进度调整动画。该接口的开放可用于扩展更多的刷新东亚样式。 113 | 114 | * 自适应contentInset系统调整与手动调整 115 | 116 | > 自适应iOS7以后UINavigationController自动调整scrollview contentOffset,KafkaRefresh也对iOS 11进行了适配;当您手动设置了contentInset的值,也无需担心KafkaRefresh会影响到视觉效果。 117 | 118 | * 解决刷新状态分组视图悬停问题 119 | 120 | > 即使在列表滑动时,分组视图都将跟随ScrollView滑动(即使处于高速滑动状态下!)。 121 | 122 | * 支持预加载 123 | 124 | 当用户滑动scrollview接近至底部时,将会自动触发刷新,无需用户再滑至底部后拉起scrollview。该功能默认不开启,因为多数人不查看文档便欣然使用改功能,不正确使用极容易引发刷新无法停止。 125 | 126 | 使用预加载功能,请严格按照下面要求使用: 127 | 128 | * ```self.tableView.footRefreshControl.autoRefreshOnFoot = YES;``` 请手动将刷新该属性至为TRUE; 129 | 130 | * 在刷新调用的block块中,严格按下列逻辑编写: 131 | 132 | ```objective-c 133 | if (没有数据需要拼接了) { 134 | [weakSelf.tableView.footRefreshControl endRefreshingAndNoLongerRefreshingWithAlertText:@"no more"]; 135 | } else { 136 | [weakSelf.tableView.footRefreshControl endRefreshingWithAlertText:@"Did load successfully" completion:nil]; 137 | } 138 | ``` 139 | 140 | * 文档覆盖率100%、支持横竖屏切换自适应、iOS 7+。 141 | 142 | 143 | ### 安装 144 | * CocoaPods 145 | ```ruby 146 | pod 'KafkaRefresh' 147 | ``` 148 | 149 | * Carthage 150 | 151 | > 如果您想通过*carthage*安装 , 请您申请一个pull request. 152 | 153 | ### 使用 154 | 155 | ##### 1.引入头文件 156 | ```objective-c 157 | #import "KafkaRefresh.h" 158 | ``` 159 | 160 | ##### 2.初始化控件 161 | * 方式一 162 | ```objective-c 163 | #pragma mark - head 164 | 165 | [self.tableView bindHeadRefreshHandler:^{ 166 | 167 | } themeColor:MainColor refreshStyle:KafkaRefreshStyleAnimatableArrow]; 168 | 169 | #pragma mark - foot 170 | 171 | [self.tableView bindFootRefreshHandler:^{ 172 | 173 | } themeColor:MainColor refreshStyle:KafkaRefreshStyleAnimatableArrow]; 174 | 175 | #pragma mark - auto refresh 176 | 177 | self.tableView.footRefreshControl.autoRefreshOnFoot = YES; 178 | ``` 179 | * 方式二 180 | ```objective-c 181 | KafkaArrowHeader * arrow = [[KafkaArrowHeader alloc] init]; 182 | arrow.refreshHandler = ^{ 183 | //..... 184 | }; 185 | self.tableView.headRefreshControl = arrow; 186 | ``` 187 | 188 | * 方式三 全局配置 189 | ```objective-c 190 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 191 | { 192 | [[KafkaRefreshDefaults standardRefreshDefaults] setHeaderDefaultStyle:KafkaRefreshStyleAnimatableRing]; 193 | return YES; 194 | } 195 | 196 | #pragma mark - global 197 | 198 | [self.tableView bindGlobalStyleForFootRefreshHandler:^{ 199 | 200 | }]; 201 | 202 | ``` 203 | ##### 3.手动触发刷新 204 | ```objective-c 205 | [self.tableView.headRefreshControl beginRefreshing]; 206 | [self.tableView.footRefreshControl beginRefreshing]; 207 | ``` 208 | 209 | ##### 4.结束刷新 210 | ```objective-c 211 | 212 | /* 213 | 一般方式结束刷新 214 | */ 215 | - (void)endRefreshing; 216 | 217 | /* 218 | 结束刷新且需要提示文字 219 | */ 220 | - (void)endRefreshingWithAlertText:(NSString *)text completion:(dispatch_block_t)completion; 221 | 222 | /* 223 | 结束刷新且不再需要刷新功能 224 | */ 225 | - (void)endRefreshingAndNoLongerRefreshingWithAlertText:(NSString *)text; 226 | ``` 227 | 228 | ##### 5.重新恢复刷新 229 | 230 | ```objective-c 231 | /** 232 | 当调用过 ‘endRefreshingAndNoLongerRefreshingWithAlertText’, 233 | 且重新需要恢复刷新功能室,调用下面方法 234 | */ 235 | - (void)resumeRefreshAvailable; 236 | ``` 237 | 238 | ### 自定义 239 | 240 | 以KafkaheadRefreshControl为例: 241 | ```objective-c 242 | #import "KafkaheadRefreshControl.h" 243 | @interface CustomHeader : KafkafootRefreshControl 244 | @end 245 | ``` 246 | ```objective-c 247 | @implementation CustomHeader 248 | 249 | - (void)setupProperties 250 | { 251 | [super setupProperties]; 252 | //初始化属性 253 | } 254 | 255 | - (void)kafkaDidScrollWithProgress:(CGFloat)progress max:(const CGFloat)max 256 | { 257 | //进度回调 258 | } 259 | 260 | - (void)kafkaRefreshStateDidChange:(KafkaRefreshState)state 261 | { 262 | [super kafkaRefreshStateDidChange:state]; 263 | } 264 | @end 265 | ``` 266 | 267 | ### FAQ 268 | 269 | * 遇到很多人咨询这样一个问题:上拉加载数据时,调用 `insertRowsAtIndexPaths: withAnimation:`拼接数据,发现tableView刷新过程发生抖动,位置发生偏移。首先申明!这不是KafkaRefresh的原因。如果想要解决这个问题,请设置`tableView.estimatedRowHeight = UITableViewAutomaticDimension;`. 270 | * 关于Bug的提交,请您一定要记住!应该提交基本信息!当前版本号,重现步骤(请不要过于相信您的中文能力,您写一句话我未必能看懂。) 271 | 272 | ### 交流 273 | 274 | > 1. 如需要帮助,请邮件 275 | > 2. 个人精力有限,KafkaRefresh开放的接口足够去扩展更丰富的UI效果,欢迎您参与,并提交pull request 276 | 277 | ### 协议 278 | > KafkaRefresh采用MIT开源协议。 279 | -------------------------------------------------------------------------------- /Demo/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/.DS_Store -------------------------------------------------------------------------------- /Demo/KafkaExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/KafkaExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Demo/KafkaExample.xcodeproj/xcshareddata/xcschemes/KafkaExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 62 | 68 | 69 | 70 | 72 | 78 | 79 | 80 | 81 | 82 | 88 | 89 | 90 | 91 | 92 | 93 | 104 | 106 | 112 | 113 | 114 | 115 | 116 | 117 | 123 | 125 | 131 | 132 | 133 | 134 | 136 | 137 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "logoiPhoneNotification_20pt@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "logoiPhoneNotification_20pt@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "logoiPhoneSpootlight5_29pt@2x.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "logoiPhoneSpootlight5_29pt@3x.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "40x40", 29 | "idiom" : "iphone", 30 | "filename" : "logoiPhoneSpootlight7_40pt@2x.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "logoiPhoneSpootlight7_40pt@3x.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "60x60", 41 | "idiom" : "iphone", 42 | "filename" : "logoiPhoneApp_60pt@2x.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "logoiPhoneApp_60pt@3x.png", 49 | "scale" : "3x" 50 | }, 51 | { 52 | "size" : "20x20", 53 | "idiom" : "ipad", 54 | "filename" : "logoiPadNotifications_20pt.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "logoiPadNotifications_20pt@2x.png", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "size" : "29x29", 65 | "idiom" : "ipad", 66 | "filename" : "logoiPadSpootlight5_29pt.png", 67 | "scale" : "1x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "logoiPadSpootlight5_29pt@2x.png", 73 | "scale" : "2x" 74 | }, 75 | { 76 | "size" : "40x40", 77 | "idiom" : "ipad", 78 | "filename" : "logoiPadSpootlight7_40pt.png", 79 | "scale" : "1x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "logoiPadSpootlight7_40pt@2x.png", 85 | "scale" : "2x" 86 | }, 87 | { 88 | "size" : "76x76", 89 | "idiom" : "ipad", 90 | "filename" : "logoiPadApp_76pt.png", 91 | "scale" : "1x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "logoiPadApp_76pt@2x.png", 97 | "scale" : "2x" 98 | }, 99 | { 100 | "size" : "83.5x83.5", 101 | "idiom" : "ipad", 102 | "filename" : "logoiPadProApp_83.5pt@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "1024x1024", 107 | "idiom" : "ios-marketing", 108 | "filename" : "logostore_1024pt.png", 109 | "scale" : "1x" 110 | } 111 | ], 112 | "info" : { 113 | "version" : 1, 114 | "author" : "xcode" 115 | } 116 | } -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPadApp_76pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPadApp_76pt.png -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPadApp_76pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPadApp_76pt@2x.png -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPadNotifications_20pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPadNotifications_20pt.png -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPadNotifications_20pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPadNotifications_20pt@2x.png -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPadProApp_83.5pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPadProApp_83.5pt@2x.png -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPadSpootlight5_29pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPadSpootlight5_29pt.png -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPadSpootlight5_29pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPadSpootlight5_29pt@2x.png -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPadSpootlight7_40pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPadSpootlight7_40pt.png -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPadSpootlight7_40pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPadSpootlight7_40pt@2x.png -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPhoneApp_60pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPhoneApp_60pt@2x.png -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPhoneApp_60pt@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPhoneApp_60pt@3x.png -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPhoneNotification_20pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPhoneNotification_20pt@2x.png -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPhoneNotification_20pt@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPhoneNotification_20pt@3x.png -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPhoneSpootlight5_29pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPhoneSpootlight5_29pt@2x.png -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPhoneSpootlight5_29pt@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPhoneSpootlight5_29pt@3x.png -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPhoneSpootlight7_40pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPhoneSpootlight7_40pt@2x.png -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPhoneSpootlight7_40pt@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logoiPhoneSpootlight7_40pt@3x.png -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logostore_1024pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/KafkaExample/Assets.xcassets/AppIcon.appiconset/logostore_1024pt.png -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "extent" : "full-screen", 5 | "idiom" : "iphone", 6 | "subtype" : "2436h", 7 | "filename" : "launchiPhoneXPortraitiOS11_375x812pt@3x.png", 8 | "minimum-system-version" : "11.0", 9 | "orientation" : "portrait", 10 | "scale" : "3x" 11 | }, 12 | { 13 | "extent" : "full-screen", 14 | "idiom" : "iphone", 15 | "subtype" : "736h", 16 | "filename" : "launchiPhonePortraitiOS89_414x736pt@3x.png", 17 | "minimum-system-version" : "8.0", 18 | "orientation" : "portrait", 19 | "scale" : "3x" 20 | }, 21 | { 22 | "extent" : "full-screen", 23 | "idiom" : "iphone", 24 | "subtype" : "667h", 25 | "filename" : "launchiPhonePortraitiOS89_375x667pt@2x.png", 26 | "minimum-system-version" : "8.0", 27 | "orientation" : "portrait", 28 | "scale" : "2x" 29 | }, 30 | { 31 | "orientation" : "portrait", 32 | "idiom" : "iphone", 33 | "filename" : "launchiPhonePortraitiOS789_320x480pt@2x.png", 34 | "extent" : "full-screen", 35 | "minimum-system-version" : "7.0", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "extent" : "full-screen", 40 | "idiom" : "iphone", 41 | "subtype" : "retina4", 42 | "filename" : "launchiPhonePortraitiOS789_320x568pt@2x.png", 43 | "minimum-system-version" : "7.0", 44 | "orientation" : "portrait", 45 | "scale" : "2x" 46 | }, 47 | { 48 | "orientation" : "portrait", 49 | "idiom" : "ipad", 50 | "filename" : "launchiPadPortraitiOS789_768x1024pt.png", 51 | "extent" : "full-screen", 52 | "minimum-system-version" : "7.0", 53 | "scale" : "1x" 54 | }, 55 | { 56 | "orientation" : "portrait", 57 | "idiom" : "ipad", 58 | "filename" : "launchiPadPortraitiOS789_768x1024pt@2x.png", 59 | "extent" : "full-screen", 60 | "minimum-system-version" : "7.0", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "orientation" : "portrait", 65 | "idiom" : "iphone", 66 | "filename" : "launchiPhonePortraitiOS56_320x480pt.png", 67 | "extent" : "full-screen", 68 | "scale" : "1x" 69 | }, 70 | { 71 | "orientation" : "portrait", 72 | "idiom" : "iphone", 73 | "filename" : "launchiPhonePortraitiOS56_320x480pt@2x.png", 74 | "extent" : "full-screen", 75 | "scale" : "2x" 76 | }, 77 | { 78 | "orientation" : "portrait", 79 | "idiom" : "iphone", 80 | "filename" : "launchiPhonePortraitiOS56_320x568pt@2x.png", 81 | "extent" : "full-screen", 82 | "subtype" : "retina4", 83 | "scale" : "2x" 84 | }, 85 | { 86 | "orientation" : "portrait", 87 | "idiom" : "ipad", 88 | "filename" : "launchiPadPortraitWOSBiOS56_768x1004pt.png", 89 | "extent" : "to-status-bar", 90 | "scale" : "1x" 91 | }, 92 | { 93 | "orientation" : "portrait", 94 | "idiom" : "ipad", 95 | "filename" : "launchiPadPortraitiOS56_768x1024pt.png", 96 | "extent" : "full-screen", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "orientation" : "portrait", 101 | "idiom" : "ipad", 102 | "filename" : "launchiPadPortraitWOSBiOS56_768x1004pt@2x.png", 103 | "extent" : "to-status-bar", 104 | "scale" : "2x" 105 | }, 106 | { 107 | "orientation" : "portrait", 108 | "idiom" : "ipad", 109 | "filename" : "launchiPadPortraitiOS56_768x1024pt@2x.png", 110 | "extent" : "full-screen", 111 | "scale" : "2x" 112 | } 113 | ], 114 | "info" : { 115 | "version" : 1, 116 | "author" : "xcode" 117 | } 118 | } -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/LaunchImage.launchimage/launchiPadPortraitWOSBiOS56_768x1004pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/KafkaExample/Assets.xcassets/LaunchImage.launchimage/launchiPadPortraitWOSBiOS56_768x1004pt.png -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/LaunchImage.launchimage/launchiPadPortraitWOSBiOS56_768x1004pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/KafkaExample/Assets.xcassets/LaunchImage.launchimage/launchiPadPortraitWOSBiOS56_768x1004pt@2x.png -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/LaunchImage.launchimage/launchiPadPortraitiOS56_768x1024pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/KafkaExample/Assets.xcassets/LaunchImage.launchimage/launchiPadPortraitiOS56_768x1024pt.png -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/LaunchImage.launchimage/launchiPadPortraitiOS56_768x1024pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/KafkaExample/Assets.xcassets/LaunchImage.launchimage/launchiPadPortraitiOS56_768x1024pt@2x.png -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/LaunchImage.launchimage/launchiPadPortraitiOS789_768x1024pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/KafkaExample/Assets.xcassets/LaunchImage.launchimage/launchiPadPortraitiOS789_768x1024pt.png -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/LaunchImage.launchimage/launchiPadPortraitiOS789_768x1024pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/KafkaExample/Assets.xcassets/LaunchImage.launchimage/launchiPadPortraitiOS789_768x1024pt@2x.png -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/LaunchImage.launchimage/launchiPhonePortraitiOS56_320x480pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/KafkaExample/Assets.xcassets/LaunchImage.launchimage/launchiPhonePortraitiOS56_320x480pt.png -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/LaunchImage.launchimage/launchiPhonePortraitiOS56_320x480pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/KafkaExample/Assets.xcassets/LaunchImage.launchimage/launchiPhonePortraitiOS56_320x480pt@2x.png -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/LaunchImage.launchimage/launchiPhonePortraitiOS56_320x568pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/KafkaExample/Assets.xcassets/LaunchImage.launchimage/launchiPhonePortraitiOS56_320x568pt@2x.png -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/LaunchImage.launchimage/launchiPhonePortraitiOS789_320x480pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/KafkaExample/Assets.xcassets/LaunchImage.launchimage/launchiPhonePortraitiOS789_320x480pt@2x.png -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/LaunchImage.launchimage/launchiPhonePortraitiOS789_320x568pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/KafkaExample/Assets.xcassets/LaunchImage.launchimage/launchiPhonePortraitiOS789_320x568pt@2x.png -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/LaunchImage.launchimage/launchiPhonePortraitiOS89_375x667pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/KafkaExample/Assets.xcassets/LaunchImage.launchimage/launchiPhonePortraitiOS89_375x667pt@2x.png -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/LaunchImage.launchimage/launchiPhonePortraitiOS89_414x736pt@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/KafkaExample/Assets.xcassets/LaunchImage.launchimage/launchiPhonePortraitiOS89_414x736pt@3x.png -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/LaunchImage.launchimage/launchiPhoneXPortraitiOS11_375x812pt@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/KafkaExample/Assets.xcassets/LaunchImage.launchimage/launchiPhoneXPortraitiOS11_375x812pt@3x.png -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Demo/KafkaExample/Assets.xcassets/icon.imageset/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/Demo/KafkaExample/Assets.xcassets/icon.imageset/icon.png -------------------------------------------------------------------------------- /Demo/KafkaExample/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 | -------------------------------------------------------------------------------- /Demo/KafkaExample/Controllers/KafkaCollectionViewController.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * e-mail:xorshine@icloud.com 6 | * github:https://github.com/xorshine 7 | * 8 | * This source code is licensed under the MIT license. 9 | */ 10 | 11 | #import 12 | #import "KafkaRefresh.h" 13 | 14 | @interface KafkaCollectionViewController : UICollectionViewController 15 | 16 | - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout 17 | refreshStyle:(KafkaRefreshStyle)style; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Demo/KafkaExample/Controllers/KafkaCollectionViewController.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * e-mail:xorshine@icloud.com 6 | * github:https://github.com/xorshine 7 | * 8 | * This source code is licensed under the MIT license. 9 | */ 10 | 11 | #import "KafkaCollectionViewController.h" 12 | 13 | #define MainColor [UIColor colorWithRed:28./255. green:164./255. blue:252/255. alpha:1.] 14 | 15 | @interface KafkaCollectionViewController () 16 | @property (assign, nonatomic) KafkaRefreshStyle style; 17 | @end 18 | 19 | @implementation KafkaCollectionViewController 20 | 21 | static NSString * const reuseIdentifier = @"Cell"; 22 | 23 | - (void)dealloc 24 | { 25 | NSLog(@"KafkaCollectionViewController dealloc"); 26 | } 27 | 28 | - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout 29 | refreshStyle:(KafkaRefreshStyle)style{ 30 | self = [super initWithCollectionViewLayout:layout]; 31 | if (self) { 32 | _style = style; 33 | } 34 | return self; 35 | } 36 | 37 | - (void)viewDidLoad { 38 | [super viewDidLoad]; 39 | self.navigationItem.title = @"UICollectionView"; 40 | 41 | self.collectionView.backgroundColor = [UIColor whiteColor]; 42 | [self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:reuseIdentifier]; 43 | 44 | __weak typeof(self) weakSelf = self; 45 | 46 | __block NSInteger count = 3; 47 | [self.collectionView bindHeadRefreshHandler:^{ 48 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 49 | if (count > 0) { 50 | [weakSelf.collectionView.headRefreshControl endRefreshingWithAlertText:@"Did load successfully" completion:nil]; 51 | count--; 52 | }else{ 53 | [weakSelf.collectionView.headRefreshControl endRefreshingWithAlertText:@"使用中文测试" completion:nil]; 54 | } 55 | }); 56 | } themeColor:MainColor refreshStyle:_style]; 57 | 58 | [self.collectionView bindFootRefreshHandler:^{ 59 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 60 | [weakSelf.collectionView.footRefreshControl endRefreshingAndNoLongerRefreshingWithAlertText:@"no more"]; 61 | }); 62 | } themeColor:MainColor refreshStyle:_style]; 63 | 64 | self.collectionView.headRefreshControl.backgroundColor = [UIColor grayColor]; 65 | } 66 | 67 | #pragma mark 68 | 69 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { 70 | return 2; 71 | } 72 | 73 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 74 | return 15; 75 | } 76 | 77 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 78 | UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath]; 79 | NSInteger items = [collectionView numberOfItemsInSection:indexPath.section]; 80 | cell.contentView.backgroundColor = [MainColor colorWithAlphaComponent:(items - indexPath.row *0.5)/items]; 81 | 82 | return cell; 83 | } 84 | 85 | @end 86 | -------------------------------------------------------------------------------- /Demo/KafkaExample/Controllers/KafkaCustomTableViewController.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * e-mail:xorshine@icloud.com 6 | * github:https://github.com/xorshine 7 | * 8 | * This source code is licensed under the MIT license. 9 | */ 10 | 11 | #import 12 | #import "KafkaRefresh.h" 13 | 14 | @interface KafkaCustomTableViewController : UITableViewController 15 | - (instancetype)initWithRefreshStyle:(KafkaRefreshStyle)style; 16 | @end 17 | -------------------------------------------------------------------------------- /Demo/KafkaExample/Controllers/KafkaCustomTableViewController.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * e-mail:xorshine@icloud.com 6 | * github:https://github.com/xorshine 7 | * 8 | * This source code is licensed under the MIT license. 9 | */ 10 | 11 | #import "KafkaCustomTableViewController.h" 12 | 13 | #define MainColor [UIColor colorWithRed:28./255. green:164./255. blue:252/255. alpha:1.] 14 | 15 | @interface KafkaCustomTableViewController () 16 | @property (assign, nonatomic) KafkaRefreshStyle style; 17 | @end 18 | 19 | @implementation KafkaCustomTableViewController 20 | 21 | - (void)dealloc 22 | { 23 | NSLog(@"KafkaCustomTableViewController dealloc"); 24 | } 25 | 26 | - (instancetype)initWithRefreshStyle:(KafkaRefreshStyle)style{ 27 | self = [super init]; 28 | if (self) { 29 | _style = style; 30 | } 31 | return self; 32 | } 33 | 34 | - (void)viewDidLoad { 35 | [super viewDidLoad]; 36 | 37 | self.navigationItem.title = @"Custom"; 38 | self.tableView.rowHeight = 60.; 39 | self.tableView.sectionHeaderHeight = 35.; 40 | self.tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine; 41 | self.tableView.tableFooterView = [UIView new]; 42 | self.tableView.backgroundColor = [UIColor whiteColor]; 43 | 44 | __weak typeof(self) weakSelf = self; 45 | [self.tableView bindRefreshStyle:_style fillColor:[UIColor whiteColor] atPosition:0 refreshHanler:^{ 46 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 47 | [weakSelf.tableView.headRefreshControl endRefreshing]; 48 | }); 49 | }]; 50 | self.tableView.headRefreshControl.backgroundColor = [UIColor grayColor]; 51 | 52 | [self.tableView.headRefreshControl beginRefreshing]; 53 | 54 | [self.tableView bindRefreshStyle:_style fillColor:[UIColor whiteColor] atPosition:1 refreshHanler:^{ 55 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 56 | [weakSelf.tableView.footRefreshControl endRefreshing]; 57 | }); 58 | }]; 59 | self.tableView.footRefreshControl.backgroundColor = [UIColor grayColor]; 60 | } 61 | 62 | #pragma mark - Table view data source 63 | 64 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 65 | return 2; 66 | } 67 | 68 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 69 | return 10; 70 | } 71 | 72 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 73 | static NSString * const reuseId = @"id"; 74 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseId]; 75 | if (!cell) { 76 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseId]; 77 | } 78 | return cell; 79 | } 80 | 81 | - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{ 82 | UILabel * label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 35)]; 83 | label.backgroundColor = MainColor; 84 | label.textAlignment = NSTextAlignmentCenter; 85 | label.textColor = [UIColor whiteColor]; 86 | label.text = @"—————————————————————"; 87 | return label; 88 | } 89 | 90 | - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{ 91 | cell.separatorInset = UIEdgeInsetsMake(0, 0, 0, 0); 92 | cell.contentView.backgroundColor = [MainColor colorWithAlphaComponent:0.7]; 93 | cell.selectionStyle = UITableViewCellSelectionStyleNone; 94 | } 95 | @end 96 | -------------------------------------------------------------------------------- /Demo/KafkaExample/Controllers/KafkaMainListController.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * e-mail:xorshine@icloud.com 6 | * github:https://github.com/xorshine 7 | * 8 | * This source code is licensed under the MIT license. 9 | */ 10 | 11 | #import 12 | 13 | @interface KafkaMainListController : UITableViewController 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Demo/KafkaExample/Controllers/KafkaMainListController.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * e-mail:xorshine@icloud.com 6 | * github:https://github.com/xorshine 7 | * 8 | * This source code is licensed under the MIT license. 9 | */ 10 | 11 | #import "KafkaMainListController.h" 12 | #import "KafkaTableViewController.h" 13 | #import "KafkaCollectionViewController.h" 14 | #import "KafkaCustomTableViewController.h" 15 | #import "KafkaScrollViewController.h" 16 | #import "KafkaRefresh.h" 17 | 18 | 19 | #define MainColor [UIColor colorWithRed:28./255. green:164./255. blue:252/255. alpha:1.] 20 | 21 | @interface KafkaMainListController () 22 | 23 | @end 24 | 25 | @implementation KafkaMainListController 26 | 27 | - (void)dealloc{ 28 | NSLog(@"call Dealloc"); 29 | } 30 | 31 | - (void)viewDidLoad { 32 | [super viewDidLoad]; 33 | 34 | [self.navigationController.navigationBar setTitleTextAttributes:@{NSForegroundColorAttributeName:[UIColor blackColor]}]; 35 | 36 | self.navigationItem.title = @"Kafka"; 37 | self.tableView.rowHeight = 55.; 38 | self.tableView.sectionHeaderHeight = 35.; 39 | self.tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine; 40 | self.tableView.tableFooterView = [UIView new]; 41 | 42 | __weak typeof(self) weakSelf = self; 43 | 44 | [self.tableView bindDefaultRefreshStyleAtPosition:KafkaRefreshPositionHeader refreshHanler:^{ 45 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2. * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 46 | [weakSelf.tableView.headRefreshControl endRefreshing]; 47 | }); 48 | }]; 49 | [self.tableView bindDefaultRefreshStyleAtPosition:KafkaRefreshPositionFooter refreshHanler:^{ 50 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1. * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 51 | [weakSelf.tableView.footRefreshControl endRefreshingAndNoLongerRefreshingWithAlertText:NSLocalizedString(@"nothing", nil)]; 52 | }); 53 | }]; 54 | } 55 | 56 | #pragma mark - Table view data source 57 | 58 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 59 | return 4; 60 | } 61 | 62 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 63 | return [self textArray].count; 64 | } 65 | 66 | - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{ 67 | UILabel * label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 35)]; 68 | label.backgroundColor = [UIColor whiteColor]; 69 | label.textAlignment = NSTextAlignmentCenter; 70 | label.textColor = MainColor; 71 | if (section == 0) { 72 | label.text = @"UITableView"; 73 | } 74 | else if (section == 1){ 75 | label.text = @"UICollectionView"; 76 | } 77 | else if (section == 2){ 78 | label.text = @"Custom"; 79 | } else if (section == 3){ 80 | label.text = @"ScrollView"; 81 | } 82 | 83 | return label; 84 | } 85 | 86 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 87 | static NSString * const reuseId = @"id"; 88 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseId]; 89 | if (!cell) { 90 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseId]; 91 | } 92 | return cell; 93 | } 94 | 95 | - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{ 96 | cell.separatorInset = UIEdgeInsetsMake(0, 0, 0, 0); 97 | NSInteger rows = [tableView numberOfRowsInSection:indexPath.section]; 98 | cell.backgroundColor = [MainColor colorWithAlphaComponent:(rows - indexPath.row *0.5)/rows]; 99 | cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; 100 | cell.selectionStyle = UITableViewCellSelectionStyleNone; 101 | cell.detailTextLabel.textColor = [UIColor whiteColor]; 102 | cell.textLabel.textColor = [UIColor whiteColor]; 103 | NSInteger section = indexPath.section; 104 | if (section == 0) { 105 | cell.textLabel.text = @"UITableView"; 106 | } 107 | else if (section == 1){ 108 | cell.textLabel.text = @"UICollectionView"; 109 | } 110 | else if (section == 2){ 111 | cell.textLabel.text = @"Custom"; 112 | } 113 | else if (section == 3){ 114 | cell.textLabel.text = @"ScrollView"; 115 | } 116 | cell.detailTextLabel.text = [[self textArray] objectAtIndex:indexPath.row]; 117 | } 118 | 119 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ 120 | if (indexPath.section == 0) { 121 | KafkaTableViewController * tableVC = [[KafkaTableViewController alloc] initWithRefreshStyle:indexPath.row];; 122 | [self.navigationController pushViewController:tableVC animated:YES]; 123 | } 124 | else if (indexPath.section == 1){ 125 | KafkaCollectionViewController *collectVC = [[KafkaCollectionViewController alloc] initWithCollectionViewLayout:[self flow] refreshStyle:indexPath.row]; 126 | [self.navigationController pushViewController:collectVC animated:YES]; 127 | } 128 | else if (indexPath.section == 2){ 129 | KafkaCustomTableViewController *customVC = [[KafkaCustomTableViewController alloc] initWithRefreshStyle:indexPath.row]; 130 | [self.navigationController pushViewController:customVC animated:YES]; 131 | } 132 | else if (indexPath.section == 3){ 133 | KafkaScrollViewController *customVC = [[KafkaScrollViewController alloc] initWithRefreshStyle:indexPath.row]; 134 | [self.navigationController pushViewController:customVC animated:YES]; 135 | } 136 | } 137 | 138 | - (UICollectionViewFlowLayout *)flow{ 139 | UICollectionViewFlowLayout * flow = [[UICollectionViewFlowLayout alloc] init]; 140 | flow.minimumLineSpacing = 1.0; 141 | flow.minimumInteritemSpacing = 1.0; 142 | flow.itemSize = CGSizeMake((self.view.bounds.size.width - 3.)/4, (self.view.bounds.size.width - 3.)/4 * 1.3); 143 | // flow.headerReferenceSize = CGSizeMake(self.view.bounds.size.width, 5.); 144 | return flow; 145 | } 146 | 147 | - (NSArray *)textArray{ 148 | return @[ 149 | @"KafkaRefreshStyleNative", 150 | @"KafkaRefreshStyleReplicatorWoody", 151 | @"KafkaRefreshStyleReplicatorAllen", 152 | @"KafkaRefreshStyleReplicatorCircle", 153 | @"KafkaRefreshStyleReplicatorDot", 154 | @"KafkaRefreshStyleReplicatorArc", 155 | @"KafkaRefreshStyleReplicatorTriangle", 156 | @"KafkaRefreshStyleAnimatableRing", 157 | @"KafkaRefreshStyleAnimatableArrow" 158 | ]; 159 | } 160 | 161 | @end 162 | -------------------------------------------------------------------------------- /Demo/KafkaExample/Controllers/KafkaScrollViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // KafkaScrollViewController.h 3 | // KafkaExample 4 | // 5 | // Created by 向小辉 on 2019/5/3. 6 | // Copyright © 2019 Kinx. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "KafkaRefresh.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface KafkaScrollViewController : UIViewController 15 | - (instancetype)initWithRefreshStyle:(KafkaRefreshStyle)style; 16 | @end 17 | 18 | NS_ASSUME_NONNULL_END 19 | -------------------------------------------------------------------------------- /Demo/KafkaExample/Controllers/KafkaScrollViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // KafkaScrollViewController.m 3 | // KafkaExample 4 | // 5 | // Created by 向小辉 on 2019/5/3. 6 | // Copyright © 2019 Kinx. All rights reserved. 7 | // 8 | 9 | #import "KafkaScrollViewController.h" 10 | 11 | 12 | #define MainColor [UIColor colorWithRed:28./255. green:164./255. blue:252/255. alpha:1.] 13 | #define MinorColor [UIColor colorWithRed:180./255. green:144./255. blue:202/255. alpha:1.] 14 | 15 | 16 | @interface KafkaScrollViewController () 17 | @property (assign, nonatomic) KafkaRefreshStyle style; 18 | @property(nonatomic, strong) UIScrollView *scrollView; 19 | @end 20 | 21 | @implementation KafkaScrollViewController 22 | 23 | - (instancetype)initWithRefreshStyle:(KafkaRefreshStyle)style{ 24 | self = [super init]; 25 | if (self) { 26 | _style = style; 27 | } 28 | return self; 29 | } 30 | 31 | 32 | - (void)viewDidLoad { 33 | [super viewDidLoad]; 34 | 35 | if (@available(iOS 11, *)) { 36 | self.scrollView.contentInsetAdjustmentBehavior = UIApplicationBackgroundFetchIntervalNever; 37 | } else { 38 | self.automaticallyAdjustsScrollViewInsets = false; 39 | } 40 | [self.navigationController.navigationBar setBackgroundImage:[UIImage new] forBarMetrics:(UIBarMetricsDefault)]; 41 | 42 | [self.view addSubview:self.scrollView]; 43 | self.scrollView.backgroundColor = [UIColor whiteColor]; 44 | self.scrollView.frame = CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height); 45 | self.scrollView.contentSize = CGSizeMake(self.view.bounds.size.width, self.view.bounds.size.height*1.5); 46 | UIView *contentView = [UIView new]; 47 | contentView.frame = CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height*1.5); 48 | contentView.backgroundColor = [UIColor blueColor]; 49 | [self.scrollView addSubview:contentView]; 50 | 51 | KafkaRefreshHandler headBlock = ^(void){ 52 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 53 | [self.scrollView.headRefreshControl endRefreshing]; 54 | }); 55 | }; 56 | 57 | [self.scrollView bindHeadRefreshHandler:headBlock themeColor:[UIColor redColor] refreshStyle:_style]; 58 | } 59 | 60 | - (UIScrollView *)scrollView { 61 | if (!_scrollView) { 62 | _scrollView = [UIScrollView new]; 63 | } 64 | return _scrollView; 65 | } 66 | 67 | @end 68 | -------------------------------------------------------------------------------- /Demo/KafkaExample/Controllers/KafkaTableViewController.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * e-mail:xorshine@icloud.com 6 | * github:https://github.com/xorshine 7 | * 8 | * This source code is licensed under the MIT license. 9 | */ 10 | 11 | #import 12 | #import "KafkaRefresh.h" 13 | 14 | @interface KafkaTableViewController : UITableViewController 15 | 16 | - (instancetype)initWithRefreshStyle:(KafkaRefreshStyle)style; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Demo/KafkaExample/Controllers/KafkaTableViewController.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * e-mail:xorshine@icloud.com 6 | * github:https://github.com/xorshine 7 | * 8 | * This source code is licensed under the MIT license. 9 | */ 10 | 11 | #import "KafkaTableViewController.h" 12 | 13 | #define MainColor [UIColor colorWithRed:28./255. green:164./255. blue:252/255. alpha:1.] 14 | #define MinorColor [UIColor colorWithRed:180./255. green:144./255. blue:202/255. alpha:1.] 15 | 16 | @interface KafkaTableViewController () 17 | @property (assign, nonatomic) KafkaRefreshStyle style; 18 | @property (strong, nonatomic) NSMutableArray * source; 19 | @end 20 | 21 | @implementation KafkaTableViewController 22 | 23 | - (void)dealloc 24 | { 25 | NSLog(@"KafkaTableViewController dealloc"); 26 | } 27 | 28 | - (instancetype)initWithRefreshStyle:(KafkaRefreshStyle)style{ 29 | self = [super init]; 30 | if (self) { 31 | _style = style; 32 | } 33 | return self; 34 | } 35 | 36 | - (void)viewDidLoad { 37 | [super viewDidLoad]; 38 | 39 | self.navigationItem.title = @"UITableView"; 40 | 41 | self.navigationController.navigationBar.translucent = YES; 42 | [self.navigationController.navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault]; 43 | 44 | self.tableView.estimatedRowHeight = 2.; 45 | self.tableView.sectionHeaderHeight = 35.; 46 | self.tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine; 47 | self.tableView.tableFooterView = [UIView new]; 48 | 49 | UIBarButtonItem * refresh = [[UIBarButtonItem alloc] initWithTitle:@"Refresh" style:UIBarButtonItemStylePlain target:self action:@selector(refresh)]; 50 | self.navigationItem.rightBarButtonItem = refresh; 51 | __block NSInteger count = 2; 52 | __weak typeof(self) weakSelf = self; 53 | 54 | KafkaRefreshHandler headBlock = ^(void){ 55 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 56 | if (count > 0) { 57 | for (NSInteger i = 0; i < 3; i++) { 58 | [weakSelf.source insertObject:@"" atIndex:0]; 59 | } 60 | [weakSelf.tableView.headRefreshControl endRefreshingWithAlertText:@"Did load successfully" completion:nil]; 61 | [weakSelf.tableView reloadData]; 62 | count--; 63 | }else{ 64 | [weakSelf.tableView.headRefreshControl endRefreshingWithAlertText:@"使用中文测试" completion:nil]; 65 | } 66 | }); 67 | }; 68 | 69 | [self.tableView bindHeadRefreshHandler:headBlock themeColor:[UIColor redColor] refreshStyle:_style]; 70 | 71 | KafkaRefreshHandler footBlock = ^(void){ 72 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 73 | [weakSelf.source addObjectsFromArray:@[@"",@"",@"",@"",@"",@""]]; 74 | 75 | [weakSelf.tableView reloadData]; 76 | [weakSelf.tableView.footRefreshControl endRefreshingWithAlertText:@"Did load successfully" completion:^{ 77 | [weakSelf.tableView reloadData]; 78 | }]; 79 | }); 80 | }; 81 | 82 | [self.tableView bindFootRefreshHandler:footBlock themeColor:MainColor refreshStyle:_style]; 83 | [self.tableView.footRefreshControl setAlertTextColor:[UIColor redColor]]; 84 | self.tableView.footRefreshControl.autoRefreshOnFoot = YES; 85 | } 86 | 87 | - (void)refresh{ 88 | [self.tableView.headRefreshControl beginRefreshing]; 89 | } 90 | 91 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{ 92 | return 2; 93 | } 94 | 95 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 96 | if (section == 0) { 97 | return self.source.count; 98 | } 99 | return 0; 100 | } 101 | 102 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ 103 | if (indexPath.row % 2 == 0) { 104 | return 40; 105 | } 106 | return 60; 107 | } 108 | 109 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 110 | static NSString * const reuseId = @"id"; 111 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseId]; 112 | if (!cell) { 113 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseId]; 114 | } 115 | return cell; 116 | } 117 | 118 | - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{ 119 | UILabel * label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 35)]; 120 | label.backgroundColor = MainColor; 121 | label.textAlignment = NSTextAlignmentCenter; 122 | label.textColor = [UIColor whiteColor]; 123 | return label; 124 | } 125 | 126 | - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{ 127 | cell.separatorInset = UIEdgeInsetsMake(0, 0, 0, 0); 128 | cell.contentView.backgroundColor = [MainColor colorWithAlphaComponent:0.9]; 129 | cell.selectionStyle = UITableViewCellSelectionStyleNone; 130 | cell.textLabel.backgroundColor = [UIColor clearColor]; 131 | cell.textLabel.textColor = [UIColor whiteColor]; 132 | cell.textLabel.text = [NSString stringWithFormat:@"section: %ld row: %ld",(long)indexPath.section,(long)indexPath.row]; 133 | } 134 | 135 | - (NSMutableArray *)source{ 136 | if (!_source) { 137 | _source = @[@"",@"",@"",@"",@"",@"",@""].mutableCopy; 138 | } 139 | return _source; 140 | } 141 | 142 | @end 143 | -------------------------------------------------------------------------------- /Demo/KafkaExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | KafkaRefresh 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Demo/KafkaExample/KafkaAppDelegate.h: -------------------------------------------------------------------------------- 1 | /************************************************************* 2 | * Copyright (c) 2016-present, K. * 3 | * All rights reserved. * 4 | * * 5 | * e-mail: xorshine@icloud.com * 6 | * github:https://github.com/xorshine * 7 | * * 8 | * This source code is licensed under the MIT license. * 9 | *************************************************************/ 10 | 11 | 12 | #import 13 | 14 | @interface KafkaAppDelegate : UIResponder 15 | 16 | @property (strong, nonatomic) UIWindow *window; 17 | 18 | 19 | @end 20 | 21 | -------------------------------------------------------------------------------- /Demo/KafkaExample/KafkaAppDelegate.m: -------------------------------------------------------------------------------- 1 | /************************************************************* 2 | * Copyright (c) 2016-present, K. * 3 | * All rights reserved. * 4 | * * 5 | * e-mail: xorshine@icloud.com * 6 | * github:https://github.com/xorshine * 7 | * * 8 | * This source code is licensed under the MIT license. * 9 | *************************************************************/ 10 | 11 | #import "KafkaAppDelegate.h" 12 | #import "KafkaRefresh.h" 13 | 14 | @interface KafkaAppDelegate () 15 | 16 | @end 17 | 18 | @implementation KafkaAppDelegate 19 | 20 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 21 | [[KafkaRefreshDefaults standardRefreshDefaults] setHeadDefaultStyle:KafkaRefreshStyleAnimatableRing]; 22 | return YES; 23 | } 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /Demo/KafkaExample/KafkaExample.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.external-accessory.wireless-configuration 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Demo/KafkaExample/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | 2 | "nothing" = "nothing"; 3 | 4 | "All data is loaded" = "All data is loaded"; 5 | 6 | "did load successfully" = "did load successfully"; 7 | -------------------------------------------------------------------------------- /Demo/KafkaExample/main.m: -------------------------------------------------------------------------------- 1 | 2 | 3 | #import 4 | #import "KafkaAppDelegate.h" 5 | 6 | int main(int argc, char * argv[]) { 7 | @autoreleasepool { 8 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([KafkaAppDelegate class])); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Demo/KafkaExample/zh-Hans.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | 2 | "nothing" = "没有了"; 3 | 4 | "All data is loaded" = "数据已经全部加载"; 5 | 6 | "did load successfully" = "加载成功"; 7 | -------------------------------------------------------------------------------- /Demo/KafkaExampleTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Demo/KafkaExampleTests/KafkaExampleTests.m: -------------------------------------------------------------------------------- 1 | 2 | #import 3 | 4 | @interface KafkaExampleTests : XCTestCase 5 | 6 | @end 7 | 8 | @implementation KafkaExampleTests 9 | 10 | - (void)setUp { 11 | [super setUp]; 12 | // Put setup code here. This method is called before the invocation of each test method in the class. 13 | } 14 | 15 | - (void)tearDown { 16 | // Put teardown code here. This method is called after the invocation of each test method in the class. 17 | [super tearDown]; 18 | } 19 | 20 | - (void)testExample { 21 | // This is an example of a functional test case. 22 | // Use XCTAssert and related functions to verify your tests produce the correct results. 23 | } 24 | 25 | - (void)testPerformanceExample { 26 | // This is an example of a performance test case. 27 | [self measureBlock:^{ 28 | // Put the code you want to measure the time of here. 29 | }]; 30 | } 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /Demo/KafkaExampleUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Demo/KafkaExampleUITests/KafkaExampleUITests.m: -------------------------------------------------------------------------------- 1 | 2 | #import 3 | 4 | @interface KafkaExampleUITests : XCTestCase 5 | 6 | @end 7 | 8 | @implementation KafkaExampleUITests 9 | 10 | - (void)setUp { 11 | [super setUp]; 12 | 13 | // Put setup code here. This method is called before the invocation of each test method in the class. 14 | 15 | // In UI tests it is usually best to stop immediately when a failure occurs. 16 | self.continueAfterFailure = NO; 17 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 18 | [[[XCUIApplication alloc] init] launch]; 19 | 20 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 21 | } 22 | 23 | - (void)tearDown { 24 | // Put teardown code here. This method is called after the invocation of each test method in the class. 25 | [super tearDown]; 26 | } 27 | 28 | - (void)testExample { 29 | // Use recording to get started writing UI tests. 30 | // Use XCTAssert and related functions to verify your tests produce the correct results. 31 | } 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /KafkaRefresh.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "KafkaRefresh" 3 | s.version = "1.7.0" 4 | s.summary = "Highly scalable, customize, multi-style refresh framework." 5 | 6 | s.homepage = "https://github.com/xorshine/KafkaRefresh" 7 | s.license = "MIT" 8 | s.author = { "k" => "xorshine@icloud.com" } 9 | 10 | s.platform = :ios, '9.0' 11 | s.requires_arc = true 12 | s.ios.deployment_target = '9.0' 13 | 14 | s.source = { :git => "https://github.com/xorshine/KafkaRefresh.git", :tag => s.version} 15 | 16 | s.source_files = "KafkaRefresh/KafkaRefresh.h" 17 | s.public_header_files = "KafkaRefresh/KafkaRefresh.h" 18 | s.resource = 'KafkaRefresh/Resource/Image.bundle' 19 | 20 | s.subspec 'Category' do |ss| 21 | ss.source_files = "KafkaRefresh/Category/**/*" 22 | ss.public_header_files = "KafkaRefresh/Category/*.{h}" 23 | end 24 | 25 | s.subspec 'Core' do |ss| 26 | ss.source_files = "KafkaRefresh/Core/**/*" 27 | ss.public_header_files = "KafkaRefresh/Core/*.{h}" 28 | ss.dependency 'KafkaRefresh/Category' 29 | end 30 | 31 | s.subspec 'Configuration' do |ss| 32 | ss.source_files = "KafkaRefresh/Configuration/**/*" 33 | ss.public_header_files = "KafkaRefresh/Configuration/**/*.{h}" 34 | ss.dependency 'KafkaRefresh/UIKit/HeadKit' 35 | ss.dependency 'KafkaRefresh/UIKit/FootKit' 36 | end 37 | 38 | s.subspec 'Default' do |ss| 39 | ss.source_files = "KafkaRefresh/Default/**/*" 40 | ss.public_header_files = "KafkaRefresh/Default/**/*.{h}" 41 | ss.dependency 'KafkaRefresh/Style' 42 | end 43 | 44 | s.subspec 'Style' do |ss| 45 | ss.source_files = "KafkaRefresh/Style/**/*" 46 | ss.public_header_files = "KafkaRefresh/Style/**/*.{h}" 47 | end 48 | 49 | s.subspec 'UIKit' do |ss| 50 | 51 | ss.subspec 'HeadKit' do |sss| 52 | sss.source_files = "KafkaRefresh/UIKit/HeadKit/**/*" 53 | sss.public_header_files = "KafkaRefresh/UIKit/HeadKit/*.{h}" 54 | sss.dependency 'KafkaRefresh/UIKit/LayerKit' 55 | sss.dependency 'KafkaRefresh/Category' 56 | sss.dependency 'KafkaRefresh/Core' 57 | sss.dependency 'KafkaRefresh/Default' 58 | sss.dependency 'KafkaRefresh/Style' 59 | end 60 | 61 | ss.subspec 'FootKit' do |sss| 62 | sss.source_files = "KafkaRefresh/UIKit/FootKit/**/*" 63 | sss.public_header_files = "KafkaRefresh/UIKit/FootKit/*.{h}" 64 | sss.dependency 'KafkaRefresh/UIKit/LayerKit' 65 | sss.dependency 'KafkaRefresh/Category' 66 | sss.dependency 'KafkaRefresh/Core' 67 | sss.dependency 'KafkaRefresh/Default' 68 | sss.dependency 'KafkaRefresh/Style' 69 | end 70 | 71 | ss.subspec 'LayerKit' do |sss| 72 | sss.source_files = "KafkaRefresh/UIKit/LayerKit/**/*" 73 | sss.public_header_files = "KafkaRefresh/UIKit/LayerKit/*.{h}" 74 | sss.dependency 'KafkaRefresh/Category' 75 | sss.dependency 'KafkaRefresh/Default' 76 | end 77 | end 78 | 79 | end 80 | -------------------------------------------------------------------------------- /KafkaRefresh/Category/KafkaCategories.h: -------------------------------------------------------------------------------- 1 | /************************************************************* 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * e-mail: xorshine@icloud.com 6 | * github:https://github.com/xorshine 7 | * 8 | * This source code is licensed under the MIT license. 9 | *************************************************************/ 10 | 11 | #import 12 | #import 13 | 14 | #ifndef weakify 15 | # define weakify(object) try{} @finally{} {} __weak __typeof__(object) weak##_##object = object; 16 | #endif 17 | 18 | #ifndef strongify 19 | # define strongify(object) try{} @finally{} __typeof__(object) object = weak##_##object; 20 | #endif 21 | 22 | #ifndef UIColorMake 23 | # define UIColorMake(r, g, b, a) [UIColor colorWithRed:(r)/255. green:(g)/255. blue:(b)/255. alpha:(a)] 24 | #endif 25 | 26 | @interface UIView (KafkaLayout) 27 | 28 | @property (nonatomic) CGFloat kr_left; 29 | @property (nonatomic) CGFloat kr_top; 30 | @property (nonatomic) CGFloat kr_right; 31 | @property (nonatomic) CGFloat kr_bottom; 32 | @property (nonatomic) CGFloat kr_width; 33 | @property (nonatomic) CGFloat kr_height; 34 | @property (nonatomic) CGFloat kr_centerX; 35 | @property (nonatomic) CGFloat kr_centerY; 36 | @property (nonatomic) CGPoint kr_origin; 37 | @property (nonatomic) CGSize kr_size; 38 | 39 | @end 40 | 41 | #pragma mark - 42 | 43 | @interface CALayer (KafkaLayout) 44 | 45 | @property (nonatomic) CGFloat kr_left; 46 | @property (nonatomic) CGFloat kr_top; 47 | @property (nonatomic) CGFloat kr_right; 48 | @property (nonatomic) CGFloat kr_bottom; 49 | @property (nonatomic) CGFloat kr_width; 50 | @property (nonatomic) CGFloat kr_height; 51 | @property (nonatomic) CGPoint kr_origin; 52 | @property (nonatomic) CGSize kr_size; 53 | @property (nonatomic) CGFloat kr_positionX; 54 | @property (nonatomic) CGFloat kr_positionY; 55 | 56 | @end 57 | 58 | #pragma mark - 59 | 60 | @interface UIScrollView (KafkaLayout) 61 | 62 | @property (nonatomic) CGFloat offsetX; 63 | @property (nonatomic) CGFloat offsetY; 64 | 65 | @property (nonatomic) CGFloat insetTop; 66 | @property (nonatomic) CGFloat insetBottom; 67 | 68 | @property (nonatomic, readonly) CGFloat contentHeight; 69 | 70 | /** 71 | scrollView's contentInset not refreshed 72 | */ 73 | @property (assign, readonly) UIEdgeInsets realContentInset; 74 | 75 | @end 76 | 77 | #pragma mark - 78 | 79 | @interface NSObject (KafkaAnimation) 80 | 81 | - (void)setAnimateBlock:(dispatch_block_t)block; 82 | - (void)setAnimateBlock:(dispatch_block_t)block completion:(dispatch_block_t)completion; 83 | 84 | @end 85 | -------------------------------------------------------------------------------- /KafkaRefresh/Category/KafkaCategories.m: -------------------------------------------------------------------------------- 1 | /************************************************************* 2 | * Copyright (c) 2016-present, K. * 3 | * All rights reserved. * 4 | * * 5 | * e-mail: xorshine@icloud.com * 6 | * github:https://github.com/xorshine * 7 | * * 8 | * This source code is licensed under the MIT license. * 9 | *************************************************************/ 10 | 11 | #import "KafkaCategories.h" 12 | 13 | @implementation UIView (KafkaLayout) 14 | 15 | - (CGFloat)kr_left { 16 | return self.frame.origin.x; 17 | } 18 | 19 | - (void)setKr_left:(CGFloat)x { 20 | CGRect frame = self.frame; 21 | frame.origin.x = x; 22 | self.frame = frame; 23 | } 24 | 25 | - (CGFloat)kr_top { 26 | return self.frame.origin.y; 27 | } 28 | 29 | - (void)setKr_top:(CGFloat)y { 30 | CGRect frame = self.frame; 31 | frame.origin.y = y; 32 | self.frame = frame; 33 | } 34 | 35 | - (CGFloat)kr_right { 36 | return self.frame.origin.x + self.frame.size.width; 37 | } 38 | 39 | - (void)setKr_right:(CGFloat)right { 40 | CGRect frame = self.frame; 41 | frame.origin.x = right - frame.size.width; 42 | self.frame = frame; 43 | } 44 | 45 | - (CGFloat)kr_bottom { 46 | return self.frame.origin.y + self.frame.size.height; 47 | } 48 | 49 | - (void)setKr_bottom:(CGFloat)bottom { 50 | CGRect frame = self.frame; 51 | frame.origin.y = bottom - frame.size.height; 52 | self.frame = frame; 53 | } 54 | 55 | - (CGFloat)kr_width { 56 | return self.frame.size.width; 57 | } 58 | 59 | - (void)setKr_width:(CGFloat)width { 60 | CGRect frame = self.frame; 61 | frame.size.width = width; 62 | self.frame = frame; 63 | } 64 | 65 | - (CGFloat)kr_height { 66 | return self.frame.size.height; 67 | } 68 | 69 | - (void)setKr_height:(CGFloat)height { 70 | CGRect frame = self.frame; 71 | frame.size.height = height; 72 | self.frame = frame; 73 | } 74 | 75 | - (CGFloat)kr_centerX { 76 | return self.center.x; 77 | } 78 | 79 | - (void)setKr_centerX:(CGFloat)centerX { 80 | self.center = CGPointMake(centerX, self.center.y); 81 | } 82 | 83 | - (CGFloat)kr_centerY { 84 | return self.center.y; 85 | } 86 | 87 | - (void)setKr_centerY:(CGFloat)centerY { 88 | self.center = CGPointMake(self.center.x, centerY); 89 | } 90 | 91 | - (CGPoint)kr_origin { 92 | return self.frame.origin; 93 | } 94 | 95 | - (void)setKr_origin:(CGPoint)origin { 96 | CGRect frame = self.frame; 97 | frame.origin = origin; 98 | self.frame = frame; 99 | } 100 | 101 | - (CGSize)kr_size { 102 | return self.frame.size; 103 | } 104 | 105 | - (void)setKr_size:(CGSize)size { 106 | CGRect frame = self.frame; 107 | frame.size = size; 108 | self.frame = frame; 109 | } 110 | 111 | @end 112 | 113 | #pragma mark - 114 | 115 | @implementation CALayer (KafkaLayout) 116 | 117 | - (CGFloat)kr_left{ 118 | return self.frame.origin.x; 119 | } 120 | 121 | - (void)setKr_left:(CGFloat)left{ 122 | CGRect frame = self.frame; 123 | frame.origin.x = left; 124 | self.frame = frame; 125 | } 126 | 127 | - (CGFloat)kr_top { 128 | return self.frame.origin.y; 129 | } 130 | 131 | - (void)setKr_top:(CGFloat)y { 132 | CGRect frame = self.frame; 133 | frame.origin.y = y; 134 | self.frame = frame; 135 | } 136 | 137 | - (CGFloat)kr_right { 138 | return self.frame.origin.x + self.frame.size.width; 139 | } 140 | 141 | - (void)setKr_right:(CGFloat)right { 142 | CGRect frame = self.frame; 143 | frame.origin.x = right - frame.size.width; 144 | self.frame = frame; 145 | } 146 | 147 | - (CGFloat)kr_bottom { 148 | return self.frame.origin.y + self.frame.size.height; 149 | } 150 | 151 | - (void)setKr_bottom:(CGFloat)bottom { 152 | CGRect frame = self.frame; 153 | frame.origin.y = bottom - frame.size.height; 154 | self.frame = frame; 155 | } 156 | 157 | - (CGFloat)kr_width { 158 | return self.frame.size.width; 159 | } 160 | 161 | - (void)setKr_width:(CGFloat)width { 162 | CGRect frame = self.frame; 163 | frame.size.width = width; 164 | self.frame = frame; 165 | } 166 | 167 | - (CGFloat)kr_height { 168 | return self.frame.size.height; 169 | } 170 | 171 | - (void)setKr_height:(CGFloat)height { 172 | CGRect frame = self.frame; 173 | frame.size.height = height; 174 | self.frame = frame; 175 | } 176 | 177 | - (CGFloat)kr_positionX { 178 | return self.position.x; 179 | } 180 | 181 | - (void)setKr_positionX:(CGFloat)positionX { 182 | self.position = CGPointMake(positionX, self.position.y); 183 | } 184 | 185 | - (CGFloat)kr_positionY { 186 | return self.position.y; 187 | } 188 | 189 | - (void)setKr_positionY:(CGFloat)positionY { 190 | self.position = CGPointMake(self.position.x, positionY); 191 | } 192 | 193 | - (CGPoint)kr_origin { 194 | return self.frame.origin; 195 | } 196 | 197 | - (void)setKr_origin:(CGPoint)origin { 198 | CGRect frame = self.frame; 199 | frame.origin = origin; 200 | self.frame = frame; 201 | } 202 | 203 | - (CGSize)kr_size { 204 | return self.frame.size; 205 | } 206 | 207 | - (void)setKr_size:(CGSize)size { 208 | CGRect frame = self.frame; 209 | frame.size = size; 210 | self.frame = frame; 211 | } 212 | 213 | @end 214 | 215 | #pragma mark - 216 | 217 | @implementation UIScrollView (KafkaLayout) 218 | 219 | - (CGFloat)offsetX{ 220 | return self.contentOffset.x; 221 | } 222 | 223 | - (void)setOffsetX:(CGFloat)offsetX{ 224 | CGPoint offset = self.contentOffset; 225 | offset.x = offsetX; 226 | self.contentOffset = offset; 227 | } 228 | 229 | - (CGFloat)offsetY{ 230 | return self.contentOffset.y; 231 | } 232 | 233 | -(void)setOffsetY:(CGFloat)offsetY{ 234 | CGPoint offset = self.contentOffset; 235 | offset.y = offsetY; 236 | self.contentOffset = offset; 237 | } 238 | 239 | - (CGFloat)insetTop{ 240 | return self.realContentInset.top; 241 | } 242 | 243 | - (void)setInsetTop:(CGFloat)insetTop{ 244 | UIEdgeInsets inset = self.contentInset; 245 | inset.top = insetTop; 246 | if (@available(iOS 11.0, *)) { 247 | inset.top -= (self.adjustedContentInset.top - self.contentInset.top); 248 | } 249 | // self.contentInset = inset; 250 | CGFloat top = self.contentInset.top; 251 | if (round(top) != round(inset.top)) { 252 | self.contentInset = inset; 253 | } 254 | } 255 | 256 | - (CGFloat)insetBottom{ 257 | return self.realContentInset.bottom; 258 | } 259 | 260 | - (void)setInsetBottom:(CGFloat)insetBottom{ 261 | UIEdgeInsets inset = self.contentInset; 262 | inset.bottom = insetBottom; 263 | if (@available(iOS 11.0, *)) { 264 | inset.bottom -= (self.adjustedContentInset.bottom - self.contentInset.bottom); 265 | } 266 | self.contentInset = inset; 267 | } 268 | 269 | - (CGFloat)contentHeight{ 270 | return self.contentSize.height; 271 | } 272 | 273 | - (UIEdgeInsets)realContentInset{ 274 | if (@available(iOS 11.0, *)) { 275 | return self.adjustedContentInset; 276 | } else { 277 | return self.contentInset; 278 | } 279 | } 280 | 281 | @end 282 | 283 | #pragma mark - 284 | 285 | @implementation NSObject (KafkaAnimation) 286 | 287 | - (void)setAnimateBlock:(dispatch_block_t)block{ 288 | [self setAnimateBlock:block completion:nil]; 289 | } 290 | 291 | - (void)setAnimateBlock:(dispatch_block_t)block completion:(dispatch_block_t)completion{ 292 | [UIView animateWithDuration:0.15 293 | delay:0 294 | options:UIViewAnimationOptionCurveLinear 295 | animations:block 296 | completion:^(BOOL finished) { 297 | if (completion) completion(); 298 | }]; 299 | } 300 | 301 | @end 302 | 303 | 304 | -------------------------------------------------------------------------------- /KafkaRefresh/Configuration/UIScrollView+KafkaConfiguration.h: -------------------------------------------------------------------------------- 1 | /************************************************************* 2 | * Copyright (c) 2016-present, K. * 3 | * All rights reserved. * 4 | * * 5 | * e-mail: xorshine@icloud.com * 6 | * github:https://github.com/xorshine * 7 | * * 8 | * This source code is licensed under the MIT license. * 9 | *************************************************************/ 10 | 11 | #import 12 | #import "KafkaRefreshStyle.h" 13 | #import "KafkaRefreshControl.h" 14 | 15 | @interface UIScrollView (KafkaConfiguration) 16 | 17 | - (void)bindGlobalStyleForHeadRefreshHandler:(KafkaRefreshHandler)block; 18 | - (void)bindHeadRefreshHandler:(KafkaRefreshHandler)block themeColor:(UIColor *)color refreshStyle:(KafkaRefreshStyle)style; 19 | 20 | - (void)bindGlobalStyleForFootRefreshHandler:(KafkaRefreshHandler)block; 21 | - (void)bindFootRefreshHandler:(KafkaRefreshHandler)block themeColor:(UIColor *)color refreshStyle:(KafkaRefreshStyle)style; 22 | 23 | @end 24 | 25 | #pragma mark - deprecated 26 | 27 | @interface UIScrollView (KafakaDeprecated) 28 | 29 | - (void)bindRefreshStyle:(KafkaRefreshStyle)style fillColor:(UIColor *)fillColor atPosition:(KafkaRefreshPosition)position refreshHanler:(KafkaRefreshHandler)handler __attribute__((deprecated("please use bindHeadRefreshHandler: themeColor: refreshStyle"))); 30 | 31 | - (void)bindRefreshStyle:(KafkaRefreshStyle)style fillColor:(UIColor *)fillColor animatedBackgroundColor:(UIColor *)backgroundColor atPosition:(KafkaRefreshPosition)position refreshHanler:(KafkaRefreshHandler)handler __attribute__((deprecated("please use bindHeadRefreshHandler: themeColor: refreshStyle"))); 32 | 33 | - (void)bindDefaultRefreshStyleAtPosition:(KafkaRefreshPosition)position refreshHanler:(KafkaRefreshHandler)handler __attribute__((deprecated("please use bindGlobalStyleForHeadRefreshHandler:"))); 34 | 35 | @end 36 | 37 | 38 | -------------------------------------------------------------------------------- /KafkaRefresh/Configuration/UIScrollView+KafkaConfiguration.m: -------------------------------------------------------------------------------- 1 | /************************************************************* 2 | * Copyright (c) 2016-present, K. * 3 | * All rights reserved. * 4 | * * 5 | * e-mail: xorshine@icloud.com * 6 | * github:https://github.com/xorshine * 7 | * * 8 | * This source code is licensed under the MIT license. * 9 | *************************************************************/ 10 | 11 | #import "UIScrollView+KafkaConfiguration.h" 12 | #import "KafkaRefreshDefaults.h" 13 | #import "UIScrollView+KafkaRefreshControl.h" 14 | #import "KafkaNativeHeader.h" 15 | #import "KafkaReplicatorHeader.h" 16 | #import "KafkaRingIndicatorHeader.h" 17 | #import "KafkaArrowHeader.h" 18 | #import "KafkaNativeFooter.h" 19 | #import "KafkaReplicatorFooter.h" 20 | #import "KafkaRingIndicatorFooter.h" 21 | #import "KafkaArrowFooter.h" 22 | 23 | @implementation UIScrollView (KafkaConfiguration) 24 | 25 | - (void)bindGlobalStyleForHeadRefreshHandler:(KafkaRefreshHandler)block{ 26 | [self bindHeadRefreshHandler:block themeColor:[KafkaRefreshDefaults standardRefreshDefaults].themeColor refreshStyle:[KafkaRefreshDefaults standardRefreshDefaults].headDefaultStyle]; 27 | } 28 | 29 | - (void)bindHeadRefreshHandler:(KafkaRefreshHandler)block themeColor:(UIColor *)color refreshStyle:(KafkaRefreshStyle)style { 30 | __kindof KafkaRefreshControl *head = nil; 31 | switch (style) { 32 | case KafkaRefreshStyleNative: { 33 | KafkaNativeHeader *h = [[KafkaNativeHeader alloc] init]; 34 | h.indicator.color = color; 35 | head = h; 36 | break; 37 | } 38 | case KafkaRefreshStyleReplicatorWoody: 39 | case KafkaRefreshStyleReplicatorAllen: 40 | case KafkaRefreshStyleReplicatorCircle: 41 | case KafkaRefreshStyleReplicatorDot: 42 | case KafkaRefreshStyleReplicatorArc: 43 | case KafkaRefreshStyleReplicatorTriangle:{ 44 | head = [[KafkaReplicatorHeader alloc] init]; 45 | ((KafkaReplicatorHeader *)head).animationStyle = style - 1; 46 | break; 47 | } 48 | case KafkaRefreshStyleAnimatableRing: { 49 | head = [[KafkaRingIndicatorHeader alloc] init]; 50 | break; 51 | } 52 | case KafkaRefreshStyleAnimatableArrow: { 53 | head = [[KafkaArrowHeader alloc] init]; 54 | break; 55 | } 56 | } 57 | head.refreshHandler = block; 58 | head.themeColor = color; 59 | self.headRefreshControl = head; 60 | } 61 | 62 | #pragma mark - 63 | 64 | - (void)bindGlobalStyleForFootRefreshHandler:(KafkaRefreshHandler)block { 65 | [self bindFootRefreshHandler:block themeColor:[KafkaRefreshDefaults standardRefreshDefaults].themeColor refreshStyle:[KafkaRefreshDefaults standardRefreshDefaults].footDefaultStyle]; 66 | } 67 | 68 | - (void)bindFootRefreshHandler:(KafkaRefreshHandler)block themeColor:(UIColor *)color refreshStyle:(KafkaRefreshStyle)style { 69 | __kindof KafkaFootRefreshControl *foot = nil; 70 | switch (style) { 71 | case KafkaRefreshStyleNative:{ 72 | foot = [[KafkaNativeFooter alloc] init]; 73 | break; 74 | } 75 | case KafkaRefreshStyleReplicatorWoody: 76 | case KafkaRefreshStyleReplicatorAllen: 77 | case KafkaRefreshStyleReplicatorCircle: 78 | case KafkaRefreshStyleReplicatorDot: 79 | case KafkaRefreshStyleReplicatorArc: 80 | case KafkaRefreshStyleReplicatorTriangle: { 81 | foot = [[KafkaReplicatorFooter alloc] init]; 82 | ((KafkaReplicatorFooter *)foot).animationStyle = style - 1; 83 | break; 84 | } 85 | case KafkaRefreshStyleAnimatableRing: { 86 | foot = [[KafkaRingIndicatorFooter alloc] init]; 87 | break; 88 | } 89 | case KafkaRefreshStyleAnimatableArrow: { 90 | foot = [[KafkaArrowFooter alloc] init]; 91 | break; 92 | } 93 | } 94 | foot.themeColor = color; 95 | foot.refreshHandler = block; 96 | self.footRefreshControl = foot; 97 | } 98 | 99 | @end 100 | 101 | #pragma mark - 102 | 103 | @implementation UIScrollView(KafakaDeprecated) 104 | 105 | - (void)bindRefreshStyle:(KafkaRefreshStyle)style fillColor:(UIColor *)fillColor atPosition:(KafkaRefreshPosition)position refreshHanler:(KafkaRefreshHandler)handler { 106 | __kindof KafkaRefreshControl *control = [self _classWithRefreshStyle:style color:fillColor position:position]; 107 | if (!control) return; 108 | control.refreshHandler = handler; 109 | if (position == KafkaRefreshPositionHeader) { 110 | self.headRefreshControl = control; 111 | }else{ 112 | self.footRefreshControl = control; 113 | } 114 | } 115 | 116 | - (void)bindRefreshStyle:(KafkaRefreshStyle)style fillColor:(UIColor *)fillColor animatedBackgroundColor:(UIColor *)backgroundColor atPosition:(KafkaRefreshPosition)position refreshHanler:(KafkaRefreshHandler)handler { 117 | __kindof KafkaRefreshControl *control = [self _classWithRefreshStyle:style color:fillColor position:position]; 118 | if (!control) return; 119 | control.refreshHandler = handler; 120 | control.animatedBackgroundColor = backgroundColor; 121 | if (position == KafkaRefreshPositionHeader) { 122 | self.headRefreshControl = control; 123 | }else{ 124 | self.footRefreshControl = control; 125 | } 126 | } 127 | 128 | - (void)bindDefaultRefreshStyleAtPosition:(KafkaRefreshPosition)position refreshHanler:(KafkaRefreshHandler)handler { 129 | if (position == KafkaRefreshPositionHeader) { 130 | __kindof KafkaRefreshControl *control = [self _classWithRefreshStyle:[KafkaRefreshDefaults standardRefreshDefaults].headDefaultStyle 131 | color:[KafkaRefreshDefaults standardRefreshDefaults].themeColor 132 | position:position]; 133 | self.headRefreshControl = control; 134 | control.refreshHandler = handler; 135 | }else{ 136 | __kindof KafkaRefreshControl *control = [self _classWithRefreshStyle:[KafkaRefreshDefaults standardRefreshDefaults].footDefaultStyle 137 | color:[KafkaRefreshDefaults standardRefreshDefaults].themeColor 138 | position:position]; 139 | self.footRefreshControl = control; 140 | control.refreshHandler = handler; 141 | } 142 | } 143 | 144 | - (__kindof KafkaRefreshControl *)_classWithRefreshStyle:(KafkaRefreshStyle)style 145 | color:(UIColor *)color 146 | position:(KafkaRefreshPosition)position{ 147 | KafkaRefreshControl *cls = nil; 148 | switch (style) { 149 | case KafkaRefreshStyleNative:{ 150 | if (position == KafkaRefreshPositionHeader) { 151 | cls = [[KafkaNativeHeader alloc] init]; 152 | }else{ 153 | cls = [[KafkaNativeFooter alloc] init]; 154 | } 155 | break; 156 | } 157 | case KafkaRefreshStyleReplicatorWoody: 158 | case KafkaRefreshStyleReplicatorAllen: 159 | case KafkaRefreshStyleReplicatorCircle: 160 | case KafkaRefreshStyleReplicatorDot: 161 | case KafkaRefreshStyleReplicatorArc: 162 | case KafkaRefreshStyleReplicatorTriangle:{ 163 | if (position == KafkaRefreshPositionHeader) { 164 | cls = [[KafkaReplicatorHeader alloc] init]; 165 | }else{ 166 | cls = [[KafkaReplicatorFooter alloc] init]; 167 | } 168 | ((KafkaReplicatorHeader *)cls).animationStyle = style - 1; 169 | break; 170 | } 171 | case KafkaRefreshStyleAnimatableRing:{ 172 | if (position == KafkaRefreshPositionHeader) { 173 | cls = [[KafkaRingIndicatorHeader alloc] init]; 174 | }else{ 175 | cls = [[KafkaRingIndicatorFooter alloc] init]; 176 | } 177 | break; 178 | } 179 | case KafkaRefreshStyleAnimatableArrow:{ 180 | if (position == KafkaRefreshPositionHeader) { 181 | cls = [[KafkaArrowHeader alloc] init]; 182 | }else{ 183 | cls = [[KafkaArrowFooter alloc] init]; 184 | } 185 | break; 186 | } 187 | } 188 | cls.themeColor = color; 189 | return cls; 190 | } 191 | 192 | @end 193 | 194 | -------------------------------------------------------------------------------- /KafkaRefresh/Core/KafkaFootRefreshControl.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * e-mail:xiaohuiprivate@gmail.com 6 | * github:https://github.com/xorshine 7 | * 8 | * This source code is licensed under the MIT license. 9 | */ 10 | 11 | 12 | #import "KafkaRefreshControl.h" 13 | 14 | @interface KafkaFootRefreshControl : KafkaRefreshControl 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /KafkaRefresh/Core/KafkaFootRefreshControl.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * e-mail:xorshine@icloud.com 6 | * github:https://github.com/xorshine 7 | * 8 | * This source code is licensed under the MIT license. 9 | */ 10 | 11 | #import "KafkaFootRefreshControl.h" 12 | #import "KafkaCategories.h" 13 | 14 | @implementation KafkaFootRefreshControl 15 | 16 | - (void)layoutSubviews { 17 | [super layoutSubviews]; 18 | self.kr_top = self.scrollView.contentHeight; 19 | } 20 | 21 | - (void)setScrollViewToRefreshLocation { 22 | [super setScrollViewToRefreshLocation]; 23 | dispatch_block_t animation = ^(void){ 24 | if (self.isTriggeredRefreshByUser) { 25 | self.refreshState = KafkaRefreshStateScrolling; 26 | if (self.scrollView.contentHeight > self.scrollView.kr_height && 27 | self.scrollView.offsetY >= OffsetOfTriggeringFootRefreshControlToRefresh(self)) { 28 | /** 29 | This condition can be pre-execute refreshHandler, and will not feel scrollview scroll 30 | */ 31 | [self.scrollView setContentOffset:RefreshingPoint(self)]; 32 | [self kafkaDidScrollWithProgress:0.5 max:self.stretchOffsetYAxisThreshold]; 33 | } 34 | } 35 | self.scrollView.insetBottom = self.presetContentInsets.bottom + self.kr_height; 36 | }; 37 | 38 | dispatch_block_t completion = ^(void){ 39 | if (self.refreshHandler) self.refreshHandler(); 40 | if (self.isTriggeredRefreshByUser) { 41 | self.refreshState = KafkaRefreshStateReady; 42 | self.refreshState = KafkaRefreshStateRefreshing; 43 | [self kafkaDidScrollWithProgress:1. max:self.stretchOffsetYAxisThreshold]; 44 | } 45 | }; 46 | @weakify(self); 47 | dispatch_async(dispatch_get_main_queue(), ^{ 48 | @strongify(self); 49 | self.presetContentInsets = self.scrollView.realContentInset; 50 | [self setAnimateBlock:animation completion:completion]; 51 | }); 52 | } 53 | 54 | - (void)setScrollViewToOriginalLocation:(dispatch_block_t)completion { 55 | [super setScrollViewToOriginalLocation:completion]; 56 | @weakify(self); 57 | [self setAnimateBlock:^{ 58 | @strongify(self); 59 | self.animating = YES; 60 | self.scrollView.insetBottom = self.presetContentInsets.bottom; 61 | } completion:^{ 62 | @strongify(self); 63 | self.animating = NO; 64 | self.triggeredRefreshByUser = NO; 65 | self.refreshState = KafkaRefreshStateNone; 66 | if (completion) { 67 | completion(); 68 | } 69 | }]; 70 | } 71 | 72 | #pragma mark - contentOffset 73 | 74 | 75 | static CGPoint RefreshingPoint(KafkaFootRefreshControl *cSelf) { 76 | UIScrollView * sc = cSelf.scrollView; 77 | CGFloat x = sc.kr_left; 78 | // CGFloat y = sc.contentHeight - sc.height + cSelf.height*cSelf.stretchOffsetYAxisThreshold; 79 | return CGPointMake(x, OffsetOfTriggeringFootRefreshControlToRefresh(cSelf)); 80 | } 81 | 82 | 83 | static CGFloat OffsetOfTriggeringFootRefreshControlToRefresh(KafkaRefreshControl * cSelf) { 84 | UIScrollView * sc = cSelf.scrollView; 85 | CGFloat y = sc.contentHeight - sc.kr_height + cSelf.stretchOffsetYAxisThreshold*cSelf.kr_height + cSelf.presetContentInsets.bottom; 86 | return y; 87 | } 88 | 89 | static CGFloat OffsetOfTriggeringFootRefreshControlToAutoRefresh(KafkaRefreshControl * cSelf) { 90 | UIScrollView * sc = cSelf.scrollView; 91 | CGFloat y = sc.contentHeight - sc.kr_height + cSelf.presetContentInsets.bottom; 92 | return y; 93 | } 94 | 95 | static CGFloat OffsetOfFootRefreshControlToRestore(KafkaRefreshControl * cSelf) { 96 | UIScrollView * sc = cSelf.scrollView; 97 | CGFloat y = sc.contentHeight - sc.kr_height + cSelf.presetContentInsets.bottom; 98 | return y; 99 | } 100 | 101 | - (void)privateContentOffsetOfScrollViewDidChange:(CGPoint)contentOffset{ 102 | [super privateContentOffsetOfScrollViewDidChange:contentOffset]; 103 | if(self.isRefresh || self.isAnimating) return; 104 | 105 | if (self.refreshState != KafkaRefreshStateRefreshing) { 106 | if (self.isTriggeredRefreshByUser) return; 107 | self.presetContentInsets = self.scrollView.realContentInset; 108 | 109 | CGFloat originY = 0.0, maxY = 0.0, minY = 0.0 , contentOffsetYInBottom = 0.0; 110 | 111 | if (self.scrollView.contentHeight + self.presetContentInsets.top <= self.scrollView.kr_height) { 112 | maxY = self.stretchOffsetYAxisThreshold * self.kr_height; 113 | minY = 0; 114 | originY = contentOffset.y + self.presetContentInsets.top; 115 | 116 | if (self.refreshState == KafkaRefreshStateScrolling) { 117 | CGFloat progress = fabs(originY) / self.kr_height; 118 | if (progress <= self.stretchOffsetYAxisThreshold) { 119 | self.progress = progress; 120 | } 121 | } 122 | } else { 123 | maxY = OffsetOfTriggeringFootRefreshControlToRefresh(self); 124 | minY = OffsetOfFootRefreshControlToRestore(self); 125 | originY = contentOffset.y; 126 | contentOffsetYInBottom = self.scrollView.contentHeight - self.scrollView.kr_height; 127 | ///////////////////////// 128 | ///uncontinuous callback 129 | ///////////////////////// 130 | static CGFloat uncontinuousOpt = 50.; 131 | if (originY < minY - uncontinuousOpt) return; 132 | 133 | if (self.refreshState == KafkaRefreshStateScrolling){ 134 | CGFloat progress = fabs((originY - contentOffsetYInBottom - self.presetContentInsets.bottom))/self.kr_height; 135 | if (progress <= self.stretchOffsetYAxisThreshold) { 136 | self.progress = progress; 137 | } 138 | } 139 | 140 | if (self.autoRefreshOnFoot) { 141 | if (self.scrollView.isDragging && originY > OffsetOfTriggeringFootRefreshControlToAutoRefresh(self) && !self.isAnimating && self.refreshState == KafkaRefreshStateNone && !self.isShouldNoLongerRefresh) { 142 | [self beginRefreshing]; 143 | } 144 | return; 145 | } 146 | } 147 | 148 | if (!self.scrollView.isDragging && self.refreshState == KafkaRefreshStateReady) { 149 | self.triggeredRefreshByUser = NO; 150 | self.refreshState = KafkaRefreshStateRefreshing; 151 | [self setScrollViewToRefreshLocation]; 152 | } 153 | else if (originY <= minY && !self.isAnimating) { 154 | self.refreshState = KafkaRefreshStateNone; 155 | } 156 | else if (self.scrollView.isDragging && originY >= minY && originY <= maxY && self.refreshState != KafkaRefreshStateScrolling) { 157 | self.refreshState = KafkaRefreshStateScrolling; 158 | } 159 | else if (self.scrollView.isDragging && originY > maxY && !self.isAnimating && self.refreshState != KafkaRefreshStateReady && !self.isShouldNoLongerRefresh) { 160 | self.refreshState = KafkaRefreshStateReady; 161 | } 162 | } 163 | } 164 | 165 | @end 166 | -------------------------------------------------------------------------------- /KafkaRefresh/Core/KafkaHeadRefreshControl.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * e-mail:xorshine@icloud.com 6 | * github:https://github.com/xorshine 7 | * 8 | * This source code is licensed under the MIT license. 9 | */ 10 | 11 | #import "KafkaRefreshControl.h" 12 | 13 | @interface KafkaHeadRefreshControl : KafkaRefreshControl 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /KafkaRefresh/Core/KafkaHeadRefreshControl.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * e-mail:xiaohuiprivate@gmail.com 6 | * github:https://github.com/xorshine 7 | * 8 | * This source code is licensed under the MIT license. 9 | */ 10 | 11 | #import "KafkaHeadRefreshControl.h" 12 | #import "KafkaCategories.h" 13 | 14 | @implementation KafkaHeadRefreshControl 15 | 16 | - (void)layoutSubviews{ 17 | [super layoutSubviews]; 18 | self.kr_top = -self.kr_height; 19 | } 20 | 21 | static inline CGPoint RefreshingPoint(KafkaHeadRefreshControl *cSelf){ 22 | UIScrollView * sc = cSelf.scrollView; 23 | CGFloat x = sc.kr_left; 24 | CGFloat y = -(cSelf.kr_height + cSelf.presetContentInsets.top); 25 | return CGPointMake(x, y); 26 | } 27 | 28 | - (void)setScrollViewToRefreshLocation{ 29 | [super setScrollViewToRefreshLocation]; 30 | dispatch_block_t animatedBlock = ^(void){ 31 | if (self.isTriggeredRefreshByUser) { 32 | self.refreshState = KafkaRefreshStateScrolling; 33 | /*/////////////////////////////////////////////////////////////////////////////////////////// 34 | In general, we use UITableView, especially UITableView need to use the drop-down refresh, 35 | we rarely set SectionHeader. Unfortunately, if you use SectionHeader and integrate with 36 | UIRefreshControl or other third-party libraries, the refresh effect will be very ugly. 37 | 38 | This code has two effects: 39 | 1. when using SectionHeader refresh effect is still very natural. 40 | 2. when your scrollView using preloading technology, only in the right place, 41 | such as pull down a pixel you can see the refresh control case, will show the 42 | refresh effect. If the pull-down distance exceeds the height of the refresh control, 43 | then the refresh control has long been unable to appear on the screen, 44 | indicating that the top of the contentOffset office there is a long distance, 45 | this time, even if you call the beginRefreshing method, ScrollView position and effect 46 | are Will not be affected, so the deal is very friendly in the data preloading technology. 47 | ///////////////////////////////////////////////////////////////////////////////////////////*/ 48 | CGFloat min = -self.presetContentInsets.top; 49 | CGFloat max = -(self.presetContentInsets.top-self.kr_height); 50 | if (self.scrollView.offsetY >= min && self.scrollView.offsetY <= max) { 51 | [self.scrollView setContentOffset:RefreshingPoint(self)]; 52 | [self kafkaDidScrollWithProgress:0.5 max:self.stretchOffsetYAxisThreshold]; 53 | self.scrollView.insetTop = self.kr_height + self.presetContentInsets.top; 54 | } 55 | }else{ 56 | self.scrollView.insetTop = self.kr_height + self.presetContentInsets.top; 57 | } 58 | }; 59 | 60 | dispatch_block_t completionBlock = ^(void){ 61 | if (self.isTriggeredRefreshByUser) { 62 | self.refreshState = KafkaRefreshStateReady; 63 | self.refreshState = KafkaRefreshStateRefreshing; 64 | [self kafkaDidScrollWithProgress:1. max:self.stretchOffsetYAxisThreshold]; 65 | } 66 | if (self.refreshHandler) self.refreshHandler(); 67 | }; 68 | @weakify(self); 69 | dispatch_async(dispatch_get_main_queue(), ^{ 70 | @strongify(self); 71 | self.presetContentInsets = self.scrollView.realContentInset; 72 | [self setAnimateBlock:animatedBlock completion:completionBlock]; 73 | }); 74 | } 75 | 76 | - (void)setScrollViewToOriginalLocation:(dispatch_block_t)block{ 77 | [super setScrollViewToOriginalLocation:block]; 78 | dispatch_block_t animation = ^{ 79 | self.animating = YES; 80 | self.scrollView.insetTop = self.presetContentInsets.top; 81 | }; 82 | 83 | dispatch_block_t completion = ^{ 84 | self.animating = NO; 85 | self.triggeredRefreshByUser = NO; 86 | self.refreshState = KafkaRefreshStateNone; 87 | if (block) { 88 | block(); 89 | } 90 | }; 91 | [self setAnimateBlock:animation completion:completion]; 92 | } 93 | 94 | #pragma mark - contentOffset 95 | 96 | static CGFloat MaxYForTriggeringRefresh(KafkaRefreshControl * cSelf){ 97 | CGFloat y = -cSelf.presetContentInsets.top + cSelf.stretchOffsetYAxisThreshold * cSelf.kr_top; 98 | return y; 99 | } 100 | 101 | static CGFloat MinYForNone(KafkaRefreshControl * cSelf){ 102 | return -cSelf.presetContentInsets.top;; 103 | } 104 | 105 | - (void)privateContentOffsetOfScrollViewDidChange:(CGPoint)contentOffset{ 106 | [super privateContentOffsetOfScrollViewDidChange:contentOffset]; 107 | CGFloat maxY = MaxYForTriggeringRefresh(self); 108 | CGFloat minY = MinYForNone(self); 109 | CGFloat originY = contentOffset.y; 110 | 111 | if (self.refreshState == KafkaRefreshStateRefreshing) { 112 | ///////////////////////////////////////////////////// 113 | //fix hover problem of sectionHeader 114 | ///////////////////////////////////////////////////// 115 | if (originY < 0 && (-originY >= self.presetContentInsets.top)) { 116 | CGFloat threshold = self.presetContentInsets.top + self.kr_height; 117 | if (-originY > threshold) { 118 | self.scrollView.insetTop = threshold; 119 | }else{ 120 | self.scrollView.insetTop = -originY; 121 | } 122 | }else{ 123 | if (self.scrollView.insetTop != self.presetContentInsets.top) { 124 | self.scrollView.insetTop = self.presetContentInsets.top; 125 | } 126 | } 127 | }else{ 128 | if (self.isTriggeredRefreshByUser) return; 129 | 130 | self.presetContentInsets = self.scrollView.realContentInset; 131 | 132 | if (self.refreshState == KafkaRefreshStateScrolling){ 133 | CGFloat progress = (fabs((double)originY) - self.presetContentInsets.top)/self.kr_height; 134 | if (progress <= self.stretchOffsetYAxisThreshold) { 135 | self.progress = progress; 136 | } 137 | } 138 | 139 | if (!self.scrollView.isDragging && self.refreshState == KafkaRefreshStateReady){ 140 | self.triggeredRefreshByUser = NO; 141 | self.refreshState = KafkaRefreshStateRefreshing; 142 | [self setScrollViewToRefreshLocation]; 143 | } 144 | else if (originY >= minY && !self.isAnimating){ 145 | self.refreshState = KafkaRefreshStateNone; 146 | } 147 | else if (self.scrollView.isDragging && originY <= minY 148 | && originY >= maxY && self.refreshState != KafkaRefreshStateScrolling){ 149 | self.refreshState = KafkaRefreshStateScrolling; 150 | } 151 | else if (self.scrollView.isDragging && originY < maxY && !self.isAnimating 152 | && self.refreshState != KafkaRefreshStateReady && !self.isShouldNoLongerRefresh){ 153 | self.refreshState = KafkaRefreshStateReady; 154 | } 155 | } 156 | } 157 | 158 | @end 159 | -------------------------------------------------------------------------------- /KafkaRefresh/Core/KafkaRefreshControl.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * e-mail:xiaohuiprivate@gmail.com 6 | * github:https://github.com/xorshine 7 | * 8 | * This source code is licensed under the MIT license. 9 | */ 10 | 11 | 12 | #import 13 | #import "KafkaRefreshProtocol.h" 14 | 15 | #ifndef Kafka_REQUIRES_SUPER 16 | # if __has_attribute(objc_requires_super) 17 | # define Kafka_REQUIRES_SUPER __attribute__((objc_requires_super)) 18 | # else 19 | # define Kafka_REQUIRES_SUPER 20 | # endif 21 | #endif 22 | 23 | #define KafkaColorWithRGBA(r,g,b,a) \ 24 | [UIColor colorWithRed:(r)/255. green:(g)/255. blue:(b)/255. alpha:(a)] 25 | 26 | /** 27 | Refresh the control's location 28 | */ 29 | typedef NS_ENUM(NSInteger,KafkaRefreshPosition) { 30 | KafkaRefreshPositionHeader = 0, 31 | KafkaRefreshPositionFooter 32 | }; 33 | 34 | typedef void(^KafkaRefreshHandler)(void); 35 | 36 | /** 37 | Refresh control base class, developers do not use this class directly 38 | */ 39 | NS_CLASS_AVAILABLE_IOS(7_0) @interface KafkaRefreshControl: UIView 40 | 41 | /** 42 | The UIScrollView to which the control is added, developers may not set 43 | */ 44 | @property (nonatomic, weak, readonly) __kindof UIScrollView *scrollView; 45 | 46 | /** 47 | Whether it is refreshing 48 | */ 49 | @property (nonatomic, readonly, getter=isRefresh) BOOL refresh; 50 | 51 | /** 52 | Judge whether the animation is executed when the refresh is over 53 | */ 54 | @property (assign, nonatomic, getter=isAnimating) BOOL animating; 55 | 56 | /** 57 | Control refresh status, developers may not set 58 | */ 59 | @property (nonatomic) KafkaRefreshState refreshState; 60 | 61 | /** 62 | When the system automatically or manually adjust contentInset, 63 | 64 | this value will be saved 65 | */ 66 | @property (assign, nonatomic) UIEdgeInsets presetContentInsets; 67 | 68 | /** 69 | This value is set to TRUE if the beginRefresh method is called automatically 70 | 71 | developers may not set 72 | */ 73 | @property (assign, nonatomic, getter=isTriggeredRefreshByUser) BOOL triggeredRefreshByUser; 74 | 75 | /** 76 | the current position offset of the control as a percentage 77 | 78 | of the offset that triggered the refresh 79 | */ 80 | @property (assign, nonatomic) CGFloat progress; 81 | 82 | /** 83 | Block will be called when refreshing 84 | */ 85 | @property (copy, nonatomic) KafkaRefreshHandler refreshHandler; 86 | 87 | /** 88 | The threshold for trigger refresh default 1.0 must be set to not less than 1.0, 89 | 90 | default value is 1.3, developers can set the value 91 | */ 92 | @property (assign, nonatomic) CGFloat stretchOffsetYAxisThreshold; 93 | 94 | /** 95 | fill colors for points, lines, and faces that appear in this control. 96 | */ 97 | @property (strong, nonatomic) UIColor *themeColor; 98 | 99 | /** 100 | The background color of the layer that executes the animation 101 | */ 102 | @property (strong, nonatomic) UIColor *animatedBackgroundColor; 103 | 104 | /** 105 | if called method "endRefreshingAndNoLongerRefreshingWithAlertText:" to end refresh, 106 | shouldNoLongerRefresh will set TRUE. 107 | */ 108 | @property (assign, nonatomic,readonly, getter=isShouldNoLongerRefresh) BOOL shouldNoLongerRefresh; 109 | /** 110 | scrollview trigger refresh automatically that don't need to scroll to bottom. 111 | default is YES; 112 | 113 | ATTENTION:!!! 114 | 115 | if (no data) { 116 | [tableView.footRefreshControl endRefreshingAndNoLongerRefreshingWithAlertText:@"no more"]; 117 | } else { 118 | [tableView.footRefreshControl endRefreshingWithAlertText:@"Did load successfully" completion:nil]; 119 | } 120 | */ 121 | @property (nonatomic, assign) BOOL autoRefreshOnFoot; 122 | 123 | /** 124 | Set the color of the prompt text after the refresh is completed. 125 | 126 | @param alertTextColor color 127 | */ 128 | - (void)setAlertTextColor:(UIColor *)alertTextColor; 129 | 130 | /** 131 | Set the background color of the prompt text after the refresh is completed. 132 | 133 | @param alertBackgroundColor color 134 | */ 135 | - (void)setAlertBackgroundColor:(UIColor *)alertBackgroundColor; 136 | 137 | /** 138 | Called right after initialization is completed 139 | */ 140 | - (void)setupProperties Kafka_REQUIRES_SUPER; 141 | 142 | /** 143 | Subclasses override this method 144 | */ 145 | - (void)privateContentOffsetOfScrollViewDidChange:(CGPoint)contentOffset Kafka_REQUIRES_SUPER; 146 | - (void)setScrollViewToRefreshLocation Kafka_REQUIRES_SUPER; 147 | - (void)setScrollViewToOriginalLocation:(dispatch_block_t)block Kafka_REQUIRES_SUPER; 148 | 149 | #pragma mark - public 150 | 151 | /** 152 | Trigger refresh 153 | */ 154 | - (void)beginRefreshing Kafka_REQUIRES_SUPER; 155 | 156 | /** 157 | end refresh 158 | */ 159 | - (void)endRefreshing Kafka_REQUIRES_SUPER; 160 | 161 | /** 162 | When this method is called to end the refresh, there will be a 1.5 second 163 | animated display of "text". Please note that the length of text, please 164 | try to be brief, otherwise it will be cut off. 165 | 166 | @param text default is nil, and no animation. 167 | @param completion when text is hidden, this block will be called. 168 | */ 169 | - (void)endRefreshingWithAlertText:(NSString *)text completion:(dispatch_block_t)completion Kafka_REQUIRES_SUPER; 170 | 171 | /** 172 | Using this method means you clearly understand that refreshing 173 | is meaningless and refreshing will be disabled. 174 | 175 | @param text If the user continues to drag, it will display the “text”, and will not trigger refresh. 176 | */ 177 | - (void)endRefreshingAndNoLongerRefreshingWithAlertText:(NSString *)text Kafka_REQUIRES_SUPER; 178 | 179 | /** 180 | After you call ‘endRefreshingAndNoLongerRefreshingWithAlertText’, 181 | you need to resume refresh available 182 | */ 183 | - (void)resumeRefreshAvailable; 184 | 185 | @end 186 | -------------------------------------------------------------------------------- /KafkaRefresh/Core/KafkaRefreshControl.m: -------------------------------------------------------------------------------- 1 | /************************************************************* 2 | * Copyright (c) 2016-present, K. * 3 | * All rights reserved. * 4 | * * 5 | * e-mail: xorshine@icloud.com * 6 | * github:https://github.com/xorshine * 7 | * * 8 | * This source code is licensed under the MIT license. * 9 | *************************************************************/ 10 | 11 | #import "KafkaRefreshControl.h" 12 | #import "KafkaCategories.h" 13 | 14 | @interface KafkaLabel : UILabel 15 | - (void)startAnimating; 16 | @end 17 | 18 | @implementation KafkaLabel{ 19 | CAGradientLayer * new; 20 | } 21 | 22 | - (instancetype)init{ 23 | self = [super init]; 24 | if (self) { 25 | new = [CAGradientLayer new]; 26 | new.locations = @[@0.2,@0.5,@0.8]; 27 | new.startPoint = CGPointMake(0, 0.5); 28 | new.endPoint = CGPointMake(1, 0.5); 29 | self.layer.masksToBounds = YES; 30 | [self.layer addSublayer:new]; 31 | } 32 | return self; 33 | } 34 | 35 | - (void)layoutSubviews{ 36 | new.frame = CGRectMake(0, 0, 0, self.kr_height); 37 | new.position = CGPointMake(self.kr_width/2.0, self.kr_height/2.); 38 | } 39 | 40 | - (void)startAnimating{ 41 | __weak typeof(self) weakSelf = self; 42 | [UIView animateWithDuration:0.3 animations:^{ 43 | weakSelf.alpha = 1.0; 44 | }]; 45 | new.colors = @[(id)[self.textColor colorWithAlphaComponent:0.2].CGColor, 46 | (id)[self.textColor colorWithAlphaComponent:0.1].CGColor, 47 | (id)[self.textColor colorWithAlphaComponent:0.2].CGColor]; 48 | 49 | CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"bounds.size.width"]; 50 | animation.fromValue = @(0); 51 | animation.toValue = @(self.kr_width); 52 | animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; 53 | animation.fillMode = kCAFillModeForwards; 54 | animation.duration = 0.3; 55 | animation.removedOnCompletion = NO; 56 | animation.delegate = self; 57 | [new addAnimation:animation forKey:animation.keyPath]; 58 | } 59 | 60 | - (void)stopAnimating{ 61 | __weak typeof(self) weakSelf = self; 62 | [UIView animateWithDuration:0.3 animations:^{ 63 | weakSelf.alpha = 0.0; 64 | }]; 65 | [new removeAllAnimations]; 66 | } 67 | 68 | @end 69 | 70 | static NSString * const KafkaContentOffset = @"contentOffset"; 71 | static NSString * const KafkaContentSize = @"contentSize"; 72 | static CGFloat const KafkaRefreshHeight = 45.; 73 | static CGFloat const kStretchOffsetYAxisThreshold = 1.0; 74 | 75 | @interface KafkaRefreshControl() 76 | @property (nonatomic, weak) __kindof UIScrollView *scrollView; 77 | @property (nonatomic, getter=isRefresh) BOOL refresh; 78 | @property (assign, nonatomic,getter=isObservering) BOOL observering; 79 | @property (strong, nonatomic) KafkaLabel *alertLabel; 80 | @property (assign, nonatomic, getter=isShouldNoLongerRefresh) BOOL shouldNoLongerRefresh; 81 | @end 82 | 83 | @implementation KafkaRefreshControl 84 | 85 | - (instancetype)initWithFrame:(CGRect)frame{ 86 | self = [super initWithFrame:frame]; 87 | if (self) { 88 | [self setupProperties]; 89 | } 90 | return self; 91 | } 92 | 93 | - (instancetype)initWithCoder:(NSCoder *)coder{ 94 | self = [super initWithCoder:coder]; 95 | if (self) { 96 | [self setupProperties]; 97 | } 98 | return self; 99 | } 100 | 101 | - (void)setupProperties{ 102 | self.backgroundColor = [UIColor clearColor]; 103 | self.alpha = 0.; 104 | [self addSubview:self.alertLabel]; 105 | _autoRefreshOnFoot = NO; 106 | _refreshState = KafkaRefreshStateNone; 107 | _stretchOffsetYAxisThreshold = kStretchOffsetYAxisThreshold; 108 | _shouldNoLongerRefresh = NO; 109 | _refresh = NO; 110 | if (CGRectEqualToRect(self.frame, CGRectZero)) self.frame = CGRectMake(0, 0, 1, 1); 111 | } 112 | 113 | - (void)setThemeColor:(UIColor *)themeColor{ 114 | if (_themeColor != themeColor) { 115 | _themeColor = themeColor; 116 | _alertLabel.textColor = themeColor; 117 | } 118 | } 119 | 120 | - (void)setAlertTextColor:(UIColor *)alertTextColor{ 121 | _alertLabel.textColor = alertTextColor; 122 | } 123 | 124 | - (void)setAlertBackgroundColor:(UIColor *)alertBackgroundColor{ 125 | _alertLabel.backgroundColor = alertBackgroundColor; 126 | } 127 | 128 | 129 | - (void)setRefreshState:(KafkaRefreshState)refreshState{ 130 | if (_refreshState == refreshState) return; 131 | _refreshState = refreshState; 132 | @weakify(self); 133 | switch (refreshState) { 134 | case KafkaRefreshStateNone:{ 135 | [self setAnimateBlock:^{@strongify(self); self.alpha = 0.0; }]; 136 | break; 137 | } 138 | case KafkaRefreshStateScrolling:{ 139 | //////////////////////////////////////////////////////////////////////////////////// 140 | ///when system adjust contentOffset atuomatically, 141 | ///will trigger refresh control's state changed. 142 | if (!self.isTriggeredRefreshByUser && !self.scrollView.isTracking) { 143 | return; 144 | } 145 | 146 | [self setAnimateBlock:^{@strongify(self); self.alpha = 1.0; }]; 147 | break; 148 | } 149 | case KafkaRefreshStateReady:{ 150 | //////////////////////////////////////////////////////////////////////////////////// 151 | ///because of scrollView contentOffset is not continuous change. 152 | ///need to manually adjust progress 153 | if (self.progress < self.stretchOffsetYAxisThreshold) { 154 | [self kafkaDidScrollWithProgress:self.stretchOffsetYAxisThreshold max:self.stretchOffsetYAxisThreshold]; 155 | } 156 | //////////////////////////////////////////////////////////////////////////////////// 157 | [self setAnimateBlock:^{@strongify(self); self.alpha = 1.0; }]; 158 | break; 159 | } 160 | case KafkaRefreshStateRefreshing:{ 161 | break; 162 | } 163 | case KafkaRefreshStateWillEndRefresh:{ 164 | [self setAnimateBlock:^{@strongify(self); self.alpha = 1.0; }]; 165 | break; 166 | } 167 | } 168 | if (self.shouldNoLongerRefresh) { 169 | self.alpha = 1.0; 170 | } 171 | [self kafkaRefreshStateDidChange:refreshState]; 172 | } 173 | 174 | - (void)setProgress:(CGFloat)progress{ 175 | if (_progress == progress) return; 176 | _progress = progress; 177 | [self kafkaDidScrollWithProgress:progress max:self.stretchOffsetYAxisThreshold]; 178 | } 179 | 180 | - (void)setStretchOffsetYAxisThreshold:(CGFloat)stretchOffsetYAxisThreshold{ 181 | if (_stretchOffsetYAxisThreshold != stretchOffsetYAxisThreshold && stretchOffsetYAxisThreshold > 1.0) { 182 | _stretchOffsetYAxisThreshold = stretchOffsetYAxisThreshold; 183 | } 184 | } 185 | 186 | - (BOOL)isRefresh{ 187 | return (_refreshState == KafkaRefreshStateRefreshing); 188 | } 189 | 190 | #pragma mark - layout 191 | 192 | - (void)layoutSubviews{ 193 | [super layoutSubviews]; 194 | 195 | self.kr_height = (self.kr_height < 45.) ? KafkaRefreshHeight : self.kr_height; 196 | self.frame = CGRectMake(0, 0, self.scrollView.kr_width, self.kr_height); 197 | self.alertLabel.frame = self.bounds; 198 | } 199 | 200 | - (void)willMoveToSuperview:(UIView *)newSuperview { 201 | NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld; 202 | if (self.superview && newSuperview == nil) { 203 | if (_observering) { 204 | [self.superview removeObserver:self forKeyPath:KafkaContentOffset]; 205 | [self.superview removeObserver:self forKeyPath:KafkaContentSize]; 206 | _observering = NO; 207 | } 208 | } 209 | else if (self.superview == nil && newSuperview) { 210 | if (!_observering) { 211 | _scrollView = (UIScrollView *)newSuperview; 212 | /** 213 | sometimes, this method called before `layoutSubviews`,such as UICollectionViewController 214 | */ 215 | [self layoutIfNeeded]; 216 | _presetContentInsets = ((UIScrollView *)newSuperview).realContentInset; 217 | [newSuperview addObserver:self forKeyPath:KafkaContentOffset options:options context:nil]; 218 | [newSuperview addObserver:self forKeyPath:KafkaContentSize options:options context:nil]; 219 | _observering = YES; 220 | } 221 | } 222 | } 223 | 224 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{ 225 | if ([keyPath isEqualToString:KafkaContentOffset]) { 226 | /** 227 | If you disable the control's refresh feature, set the control to hidden 228 | */ 229 | if (self.isHidden || self.shouldNoLongerRefresh) return; 230 | 231 | CGPoint point = [[change valueForKey:NSKeyValueChangeNewKey] CGPointValue]; 232 | /** 233 | If you quickly scroll scrollview in an instant, contentoffset changes are not continuous 234 | */ 235 | [self privateContentOffsetOfScrollViewDidChange:point]; 236 | } 237 | else if([keyPath isEqualToString:KafkaContentSize]) { 238 | [self layoutSubviews]; 239 | } 240 | } 241 | 242 | - (void)privateContentOffsetOfScrollViewDidChange:(CGPoint)contentOffset{} 243 | 244 | - (void)beginRefreshing{ 245 | if (self.refreshState != KafkaRefreshStateNone || self.isHidden || self.triggeredRefreshByUser) return; 246 | if (self.isShouldNoLongerRefresh) self.alertLabel.hidden = YES; 247 | self.shouldNoLongerRefresh = NO; 248 | self.triggeredRefreshByUser = YES; 249 | [self setScrollViewToRefreshLocation]; 250 | } 251 | 252 | - (void)setScrollViewToRefreshLocation{ 253 | self.animating = YES; 254 | } 255 | 256 | - (void)endRefreshing{ 257 | [self endRefreshingWithAlertText:nil completion:nil]; 258 | } 259 | 260 | - (void)endRefreshingWithAlertText:(NSString *)text completion:(dispatch_block_t)completion { 261 | if((!self.isRefresh && !self.isAnimating) || self.isHidden) return; 262 | if (text) { 263 | [self bringSubviewToFront:self.alertLabel]; 264 | self.alertLabel.text = text; 265 | [self.alertLabel startAnimating]; 266 | @weakify(self); 267 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 268 | @strongify(self); 269 | [self.alertLabel stopAnimating]; 270 | [self _endRefresh:completion]; 271 | }); 272 | } else { 273 | [self _endRefresh:nil]; 274 | } 275 | } 276 | 277 | - (void)endRefreshingAndNoLongerRefreshingWithAlertText:(NSString *)text{ 278 | if (self.isHidden) return; 279 | if (self.isShouldNoLongerRefresh) return; 280 | 281 | self.shouldNoLongerRefresh = YES; 282 | 283 | @weakify(self); 284 | if (self.alertLabel.alpha == 0.0) { 285 | [UIView animateWithDuration:0.3 animations:^{ 286 | @strongify(self); 287 | self.alertLabel.alpha = 1.0; 288 | }]; 289 | } 290 | [self bringSubviewToFront:self.alertLabel]; 291 | self.alertLabel.text = text; 292 | if (text) { 293 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 294 | @strongify(self); 295 | [self _endRefresh:nil]; 296 | }); 297 | } else { 298 | [self _endRefresh:nil]; 299 | } 300 | } 301 | 302 | - (void)resumeRefreshAvailable{ 303 | self.shouldNoLongerRefresh = NO; 304 | self.alertLabel.alpha = 0.0; 305 | } 306 | 307 | - (void)_endRefresh:(dispatch_block_t)completion{ 308 | [self kafkaRefreshStateDidChange:KafkaRefreshStateWillEndRefresh]; 309 | self.refreshState = KafkaRefreshStateScrolling; 310 | [self setScrollViewToOriginalLocation:completion]; 311 | } 312 | 313 | - (void)setScrollViewToOriginalLocation:(dispatch_block_t)completion{} 314 | 315 | #pragma mark - 316 | 317 | #pragma mark - progress callback 318 | 319 | - (void)kafkaRefreshStateDidChange:(KafkaRefreshState)state{} 320 | 321 | - (void)kafkaDidScrollWithProgress:(CGFloat)progress max:(const CGFloat)max{} 322 | 323 | #pragma mark - getter 324 | 325 | - (KafkaLabel *)alertLabel{ 326 | if (!_alertLabel) { 327 | _alertLabel = [KafkaLabel new]; 328 | _alertLabel.textAlignment = NSTextAlignmentCenter; 329 | _alertLabel.font = [UIFont fontWithName:@"Helvetica" size:15.f]; 330 | _alertLabel.textColor = _themeColor; 331 | _alertLabel.alpha = 0.0; 332 | _alertLabel.backgroundColor = [UIColor whiteColor]; 333 | } 334 | return _alertLabel; 335 | } 336 | 337 | @end 338 | -------------------------------------------------------------------------------- /KafkaRefresh/Core/KafkaRefreshProtocol.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * e-mail:xiaohuiprivate@gmail.com 6 | * github:https://github.com/xorshine 7 | * 8 | * This source code is licensed under the MIT license. 9 | */ 10 | 11 | #ifndef KafkaRefreshProtocol_h 12 | #define KafkaRefreshProtocol_h 13 | #import 14 | 15 | /** 16 | - KafkaRefreshStateNone: Refresh control is not visible, and non-refresh state 17 | - KafkaRefreshStateScrolling: Refresh control visible, UIScrollView sliding 18 | - KafkaRefreshStateReady: release your hand to refresh 19 | - KafkaRefreshStateRefreshing: refreshing 20 | - KafkaRefreshStateWillEndRefresh: the end of the refresh will begin to slide to the original position 21 | */ 22 | typedef NS_ENUM(NSInteger,KafkaRefreshState) { 23 | KafkaRefreshStateNone = 1, 24 | KafkaRefreshStateScrolling, 25 | KafkaRefreshStateReady, 26 | KafkaRefreshStateRefreshing, 27 | KafkaRefreshStateWillEndRefresh 28 | }; 29 | 30 | @protocol KafkaRefreshControlProtocol 31 | 32 | @end 33 | 34 | 35 | @protocol KafkaRefreshProgressDelegate 36 | @required 37 | 38 | /** 39 | * when the state of the refresh control changes, the method is called 40 | * 41 | * @state KafkaRefreshState 42 | */ 43 | - (void)kafkaRefreshStateDidChange:(KafkaRefreshState)state; 44 | 45 | /** 46 | * when the state of the refresh control changes, the method is called 47 | * 48 | * @progress the current position offset of the control as a percentage of the offset that triggered the refresh 49 | * 50 | * @max the offset that triggered the refresh 51 | */ 52 | - (void)kafkaDidScrollWithProgress:(CGFloat)progress max:(const CGFloat)max; 53 | 54 | @end 55 | 56 | #endif /* KafkaRefreshProtocol_h */ 57 | -------------------------------------------------------------------------------- /KafkaRefresh/Core/UIScrollView+KafkaRefreshControl.h: -------------------------------------------------------------------------------- 1 | /************************************************************* 2 | * Copyright (c) 2016-present, K. * 3 | * All rights reserved. * 4 | * * 5 | * e-mail: xorshine@icloud.com * 6 | * github:https://github.com/xorshine * 7 | * * 8 | * This source code is licensed under the MIT license. * 9 | *************************************************************/ 10 | 11 | #import 12 | 13 | @class KafkaHeadRefreshControl,KafkaFootRefreshControl; 14 | 15 | @interface UIScrollView (KafkaRefreshControl) 16 | 17 | /** 18 | * Bind the UIScrollView to the refresh control, dynamic binding at runtime 19 | */ 20 | @property (strong, nonatomic) __kindof KafkaHeadRefreshControl *headRefreshControl; 21 | @property (strong, nonatomic) __kindof KafkaFootRefreshControl *footRefreshControl; 22 | 23 | @end 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /KafkaRefresh/Core/UIScrollView+KafkaRefreshControl.m: -------------------------------------------------------------------------------- 1 | /************************************************************* 2 | * Copyright (c) 2016-present, K. * 3 | * All rights reserved. * 4 | * * 5 | * e-mail: xorshine@icloud.com * 6 | * github:https://github.com/xorshine * 7 | * * 8 | * This source code is licensed under the MIT license. * 9 | *************************************************************/ 10 | 11 | #import "UIScrollView+KafkaRefreshControl.h" 12 | #import 13 | #import "KafkaHeadRefreshControl.h" 14 | #import "KafkaFootRefreshControl.h" 15 | 16 | #define KafkaSelfSetValueRetainNonatomicAndKVOChange(keyPath,key,value) \ 17 | [self willChangeValueForKey:(keyPath)]; \ 18 | objc_setAssociatedObject(self,(key),(value),OBJC_ASSOCIATION_RETAIN_NONATOMIC); \ 19 | [self addSubview:value]; \ 20 | [self didChangeValueForKey:(keyPath)]; 21 | 22 | const void * KafkaHeadRefreshKey = &KafkaHeadRefreshKey; 23 | const void * KafkaFootRefreshKey = &KafkaFootRefreshKey; 24 | 25 | NSString * KafkaHeadKeyPath = @"KafkaHeadKeyPath"; 26 | NSString * KafkaFootKeyPath = @"KafkaHeadKeyPath"; 27 | 28 | @implementation UIScrollView (KafkaRefreshControl) 29 | 30 | #pragma mark - setter and getter 31 | 32 | - (void)setHeadRefreshControl:(__kindof KafkaHeadRefreshControl *)headRefreshControl{ 33 | if (headRefreshControl != self.headRefreshControl) { 34 | if (self.headRefreshControl) [self.headRefreshControl removeFromSuperview]; 35 | KafkaSelfSetValueRetainNonatomicAndKVOChange(KafkaHeadKeyPath, KafkaHeadRefreshKey, headRefreshControl) 36 | } 37 | } 38 | 39 | - (KafkaHeadRefreshControl *)headRefreshControl{ 40 | return objc_getAssociatedObject(self, KafkaHeadRefreshKey); 41 | } 42 | 43 | - (void)setFootRefreshControl:(__kindof KafkaFootRefreshControl *)footRefreshControl{ 44 | if (footRefreshControl != self.footRefreshControl) { 45 | if (self.footRefreshControl) [self.footRefreshControl removeFromSuperview]; 46 | KafkaSelfSetValueRetainNonatomicAndKVOChange(KafkaFootKeyPath, KafkaFootRefreshKey, footRefreshControl); 47 | } 48 | } 49 | 50 | - (KafkaFootRefreshControl *)footRefreshControl{ 51 | return objc_getAssociatedObject(self, KafkaFootRefreshKey); 52 | } 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /KafkaRefresh/Default/KafkaRefreshDefaults.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * Email:xorshine@icloud.com 6 | * 7 | * This source code is licensed under the MIT license. 8 | */ 9 | 10 | #import 11 | #import "KafkaRefreshStyle.h" 12 | 13 | /** 14 | Global configuration class 15 | */ 16 | @interface KafkaRefreshDefaults : NSObject 17 | 18 | /** 19 | Return a singleton 20 | */ 21 | + (instancetype)standardRefreshDefaults; 22 | 23 | /** 24 | Head refresh control style 25 | */ 26 | @property (assign, nonatomic) KafkaRefreshStyle headDefaultStyle; 27 | 28 | /** 29 | Foot refresh control style 30 | */ 31 | @property (assign, nonatomic) KafkaRefreshStyle footDefaultStyle; 32 | 33 | /** 34 | default R G B --> 28. 164. 252. 35 | */ 36 | @property (strong, nonatomic) UIColor * themeColor; 37 | 38 | /** 39 | refresh control‘s backgroundColor 40 | */ 41 | @property (strong, nonatomic) UIColor * backgroundColor; 42 | 43 | /** 44 | Head drag to display the text 45 | */ 46 | @property (copy, nonatomic) NSString * headPullingText; 47 | 48 | /** 49 | Foot drag to display the text 50 | */ 51 | @property (copy, nonatomic) NSString * footPullingText; 52 | 53 | /** 54 | The text displayed when you release your finger before releasing it 55 | */ 56 | @property (copy, nonatomic) NSString * readyText; 57 | 58 | /** 59 | Text displayed on refresh 60 | */ 61 | @property (copy, nonatomic) NSString * refreshingText; 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /KafkaRefresh/Default/KafkaRefreshDefaults.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * Email:xorshine@icloud.com 6 | * 7 | * This source code is licensed under the MIT license. 8 | */ 9 | 10 | #import "KafkaRefreshDefaults.h" 11 | 12 | #define KafkaColorWithRGBA(r,g,b,a) \ 13 | [UIColor colorWithRed:(r)/255. green:(g)/255. blue:(b)/255. alpha:(a)] 14 | 15 | @implementation KafkaRefreshDefaults 16 | 17 | + (instancetype)standardRefreshDefaults{ 18 | static KafkaRefreshDefaults *defaults = nil; 19 | static dispatch_once_t onceToken; 20 | dispatch_once(&onceToken, ^{ 21 | defaults = [[KafkaRefreshDefaults alloc] init]; 22 | }); 23 | return defaults; 24 | } 25 | 26 | - (instancetype)init{ 27 | if (self = [super init]) { 28 | _headDefaultStyle = KafkaRefreshStyleAnimatableArrow; 29 | _footDefaultStyle = KafkaRefreshStyleNative; 30 | 31 | _themeColor = KafkaColorWithRGBA(28., 164., 252., 1.0); 32 | _backgroundColor = [UIColor whiteColor]; 33 | 34 | _headPullingText = @"继续下拉"; 35 | _footPullingText = @"继续上拉"; 36 | _readyText = @"松开刷新"; 37 | _refreshingText = @"正在加载"; 38 | } 39 | return self; 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /KafkaRefresh/KafkaRefresh.h: -------------------------------------------------------------------------------- 1 | /************************************************************* 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * e-mail: xorshine@icloud.com 6 | * github:https://github.com/xorshine 7 | * 8 | * This source code is licensed under the MIT license. 9 | *************************************************************/ 10 | 11 | #if __has_include() 12 | #import 13 | #import 14 | #import 15 | #import 16 | #import 17 | #import 18 | #import 19 | #else 20 | #import "UIScrollView+KafkaRefreshControl.h" 21 | #import "UIScrollView+KafkaConfiguration.h" 22 | #import "KafkaHeadRefreshControl.h" 23 | #import "KafkaFootRefreshControl.h" 24 | #import "KafkaRefreshDefaults.h" 25 | #import "KafkaRefreshProtocol.h" 26 | #import "KafkaRefreshStyle.h" 27 | #endif 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /KafkaRefresh/Resource/Image.bundle/arrow48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BeatsKitano/KafkaRefresh/7d3bb0a5ce8d6ab11e976398cff8e52332f7ff63/KafkaRefresh/Resource/Image.bundle/arrow48.png -------------------------------------------------------------------------------- /KafkaRefresh/Style/KafkaRefreshStyle.h: -------------------------------------------------------------------------------- 1 | /************************************************************* 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * e-mail: xorshine@icloud.com 6 | * github:https://github.com/xorshine 7 | * 8 | * This source code is licensed under the MIT license. 9 | *************************************************************/ 10 | 11 | 12 | typedef NS_ENUM(NSInteger,KafkaRefreshStyle) { 13 | //Native 14 | KafkaRefreshStyleNative = 0, 15 | 16 | //Replicator 17 | KafkaRefreshStyleReplicatorWoody, 18 | KafkaRefreshStyleReplicatorAllen, 19 | KafkaRefreshStyleReplicatorCircle, 20 | KafkaRefreshStyleReplicatorDot, 21 | KafkaRefreshStyleReplicatorArc, 22 | KafkaRefreshStyleReplicatorTriangle, 23 | 24 | //Ring 25 | KafkaRefreshStyleAnimatableRing, 26 | KafkaRefreshStyleAnimatableArrow 27 | }; 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /KafkaRefresh/UIKit/FootKit/KafkaArrowFooter.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * e-mail:xorshine@icloud.com 6 | * github:https://github.com/xorshine 7 | * 8 | * This source code is licensed under the MIT license. 9 | */ 10 | 11 | #import "KafkaFootRefreshControl.h" 12 | 13 | @interface KafkaArrowFooter : KafkaFootRefreshControl 14 | 15 | /** 16 | Foot drag to display the text 17 | */ 18 | @property (copy, nonatomic) NSString * pullingText; 19 | 20 | /** 21 | The text displayed when you release your finger before releasing it 22 | */ 23 | @property (copy, nonatomic) NSString * readyText; 24 | 25 | /** 26 | Text displayed on refresh 27 | */ 28 | @property (copy, nonatomic) NSString * refreshingText; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /KafkaRefresh/UIKit/FootKit/KafkaArrowFooter.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * e-mail:xorshine@icloud.com 6 | * github:https://github.com/xorshine 7 | * 8 | * This source code is licensed under the MIT license. 9 | */ 10 | 11 | #import "KafkaArrowFooter.h" 12 | #import "KafkaRefreshDefaults.h" 13 | #import "KafkaCategories.h" 14 | 15 | @interface KafkaArrowFooter() 16 | 17 | @property (strong, nonatomic) UIImageView * arrowImgV; 18 | @property (strong, nonatomic) UILabel * promptlabel; 19 | @property (strong, nonatomic) UIActivityIndicatorView * indicator; 20 | 21 | @end 22 | 23 | @implementation KafkaArrowFooter 24 | 25 | - (void)setupProperties{ 26 | [super setupProperties]; 27 | [self addSubview:self.arrowImgV]; 28 | [self addSubview:self.promptlabel]; 29 | [self addSubview:self.indicator]; 30 | 31 | _pullingText = [KafkaRefreshDefaults standardRefreshDefaults].footPullingText; 32 | _readyText = [KafkaRefreshDefaults standardRefreshDefaults].readyText; 33 | _refreshingText = [KafkaRefreshDefaults standardRefreshDefaults].refreshingText; 34 | } 35 | 36 | - (void)layoutSubviews{ 37 | [super layoutSubviews]; 38 | 39 | [self.promptlabel sizeToFit]; 40 | self.promptlabel.center = CGPointMake(self.kr_width/2.0, self.kr_height/2.); 41 | 42 | self.arrowImgV.frame = CGRectMake(0, 0, 12, 12); 43 | self.arrowImgV.kr_right = self.promptlabel.kr_left-20.; 44 | self.arrowImgV.kr_top = self.promptlabel.kr_centerY; 45 | 46 | self.indicator.center = self.arrowImgV.center; 47 | } 48 | 49 | - (void)kafkaDidScrollWithProgress:(CGFloat)progress max:(const CGFloat)max{ 50 | 51 | 52 | } 53 | 54 | - (void)kafkaRefreshStateDidChange:(KafkaRefreshState)state{ 55 | [super kafkaRefreshStateDidChange:state]; 56 | __weak typeof(self) weakSelf = self; 57 | switch (state) { 58 | case KafkaRefreshStateNone:{ 59 | self.arrowImgV.hidden = NO; 60 | [_indicator stopAnimating]; 61 | [UIView animateWithDuration:0.3 animations:^{ 62 | weakSelf.arrowImgV.transform = CGAffineTransformMakeRotation(M_PI); 63 | }]; 64 | break; 65 | } 66 | case KafkaRefreshStateScrolling:{ 67 | [_indicator stopAnimating]; 68 | self.promptlabel.text = _pullingText; 69 | [self.promptlabel sizeToFit]; 70 | __weak typeof(self) weakSelf = self; 71 | [UIView animateWithDuration:0.3 animations:^{ 72 | weakSelf.arrowImgV.transform = CGAffineTransformMakeRotation(M_PI); 73 | }]; 74 | break; 75 | } 76 | case KafkaRefreshStateReady:{ 77 | [_indicator stopAnimating]; 78 | self.promptlabel.text = _readyText; 79 | [UIView animateWithDuration:0.3 animations:^{ 80 | weakSelf.arrowImgV.transform = CGAffineTransformMakeRotation(M_PI*2); 81 | }]; 82 | break; 83 | } 84 | case KafkaRefreshStateRefreshing:{ 85 | self.promptlabel.text = _refreshingText; 86 | self.arrowImgV.hidden = YES; 87 | [_indicator startAnimating]; 88 | break; 89 | } 90 | case KafkaRefreshStateWillEndRefresh:{ 91 | [_indicator stopAnimating]; 92 | break; 93 | } 94 | } 95 | } 96 | 97 | #pragma mark - getter 98 | 99 | - (UIImageView *)arrowImgV{ 100 | if (!_arrowImgV) { 101 | NSBundle *b = [NSBundle bundleWithPath:[[NSBundle bundleForClass:[self class]] pathForResource:@"Image" ofType:@"bundle"]]; 102 | NSString *path = [b pathForResource:@"arrow48.png" ofType:nil]; 103 | UIImage *image = [UIImage imageWithContentsOfFile:path]; 104 | _arrowImgV = [[UIImageView alloc] initWithImage:image]; 105 | _arrowImgV.layer.anchorPoint = CGPointMake(0.5, 0); 106 | } 107 | return _arrowImgV; 108 | } 109 | 110 | - (UILabel *)promptlabel{ 111 | if (!_promptlabel) { 112 | _promptlabel = [UILabel new]; 113 | _promptlabel.textAlignment = NSTextAlignmentCenter; 114 | _promptlabel.textColor = KafkaColorWithRGBA(100.,100.,100.,1.0); 115 | if (@available(iOS 8.2, *)) 116 | _promptlabel.font = [UIFont systemFontOfSize:11. weight:UIFontWeightThin]; 117 | else 118 | _promptlabel.font = [UIFont systemFontOfSize:11.]; 119 | } 120 | return _promptlabel; 121 | } 122 | 123 | - (UIActivityIndicatorView *)indicator{ 124 | if (!_indicator) { 125 | _indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; 126 | } 127 | return _indicator; 128 | } 129 | 130 | @end 131 | -------------------------------------------------------------------------------- /KafkaRefresh/UIKit/FootKit/KafkaNativeFooter.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * Email:xorshine@icloud.com 6 | * 7 | * This source code is licensed under the MIT license. 8 | */ 9 | 10 | #import "KafkaFootRefreshControl.h" 11 | 12 | @interface KafkaNativeFooter : KafkaFootRefreshControl 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /KafkaRefresh/UIKit/FootKit/KafkaNativeFooter.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * Email:xorshine@icloud.com 6 | * 7 | * This source code is licensed under the MIT license. 8 | */ 9 | 10 | #import "KafkaNativeFooter.h" 11 | #import "KafkaCategories.h" 12 | 13 | @interface KafkaNativeFooter() 14 | 15 | @property (strong, nonatomic) UIActivityIndicatorView * indicator; 16 | 17 | @end 18 | 19 | @implementation KafkaNativeFooter 20 | 21 | - (instancetype)initWithFrame:(CGRect)frame{ 22 | self = [super initWithFrame:frame]; 23 | if (self) { 24 | [self addSubview:self.indicator]; 25 | } 26 | return self; 27 | } 28 | 29 | - (void)layoutSubviews{ 30 | [super layoutSubviews]; 31 | 32 | self.indicator.center = CGPointMake(self.kr_width/2., self.kr_height/2.); 33 | } 34 | 35 | - (void)kafkaDidScrollWithProgress:(CGFloat)progress max:(const CGFloat)max{ 36 | 37 | } 38 | 39 | - (void)kafkaRefreshStateDidChange:(KafkaRefreshState)state{ 40 | [super kafkaRefreshStateDidChange:state]; 41 | switch (state) { 42 | case KafkaRefreshStateNone:{ 43 | break; 44 | } 45 | case KafkaRefreshStateScrolling:{ 46 | break; 47 | } 48 | case KafkaRefreshStateReady:{ 49 | break; 50 | } 51 | case KafkaRefreshStateRefreshing:{ 52 | [self.indicator startAnimating]; 53 | break; 54 | } 55 | case KafkaRefreshStateWillEndRefresh:{ 56 | [self.indicator stopAnimating]; 57 | break; 58 | } 59 | } 60 | } 61 | 62 | #pragma mark - lazy 63 | 64 | - (UIActivityIndicatorView *)indicator{ 65 | if (!_indicator) { 66 | if (@available(iOS 13.0, *)) { 67 | _indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleMedium]; 68 | } else { 69 | _indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; 70 | } 71 | _indicator.hidesWhenStopped = NO; 72 | } 73 | return _indicator; 74 | } 75 | 76 | @end 77 | -------------------------------------------------------------------------------- /KafkaRefresh/UIKit/FootKit/KafkaReplicatorFooter.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * e-mail:xorshine@icloud.com 6 | * github:https://github.com/xorshine 7 | * 8 | * This source code is licensed under the MIT license. 9 | */ 10 | 11 | #import "KafkaFootRefreshControl.h" 12 | #import "KafkaReplicatorLayer.h" 13 | 14 | @interface KafkaReplicatorFooter : KafkaFootRefreshControl 15 | 16 | @property (strong, nonatomic) KafkaReplicatorLayer * replicatorLayer; 17 | @property (assign, nonatomic) KafkaReplicatorLayerAnimationStyle animationStyle; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /KafkaRefresh/UIKit/FootKit/KafkaReplicatorFooter.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * e-mail:xorshine@icloud.com 6 | * github:https://github.com/xorshine 7 | * 8 | * This source code is licensed under the MIT license. 9 | */ 10 | 11 | #import "KafkaReplicatorFooter.h" 12 | #import "KafkaCategories.h" 13 | 14 | @implementation KafkaReplicatorFooter 15 | 16 | - (void)setupProperties{ 17 | [super setupProperties]; 18 | [self.layer addSublayer:self.replicatorLayer]; 19 | } 20 | 21 | - (void)layoutSubviews{ 22 | [super layoutSubviews]; 23 | self.replicatorLayer.frame = CGRectMake(0, 0, self.kr_width, self.kr_height); 24 | } 25 | 26 | - (void)setThemeColor:(UIColor *)themeColor{ 27 | [super setThemeColor:themeColor]; 28 | self.replicatorLayer.themeColor = themeColor; 29 | } 30 | 31 | - (void)setAnimationStyle:(KafkaReplicatorLayerAnimationStyle)animationStyle{ 32 | _animationStyle = animationStyle; 33 | self.replicatorLayer.animationStyle = animationStyle; 34 | } 35 | 36 | - (void)kafkaDidScrollWithProgress:(CGFloat)progress max:(const CGFloat)max{ 37 | #define kOffset 0.7 38 | if (progress >= 0.8) { 39 | progress = (progress-kOffset)/(max - kOffset); 40 | } 41 | switch (self.animationStyle) { 42 | case KafkaReplicatorLayerAnimationStyleWoody:{ 43 | break; 44 | } 45 | case KafkaReplicatorLayerAnimationStyleAllen:{ 46 | 47 | break; 48 | } 49 | case KafkaReplicatorLayerAnimationStyleCircle:{ 50 | 51 | break; 52 | } 53 | case KafkaReplicatorLayerAnimationStyleDot:{ 54 | 55 | break; 56 | } 57 | case KafkaReplicatorLayerAnimationStyleArc:{ 58 | self.replicatorLayer.indicatorShapeLayer.strokeEnd = progress; 59 | break; 60 | } 61 | case KafkaReplicatorLayerAnimationStyleTriangle:{ 62 | 63 | break; 64 | } 65 | } 66 | } 67 | 68 | - (void)kafkaRefreshStateDidChange:(KafkaRefreshState)state{ 69 | [super kafkaRefreshStateDidChange:state]; 70 | switch (state) { 71 | case KafkaRefreshStateNone:{ 72 | break; 73 | } 74 | case KafkaRefreshStateScrolling:{ 75 | break; 76 | } 77 | case KafkaRefreshStateReady:{ 78 | self.replicatorLayer.opacity = 1.; 79 | break; 80 | } 81 | case KafkaRefreshStateRefreshing:{ 82 | [self.replicatorLayer startAnimating]; 83 | break; 84 | } 85 | case KafkaRefreshStateWillEndRefresh:{ 86 | [self.replicatorLayer stopAnimating]; 87 | break; 88 | } 89 | } 90 | } 91 | 92 | - (KafkaReplicatorLayer *)replicatorLayer{ 93 | if (!_replicatorLayer) { 94 | _replicatorLayer = [KafkaReplicatorLayer layer]; 95 | } 96 | return _replicatorLayer; 97 | } 98 | 99 | @end 100 | -------------------------------------------------------------------------------- /KafkaRefresh/UIKit/FootKit/KafkaRingIndicatorFooter.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * e-mail:xorshine@icloud.com 6 | * github:https://github.com/xorshine 7 | * 8 | * This source code is licensed under the MIT license. 9 | */ 10 | 11 | #import "KafkaFootRefreshControl.h" 12 | #import "KafkaArcLayer.h" 13 | 14 | @interface KafkaRingIndicatorFooter : KafkaFootRefreshControl 15 | 16 | @property (strong, nonatomic) KafkaArcLayer * arcLayer; 17 | @property (strong, nonatomic) UIActivityIndicatorView * indicator; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /KafkaRefresh/UIKit/FootKit/KafkaRingIndicatorFooter.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * e-mail:xorshine@icloud.com 6 | * github:https://github.com/xorshine 7 | * 8 | * This source code is licensed under the MIT license. 9 | */ 10 | 11 | #import "KafkaRingIndicatorFooter.h" 12 | #import "KafkaCategories.h" 13 | 14 | @implementation KafkaRingIndicatorFooter 15 | 16 | - (void)setupProperties{ 17 | [super setupProperties]; 18 | [self.layer addSublayer:self.arcLayer]; 19 | [self addSubview:self.indicator]; 20 | } 21 | 22 | - (void)layoutSubviews{ 23 | [super layoutSubviews]; 24 | self.arcLayer.frame = CGRectMake(0, 0, self.kr_width, self.kr_height); 25 | self.indicator.center = CGPointMake(self.kr_width/2., self.kr_height/2.); 26 | } 27 | 28 | - (void)setThemeColor:(UIColor *)fillColor{ 29 | if (super.themeColor == fillColor) { 30 | return; 31 | } 32 | [super setThemeColor:fillColor]; 33 | self.arcLayer.ringFillColor = fillColor; 34 | } 35 | 36 | - (void)setAnimatedBackgroundColor:(UIColor *)animatedBackgroundColor{ 37 | if (super.animatedBackgroundColor == animatedBackgroundColor) { 38 | return; 39 | } 40 | [super setAnimatedBackgroundColor:animatedBackgroundColor]; 41 | self.arcLayer.ringBackgroundColor = animatedBackgroundColor; 42 | } 43 | 44 | - (void)kafkaDidScrollWithProgress:(CGFloat)progress max:(const CGFloat)max{ 45 | #define kOffset 0.3 46 | if (progress >= kOffset) { 47 | progress = (progress-kOffset)/(max - kOffset); 48 | [self.arcLayer setProgress:progress]; 49 | } 50 | } 51 | 52 | - (void)kafkaRefreshStateDidChange:(KafkaRefreshState)state{ 53 | [super kafkaRefreshStateDidChange:state]; 54 | switch (state) { 55 | case KafkaRefreshStateNone:{ 56 | break; 57 | } 58 | case KafkaRefreshStateScrolling:{ 59 | break; 60 | } 61 | case KafkaRefreshStateReady:{ 62 | self.arcLayer.opacity = 1.; 63 | break; 64 | } 65 | case KafkaRefreshStateRefreshing:{ 66 | [self.arcLayer startAnimating]; 67 | self.arcLayer.hidden = YES; 68 | [self.indicator startAnimating]; 69 | break; 70 | } 71 | case KafkaRefreshStateWillEndRefresh:{ 72 | [self.indicator stopAnimating]; 73 | self.arcLayer.hidden = NO; 74 | [self.arcLayer stopAnimating]; 75 | break; 76 | } 77 | } 78 | } 79 | 80 | - (KafkaArcLayer *)arcLayer{ 81 | if (!_arcLayer) { 82 | _arcLayer = [KafkaArcLayer layer]; 83 | } 84 | return _arcLayer; 85 | } 86 | 87 | - (UIActivityIndicatorView *)indicator{ 88 | if (!_indicator) { 89 | _indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; 90 | _indicator.hidesWhenStopped = YES; 91 | } 92 | return _indicator; 93 | } 94 | 95 | @end 96 | -------------------------------------------------------------------------------- /KafkaRefresh/UIKit/HeadKit/KafkaArrowHeader.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * e-mail:xorshine@icloud.com 6 | * github:https://github.com/xorshine 7 | * 8 | * This source code is licensed under the MIT license. 9 | */ 10 | 11 | #import "KafkaHeadRefreshControl.h" 12 | 13 | @interface KafkaArrowHeader : KafkaHeadRefreshControl 14 | 15 | /** 16 | Foot drag to display the text 17 | */ 18 | @property (copy, nonatomic) NSString * pullingText; 19 | 20 | /** 21 | The text displayed when you release your finger before releasing it 22 | */ 23 | @property (copy, nonatomic) NSString * readyText; 24 | 25 | /** 26 | Text displayed on refresh 27 | */ 28 | @property (copy, nonatomic) NSString * refreshingText; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /KafkaRefresh/UIKit/HeadKit/KafkaArrowHeader.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * e-mail:xorshine@icloud.com 6 | * github:https://github.com/xorshine 7 | * 8 | * This source code is licensed under the MIT license. 9 | */ 10 | 11 | #import "KafkaArrowHeader.h" 12 | #import "KafkaRefreshDefaults.h" 13 | #import "KafkaCategories.h" 14 | 15 | @interface KafkaArrowHeader() 16 | 17 | @property (strong, nonatomic) UIImageView * arrowImgV; 18 | @property (strong, nonatomic) UILabel * promptlabel; 19 | @property (strong, nonatomic) UIActivityIndicatorView * indicator; 20 | 21 | @end 22 | 23 | @implementation KafkaArrowHeader 24 | 25 | - (void)setupProperties{ 26 | [super setupProperties]; 27 | [self addSubview:self.arrowImgV]; 28 | [self addSubview:self.promptlabel]; 29 | [self addSubview:self.indicator]; 30 | 31 | _pullingText = [KafkaRefreshDefaults standardRefreshDefaults].headPullingText; 32 | _readyText = [KafkaRefreshDefaults standardRefreshDefaults].readyText; 33 | _refreshingText = [KafkaRefreshDefaults standardRefreshDefaults].refreshingText; 34 | } 35 | 36 | - (void)layoutSubviews{ 37 | [super layoutSubviews]; 38 | 39 | self.promptlabel.center = CGPointMake(self.kr_width/2.0, self.kr_height/2.); 40 | 41 | self.arrowImgV.frame = CGRectMake(0, 0, 12, 12); 42 | self.arrowImgV.kr_right = self.promptlabel.kr_left-20.; 43 | self.arrowImgV.kr_top = self.promptlabel.kr_centerY; 44 | 45 | self.indicator.center = self.arrowImgV.center; 46 | } 47 | 48 | - (void)kafkaDidScrollWithProgress:(CGFloat)progress max:(const CGFloat)max{ 49 | 50 | } 51 | 52 | - (void)kafkaRefreshStateDidChange:(KafkaRefreshState)state{ 53 | [super kafkaRefreshStateDidChange:state]; 54 | __weak typeof(self) weakSelf = self; 55 | switch (state) { 56 | case KafkaRefreshStateNone:{ 57 | self.arrowImgV.hidden = NO; 58 | [_indicator stopAnimating]; 59 | [UIView animateWithDuration:0.3 animations:^{ 60 | weakSelf.arrowImgV.transform = CGAffineTransformIdentity; 61 | }]; 62 | break; 63 | } 64 | case KafkaRefreshStateScrolling:{ 65 | self.promptlabel.text = _pullingText; 66 | [self.promptlabel sizeToFit]; 67 | [UIView animateWithDuration:0.3 animations:^{ 68 | weakSelf.arrowImgV.transform = CGAffineTransformIdentity; 69 | }]; 70 | break; 71 | } 72 | case KafkaRefreshStateReady:{ 73 | [_indicator stopAnimating]; 74 | self.promptlabel.text = _readyText; 75 | [UIView animateWithDuration:0.3 animations:^{ 76 | weakSelf.arrowImgV.transform = CGAffineTransformMakeRotation(M_PI); 77 | }]; 78 | break; 79 | } 80 | case KafkaRefreshStateRefreshing:{ 81 | self.promptlabel.text = _refreshingText; 82 | self.arrowImgV.hidden = YES; 83 | [_indicator startAnimating]; 84 | break; 85 | } 86 | case KafkaRefreshStateWillEndRefresh:{ 87 | [_indicator stopAnimating]; 88 | break; 89 | } 90 | } 91 | } 92 | 93 | #pragma mark - getter 94 | 95 | - (UIImageView *)arrowImgV{ 96 | if (!_arrowImgV) { 97 | NSBundle *b = [NSBundle bundleWithPath:[[NSBundle bundleForClass:[self class]] pathForResource:@"Image" ofType:@"bundle"]]; 98 | NSString *path = [b pathForResource:@"arrow48.png" ofType:nil]; 99 | UIImage *image = [UIImage imageWithContentsOfFile:path]; 100 | _arrowImgV = [[UIImageView alloc] initWithImage:image]; 101 | _arrowImgV.layer.anchorPoint = CGPointMake(0.5, 0); 102 | } 103 | return _arrowImgV; 104 | } 105 | 106 | - (UILabel *)promptlabel{ 107 | if (!_promptlabel) { 108 | _promptlabel = [[UILabel alloc] init]; 109 | _promptlabel.textAlignment = NSTextAlignmentCenter; 110 | _promptlabel.textColor = KafkaColorWithRGBA(100.,100.,100.,1.0); 111 | if (@available(iOS 8.2, *)) 112 | _promptlabel.font = [UIFont systemFontOfSize:11. weight:UIFontWeightThin]; 113 | else 114 | _promptlabel.font = [UIFont systemFontOfSize:11.]; 115 | } 116 | return _promptlabel; 117 | } 118 | 119 | - (UIActivityIndicatorView *)indicator{ 120 | if (!_indicator) { 121 | _indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; 122 | } 123 | return _indicator; 124 | } 125 | 126 | @end 127 | -------------------------------------------------------------------------------- /KafkaRefresh/UIKit/HeadKit/KafkaNativeHeader.h: -------------------------------------------------------------------------------- 1 | /************************************************************* 2 | * Copyright (c) 2016-present, K. * 3 | * All rights reserved. * 4 | * * 5 | * e-mail: xorshine@icloud.com * 6 | * github:https://github.com/xorshine * 7 | * * 8 | * This source code is licensed under the MIT license. * 9 | *************************************************************/ 10 | 11 | 12 | #import "KafkaHeadRefreshControl.h" 13 | 14 | @interface KafkaNativeHeader : KafkaHeadRefreshControl 15 | @property (strong, nonatomic) UIActivityIndicatorView * indicator; 16 | @end 17 | -------------------------------------------------------------------------------- /KafkaRefresh/UIKit/HeadKit/KafkaNativeHeader.m: -------------------------------------------------------------------------------- 1 | /************************************************************* 2 | * Copyright (c) 2016-present, K. * 3 | * All rights reserved. * 4 | * * 5 | * e-mail: xorshine@icloud.com * 6 | * github:https://github.com/xorshine * 7 | * * 8 | * This source code is licensed under the MIT license. * 9 | *************************************************************/ 10 | 11 | #import "KafkaNativeHeader.h" 12 | #import "KafkaCategories.h" 13 | 14 | @interface KafkaNativeHeader() 15 | 16 | 17 | @end 18 | 19 | @implementation KafkaNativeHeader 20 | 21 | - (void)setupProperties{ 22 | [super setupProperties]; 23 | [self addSubview:self.indicator]; 24 | } 25 | 26 | - (void)layoutSubviews{ 27 | [super layoutSubviews]; 28 | 29 | self.indicator.center = CGPointMake(self.kr_width/2., self.kr_height/2.); 30 | } 31 | 32 | - (void)kafkaDidScrollWithProgress:(CGFloat)progress max:(const CGFloat)max{ 33 | 34 | } 35 | 36 | - (void)kafkaRefreshStateDidChange:(KafkaRefreshState)state{ 37 | [super kafkaRefreshStateDidChange:state]; 38 | switch (state) { 39 | case KafkaRefreshStateNone: 40 | case KafkaRefreshStateScrolling: 41 | case KafkaRefreshStateReady: 42 | break; 43 | case KafkaRefreshStateRefreshing:{ 44 | [self.indicator startAnimating]; 45 | break; 46 | } 47 | case KafkaRefreshStateWillEndRefresh:{ 48 | [self.indicator stopAnimating]; 49 | break; 50 | } 51 | } 52 | } 53 | 54 | #pragma mark - getter 55 | 56 | - (UIActivityIndicatorView *)indicator{ 57 | if (!_indicator) { 58 | if (@available(iOS 13.0, *)) { 59 | _indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleMedium]; 60 | } else { 61 | _indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; 62 | } 63 | 64 | _indicator.hidesWhenStopped = NO; 65 | } 66 | return _indicator; 67 | } 68 | 69 | @end 70 | -------------------------------------------------------------------------------- /KafkaRefresh/UIKit/HeadKit/KafkaReplicatorHeader.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * e-mail:xorshine@icloud.com 6 | * github:https://github.com/xorshine 7 | * 8 | * This source code is licensed under the MIT license. 9 | */ 10 | 11 | #import "KafkaHeadRefreshControl.h" 12 | #import "KafkaReplicatorLayer.h" 13 | 14 | @interface KafkaReplicatorHeader : KafkaHeadRefreshControl 15 | 16 | @property (strong, nonatomic) KafkaReplicatorLayer * replicatorLayer; 17 | @property (assign, nonatomic) KafkaReplicatorLayerAnimationStyle animationStyle; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /KafkaRefresh/UIKit/HeadKit/KafkaReplicatorHeader.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * e-mail:xorshine@icloud.com 6 | * github:https://github.com/xorshine 7 | * 8 | * This source code is licensed under the MIT license. 9 | */ 10 | 11 | #import "KafkaReplicatorHeader.h" 12 | #import "KafkaCategories.h" 13 | 14 | @implementation KafkaReplicatorHeader 15 | 16 | - (void)setupProperties{ 17 | [super setupProperties]; 18 | [self.layer addSublayer:self.replicatorLayer]; 19 | } 20 | 21 | - (void)layoutSubviews{ 22 | [super layoutSubviews]; 23 | self.replicatorLayer.frame = CGRectMake(0, 0, self.kr_width, self.kr_height); 24 | } 25 | 26 | - (void)setThemeColor:(UIColor *)themeColor{ 27 | [super setThemeColor:themeColor]; 28 | self.replicatorLayer.themeColor = themeColor; 29 | } 30 | 31 | - (void)setAnimationStyle:(KafkaReplicatorLayerAnimationStyle)animationStyle{ 32 | _animationStyle = animationStyle; 33 | self.replicatorLayer.animationStyle = animationStyle; 34 | } 35 | 36 | - (void)kafkaDidScrollWithProgress:(CGFloat)progress max:(const CGFloat)max{ 37 | #define kOffset 0.7 38 | if (progress >= 0.8) { 39 | progress = (progress-kOffset)/(max - kOffset); 40 | } 41 | switch (self.animationStyle) { 42 | case KafkaReplicatorLayerAnimationStyleWoody:{ 43 | break; 44 | } 45 | case KafkaReplicatorLayerAnimationStyleAllen:{ 46 | 47 | break; 48 | } 49 | case KafkaReplicatorLayerAnimationStyleCircle:{ 50 | 51 | break; 52 | } 53 | case KafkaReplicatorLayerAnimationStyleDot:{ 54 | 55 | break; 56 | } 57 | case KafkaReplicatorLayerAnimationStyleArc:{ 58 | self.replicatorLayer.indicatorShapeLayer.strokeEnd = progress; 59 | break; 60 | } 61 | case KafkaReplicatorLayerAnimationStyleTriangle:{ 62 | 63 | break; 64 | } 65 | } 66 | } 67 | 68 | - (void)kafkaRefreshStateDidChange:(KafkaRefreshState)state{ 69 | [super kafkaRefreshStateDidChange:state]; 70 | switch (state) { 71 | case KafkaRefreshStateNone: 72 | case KafkaRefreshStateScrolling:break; 73 | case KafkaRefreshStateReady:{ 74 | self.replicatorLayer.opacity = 1.; 75 | break; 76 | } 77 | case KafkaRefreshStateRefreshing:{ 78 | [self.replicatorLayer startAnimating]; 79 | break; 80 | } 81 | case KafkaRefreshStateWillEndRefresh:{ 82 | [self.replicatorLayer stopAnimating]; 83 | break; 84 | } 85 | } 86 | } 87 | 88 | - (KafkaReplicatorLayer *)replicatorLayer{ 89 | if (!_replicatorLayer) { 90 | _replicatorLayer = [KafkaReplicatorLayer layer]; 91 | } 92 | return _replicatorLayer; 93 | } 94 | 95 | @end 96 | -------------------------------------------------------------------------------- /KafkaRefresh/UIKit/HeadKit/KafkaRingIndicatorHeader.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * e-mail:xorshine@icloud.com 6 | * github:https://github.com/xorshine 7 | * 8 | * This source code is licensed under the MIT license. 9 | */ 10 | 11 | #import "KafkaHeadRefreshControl.h" 12 | #import "KafkaArcLayer.h" 13 | 14 | @interface KafkaRingIndicatorHeader : KafkaHeadRefreshControl 15 | 16 | @property (strong, nonatomic) KafkaArcLayer * arcLayer; 17 | @property (strong, nonatomic) UIActivityIndicatorView * indicator; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /KafkaRefresh/UIKit/HeadKit/KafkaRingIndicatorHeader.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * e-mail:xorshine@icloud.com 6 | * github:https://github.com/xorshine 7 | * 8 | * This source code is licensed under the MIT license. 9 | */ 10 | 11 | #import "KafkaRingIndicatorHeader.h" 12 | #import "KafkaCategories.h" 13 | 14 | @implementation KafkaRingIndicatorHeader 15 | 16 | - (void)setupProperties{ 17 | [super setupProperties]; 18 | [self.layer addSublayer:self.arcLayer]; 19 | [self addSubview:self.indicator]; 20 | } 21 | 22 | - (void)layoutSubviews{ 23 | [super layoutSubviews]; 24 | self.arcLayer.frame = CGRectMake(0, 0, self.kr_width, self.kr_height); 25 | self.indicator.center = CGPointMake(self.kr_width/2., self.kr_height/2.); 26 | } 27 | 28 | - (void)setThemeColor:(UIColor *)themeColor{ 29 | if (super.themeColor == themeColor) { 30 | return; 31 | } 32 | [super setThemeColor:themeColor]; 33 | self.arcLayer.ringFillColor = themeColor; 34 | } 35 | 36 | - (void)setAnimatedBackgroundColor:(UIColor *)animatedBackgroundColor{ 37 | if (super.animatedBackgroundColor == animatedBackgroundColor) { 38 | return; 39 | } 40 | [super setAnimatedBackgroundColor:animatedBackgroundColor]; 41 | self.arcLayer.ringBackgroundColor = animatedBackgroundColor; 42 | } 43 | 44 | - (void)kafkaDidScrollWithProgress:(CGFloat)progress max:(const CGFloat)max{ 45 | #define kOffset 0.7 46 | if (progress >= kOffset) { 47 | progress = (progress-kOffset)/(max - kOffset); 48 | [self.arcLayer setProgress:progress]; 49 | } 50 | } 51 | 52 | - (void)kafkaRefreshStateDidChange:(KafkaRefreshState)state{ 53 | [super kafkaRefreshStateDidChange:state]; 54 | switch (state) { 55 | case KafkaRefreshStateNone:{ 56 | [self.arcLayer setProgress:0]; 57 | break; 58 | } 59 | case KafkaRefreshStateScrolling: 60 | case KafkaRefreshStateReady:{ 61 | break; 62 | } 63 | case KafkaRefreshStateRefreshing:{ 64 | [self.arcLayer startAnimating]; 65 | self.arcLayer.hidden = YES; 66 | [self.indicator startAnimating]; 67 | break; 68 | } 69 | case KafkaRefreshStateWillEndRefresh:{ 70 | [self.arcLayer stopAnimating]; 71 | [self.indicator stopAnimating]; 72 | self.arcLayer.hidden = NO; 73 | break; 74 | } 75 | } 76 | } 77 | 78 | - (KafkaArcLayer *)arcLayer{ 79 | if (!_arcLayer) { 80 | _arcLayer = [KafkaArcLayer layer]; 81 | } 82 | return _arcLayer; 83 | } 84 | 85 | - (UIActivityIndicatorView *)indicator{ 86 | if (!_indicator) { 87 | _indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; 88 | _indicator.hidesWhenStopped = YES; 89 | } 90 | return _indicator; 91 | } 92 | 93 | @end 94 | -------------------------------------------------------------------------------- /KafkaRefresh/UIKit/LayerKit/KafkaAnimatableProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // KafkaAnimatableProtocol.h 3 | // KafkaExample 4 | // 5 | // Created by 向小辉 on 2018/1/3. 6 | // Copyright © 2018年 Kinx. All rights reserved. 7 | // 8 | 9 | #ifndef KafkaAnimatableProtocol_h 10 | #define KafkaAnimatableProtocol_h 11 | 12 | @protocol KafkaAnimatableProtocol 13 | 14 | - (void)startAnimating; 15 | 16 | - (void)stopAnimating; 17 | 18 | @end 19 | 20 | #endif /* KafkaAnimatableProtocol_h */ 21 | -------------------------------------------------------------------------------- /KafkaRefresh/UIKit/LayerKit/KafkaArcLayer.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * Email:xorshine@icloud.com 6 | * 7 | * This source code is licensed under the MIT license. 8 | */ 9 | 10 | #import 11 | #import 12 | #import "KafkaAnimatableProtocol.h" 13 | 14 | @interface KafkaArcLayer : CALayer 15 | 16 | @property (strong, nonatomic) UIColor * ringBackgroundColor; 17 | @property (strong, nonatomic) UIColor * ringFillColor; 18 | 19 | - (void)setProgress:(CGFloat)progress; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /KafkaRefresh/UIKit/LayerKit/KafkaArcLayer.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * Email:xorshine@icloud.com 6 | * 7 | * This source code is licensed under the MIT license. 8 | */ 9 | 10 | #import "KafkaArcLayer.h" 11 | #import "KafkaCategories.h" 12 | 13 | #define KafkaColorWithRGBA(r,g,b,a) \ 14 | [UIColor colorWithRed:(r)/255. green:(g)/255. blue:(b)/255. alpha:(a)] 15 | 16 | @interface KafkaArcLayer() 17 | 18 | @property (strong, nonatomic) CAShapeLayer * ringBackgroundLayer; 19 | @property (strong, nonatomic) CAShapeLayer * ringShapeLayer; 20 | @property (strong, nonatomic) UIBezierPath * bezierPath; 21 | 22 | @end 23 | 24 | @implementation KafkaArcLayer 25 | 26 | - (instancetype)init{ 27 | self = [super init]; 28 | if (self) { 29 | _ringFillColor = KafkaColorWithRGBA(180., 180., 180., 1.0); 30 | _ringBackgroundColor = KafkaColorWithRGBA(230., 230., 230., 1.0); 31 | 32 | [self addSublayer:self.ringBackgroundLayer]; 33 | [self.ringBackgroundLayer addSublayer:self.ringShapeLayer]; 34 | } 35 | return self; 36 | } 37 | 38 | - (void)startAnimating{ 39 | 40 | } 41 | 42 | - (void)stopAnimating{ 43 | self.ringShapeLayer.strokeEnd = 0.0; 44 | [self.ringShapeLayer removeAllAnimations]; 45 | } 46 | 47 | - (void)setProgress:(CGFloat)progress{ 48 | self.ringShapeLayer.strokeEnd = progress; 49 | } 50 | 51 | - (void)setRingFillColor:(UIColor *)ringFillColor{ 52 | _ringFillColor = ringFillColor; 53 | self.ringShapeLayer.strokeColor = ringFillColor.CGColor; 54 | } 55 | 56 | - (void)setRingBackgroundColor:(UIColor *)ringBackgroundColor{ 57 | _ringBackgroundColor = ringBackgroundColor; 58 | self.ringBackgroundLayer.strokeColor = ringBackgroundColor.CGColor; 59 | } 60 | 61 | - (void)layoutSublayers{ 62 | [super layoutSublayers]; 63 | 64 | self.ringBackgroundLayer.frame = CGRectMake(0, 0, 30, 30); 65 | self.ringBackgroundLayer.position = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)); 66 | 67 | self.ringShapeLayer.frame = self.ringBackgroundLayer.bounds; 68 | self.ringShapeLayer.position = CGPointMake(CGRectGetMidX(self.ringBackgroundLayer.bounds), CGRectGetMidY(self.ringBackgroundLayer.bounds)); 69 | 70 | self.bezierPath = [UIBezierPath bezierPathWithRoundedRect:self.ringShapeLayer.bounds cornerRadius:self.ringShapeLayer.kr_width/2.]; 71 | 72 | self.ringBackgroundLayer.path = self.bezierPath.CGPath; 73 | self.ringShapeLayer.path = self.bezierPath.CGPath; 74 | } 75 | 76 | - (CAShapeLayer *)ringBackgroundLayer{ 77 | if (!_ringBackgroundLayer) { 78 | _ringBackgroundLayer = [CAShapeLayer layer]; 79 | _ringBackgroundLayer.lineWidth = 3.; 80 | _ringBackgroundLayer.lineCap = kCALineCapRound; 81 | _ringBackgroundLayer.backgroundColor = [UIColor clearColor].CGColor; 82 | _ringBackgroundLayer.fillColor = [UIColor clearColor].CGColor; 83 | _ringBackgroundLayer.strokeColor = self.ringBackgroundColor.CGColor; 84 | } 85 | return _ringBackgroundLayer; 86 | } 87 | 88 | - (CAShapeLayer *)ringShapeLayer{ 89 | if (!_ringShapeLayer) { 90 | _ringShapeLayer = [CAShapeLayer layer]; 91 | _ringShapeLayer.lineWidth = 3.; 92 | _ringShapeLayer.lineCap = kCALineCapRound; 93 | _ringShapeLayer.backgroundColor = [UIColor clearColor].CGColor; 94 | _ringShapeLayer.fillColor = [UIColor clearColor].CGColor; 95 | _ringShapeLayer.strokeColor = self.ringFillColor.CGColor; 96 | _ringShapeLayer.strokeEnd = 0; 97 | } 98 | return _ringShapeLayer; 99 | } 100 | 101 | @end 102 | -------------------------------------------------------------------------------- /KafkaRefresh/UIKit/LayerKit/KafkaReplicatorLayer.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * Email:xorshine@icloud.com 6 | * 7 | * This source code is licensed under the MIT license. 8 | */ 9 | 10 | #import 11 | #import 12 | #import "KafkaAnimatableProtocol.h" 13 | 14 | typedef NS_ENUM(NSInteger,KafkaReplicatorLayerAnimationStyle) { 15 | KafkaReplicatorLayerAnimationStyleWoody, 16 | KafkaReplicatorLayerAnimationStyleAllen, 17 | KafkaReplicatorLayerAnimationStyleCircle, 18 | KafkaReplicatorLayerAnimationStyleDot, 19 | KafkaReplicatorLayerAnimationStyleArc, 20 | KafkaReplicatorLayerAnimationStyleTriangle 21 | }; 22 | 23 | @interface KafkaReplicatorLayer : CALayer 24 | 25 | @property (strong, nonatomic) CAReplicatorLayer *replicatorLayer; 26 | @property (strong, nonatomic) CAShapeLayer *indicatorShapeLayer; 27 | 28 | @property (assign, nonatomic) KafkaReplicatorLayerAnimationStyle animationStyle; 29 | @property (nonatomic, strong) UIColor *themeColor; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /KafkaRefresh/UIKit/LayerKit/KafkaReplicatorLayer.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, K. 3 | * All rights reserved. 4 | * 5 | * Email:xorshine@icloud.com 6 | * 7 | * This source code is licensed under the MIT license. 8 | */ 9 | 10 | #define KafkaColorWithRGBA(r,g,b,a) \ 11 | [UIColor colorWithRed:(r)/255. green:(g)/255. blue:(b)/255. alpha:(a)] 12 | 13 | #import "KafkaReplicatorLayer.h" 14 | #import "KafkaCategories.h" 15 | #import "KafkaRefreshDefaults.h" 16 | #import 17 | 18 | static void * KafkaLeftDot = &KafkaLeftDot; 19 | static void * KafkaRightDot = &KafkaRightDot; 20 | 21 | @implementation KafkaReplicatorLayer 22 | 23 | - (instancetype)init{ 24 | self = [super init]; 25 | if (self) { 26 | [self setupProperties]; 27 | } 28 | return self; 29 | } 30 | 31 | - (void)setupProperties{ 32 | [self addSublayer:self.replicatorLayer]; 33 | [self.replicatorLayer addSublayer:self.indicatorShapeLayer]; 34 | self.indicatorShapeLayer.strokeColor = [KafkaRefreshDefaults standardRefreshDefaults].themeColor.CGColor; 35 | self.indicatorShapeLayer.backgroundColor = [KafkaRefreshDefaults standardRefreshDefaults].backgroundColor.CGColor; 36 | } 37 | 38 | - (void)setAnimationStyle:(KafkaReplicatorLayerAnimationStyle)animationStyle{ 39 | if (_animationStyle != animationStyle) { 40 | _animationStyle = animationStyle; 41 | [self setNeedsLayout]; 42 | } 43 | } 44 | 45 | - (void)setThemeColor:(UIColor *)themeColor{ 46 | _themeColor = themeColor; 47 | self.indicatorShapeLayer.strokeColor = themeColor.CGColor; 48 | self.indicatorShapeLayer.backgroundColor = themeColor.CGColor; 49 | } 50 | 51 | - (void)layoutSublayers { 52 | [super layoutSublayers]; 53 | 54 | self.replicatorLayer.frame = self.bounds; 55 | 56 | CGFloat padding = 10.; 57 | switch (self.animationStyle) { 58 | case KafkaReplicatorLayerAnimationStyleWoody: 59 | case KafkaReplicatorLayerAnimationStyleAllen:{ 60 | CGFloat h = self.kr_height / 3.0; 61 | CGFloat w = 3.0; 62 | CGFloat x = self.kr_width / 2. - (2.5 * w + padding * 2); 63 | CGFloat y = self.kr_height / 2. - h / 2.0; 64 | self.indicatorShapeLayer.frame = CGRectMake(x, y, w, h); 65 | self.indicatorShapeLayer.cornerRadius = 1.; 66 | 67 | self.replicatorLayer.instanceCount = 5; 68 | self.replicatorLayer.instanceDelay = 0.3/5; 69 | self.replicatorLayer.instanceTransform = CATransform3DMakeTranslation(padding+w, 0.0, 0.0); 70 | self.replicatorLayer.instanceBlueOffset = -0.01; 71 | self.replicatorLayer.instanceGreenOffset = -0.01; 72 | break; 73 | } 74 | case KafkaReplicatorLayerAnimationStyleCircle:{ 75 | self.indicatorShapeLayer.frame = CGRectMake(self.kr_width/2. - 2., 10, 4., 4.); 76 | self.indicatorShapeLayer.cornerRadius = 2.; 77 | self.indicatorShapeLayer.transform = CATransform3DMakeScale(0.2, 0.2, 0.2); 78 | 79 | self.replicatorLayer.instanceCount = 12; 80 | self.replicatorLayer.instanceDelay = 0.8/12; 81 | CGFloat angle = (2 * M_PI)/self.replicatorLayer.instanceCount; 82 | self.replicatorLayer.instanceTransform = CATransform3DMakeRotation(angle, 0, 0, 0.1); 83 | self.replicatorLayer.instanceBlueOffset = -0.01; 84 | self.replicatorLayer.instanceGreenOffset = -0.01; 85 | break; 86 | } 87 | case KafkaReplicatorLayerAnimationStyleDot:{ 88 | CGFloat innerPadding = 30.; 89 | CGFloat h = 8.0;; 90 | CGFloat w = 8.0; 91 | CGFloat x = self.kr_width / 2. - (.5 * w + innerPadding); 92 | CGFloat y = self.kr_height/2. - h/2.0; 93 | self.indicatorShapeLayer.frame = CGRectMake(x, y, w, h); 94 | self.indicatorShapeLayer.cornerRadius = 4.; 95 | 96 | self.replicatorLayer.instanceCount = 3; 97 | self.replicatorLayer.instanceDelay = 0.5/3; 98 | self.replicatorLayer.instanceTransform = CATransform3DMakeTranslation(innerPadding, 0.0, 0.0); 99 | break; 100 | } 101 | case KafkaReplicatorLayerAnimationStyleArc:{ 102 | CGFloat h = self.kr_height - 10.; 103 | CGFloat w = h; 104 | CGFloat x = self.kr_width/2. - 0.5 * w; 105 | CGFloat y = self.kr_height/2.- h/2.0; 106 | self.indicatorShapeLayer.frame = CGRectMake(x, y, w, h); 107 | self.indicatorShapeLayer.fillColor = [UIColor clearColor].CGColor; 108 | self.indicatorShapeLayer.lineWidth = 3.; 109 | self.indicatorShapeLayer.backgroundColor = [UIColor clearColor].CGColor; 110 | 111 | UIBezierPath *arcPath = [UIBezierPath bezierPath]; 112 | [arcPath addArcWithCenter:CGPointMake(w/2.0, h/2.) 113 | radius:h/2. 114 | startAngle:M_PI/2.3 115 | endAngle:-M_PI/2.3 116 | clockwise:NO]; 117 | self.indicatorShapeLayer.path = arcPath.CGPath; 118 | self.indicatorShapeLayer.strokeEnd = 0.1; 119 | self.replicatorLayer.instanceCount = 2; 120 | self.replicatorLayer.instanceTransform = CATransform3DMakeRotation(M_PI, 0, 0, 0.1); 121 | break; 122 | } 123 | case KafkaReplicatorLayerAnimationStyleTriangle:{ 124 | CGFloat h = 8.0; 125 | CGFloat w = h; 126 | self.indicatorShapeLayer.frame = CGRectMake(self.replicatorLayer.kr_width/2.-w/2, 5., w, h); 127 | self.indicatorShapeLayer.cornerRadius = self.indicatorShapeLayer.kr_width/2.; 128 | CGPoint topPoint = self.indicatorShapeLayer.position; 129 | CGPoint leftPoint = CGPointMake(topPoint.x-15, topPoint.y+23); 130 | CGPoint rightPoint = CGPointMake(topPoint.x+15, topPoint.y+23); 131 | 132 | CAShapeLayer *leftCircle = [self leftCircle]; 133 | CAShapeLayer *rightCircle = [self rightCircle]; 134 | if (leftCircle) 135 | [leftCircle removeFromSuperlayer]; 136 | if (rightCircle) 137 | [rightCircle removeFromSuperlayer]; 138 | 139 | leftCircle.kr_size = self.indicatorShapeLayer.kr_size; 140 | leftCircle.position = leftPoint; 141 | leftCircle.cornerRadius = self.indicatorShapeLayer.cornerRadius; 142 | [self.replicatorLayer addSublayer:leftCircle]; 143 | 144 | rightCircle.kr_size = self.indicatorShapeLayer.kr_size; 145 | rightCircle.position = rightPoint; 146 | rightCircle.cornerRadius = self.indicatorShapeLayer.cornerRadius; 147 | [self.replicatorLayer addSublayer:rightCircle]; 148 | break; 149 | } 150 | } 151 | } 152 | 153 | - (void)startAnimating{ 154 | [self.indicatorShapeLayer removeAllAnimations]; 155 | switch (self.animationStyle) { 156 | case KafkaReplicatorLayerAnimationStyleWoody:{ 157 | CABasicAnimation *basicAnimation = [self animationKeyPath:@"transform.scale.y" 158 | from:@(1.5) 159 | to:@(0.0) 160 | duration:0.3 161 | repeatTime:INFINITY]; 162 | basicAnimation.autoreverses = YES; 163 | [self.indicatorShapeLayer addAnimation:basicAnimation forKey:basicAnimation.keyPath]; 164 | break; 165 | } 166 | case KafkaReplicatorLayerAnimationStyleAllen:{ 167 | CABasicAnimation *basicAnimation = [self animationKeyPath:@"position.y" 168 | from:@(self.indicatorShapeLayer.position.y+10) 169 | to:@(self.indicatorShapeLayer.position.y-10) 170 | duration:0.3 171 | repeatTime:INFINITY]; 172 | basicAnimation.autoreverses = YES; 173 | [self.indicatorShapeLayer addAnimation:basicAnimation forKey:basicAnimation.keyPath]; 174 | break; 175 | } 176 | case KafkaReplicatorLayerAnimationStyleCircle:{ 177 | CABasicAnimation *basicAnimation = [self animationKeyPath:@"transform.scale" 178 | from:@(1.0) 179 | to:@(0.2) 180 | duration:0.8 181 | repeatTime:INFINITY]; 182 | [self.indicatorShapeLayer addAnimation:basicAnimation forKey:basicAnimation.keyPath]; 183 | break; 184 | } 185 | case KafkaReplicatorLayerAnimationStyleDot:{ 186 | CABasicAnimation *basicAnimation = [self animationKeyPath:@"transform.scale" 187 | from:@(0.3) 188 | to:@(2.5) 189 | duration:0.5 190 | repeatTime:INFINITY]; 191 | basicAnimation.autoreverses = YES; 192 | CABasicAnimation * opc = [self animationKeyPath:@"opacity" 193 | from:@(0.1) 194 | to:@(1.0) 195 | duration:0.5 196 | repeatTime:INFINITY]; 197 | 198 | opc.autoreverses = YES; 199 | CAAnimationGroup * group = [CAAnimationGroup animation]; 200 | group.animations = @[basicAnimation,opc]; 201 | group.autoreverses = YES; 202 | group.repeatCount = INFINITY; 203 | group.duration = 0.5; 204 | [self.indicatorShapeLayer addAnimation:group forKey:basicAnimation.keyPath]; 205 | break; 206 | } 207 | case KafkaReplicatorLayerAnimationStyleArc:{ 208 | CABasicAnimation *basicAnimation = [self animationKeyPath:@"transform.rotation.z" 209 | from:@(0.0) 210 | to:@(2*M_PI) 211 | duration:0.8 212 | repeatTime:INFINITY]; 213 | [self.indicatorShapeLayer addAnimation:basicAnimation forKey:basicAnimation.keyPath]; 214 | break; 215 | } 216 | case KafkaReplicatorLayerAnimationStyleTriangle:{ 217 | CAShapeLayer *leftCircle = objc_getAssociatedObject(self, KafkaLeftDot); 218 | CAShapeLayer *rightCircle = objc_getAssociatedObject(self, KafkaRightDot); 219 | 220 | CGPoint topPoint = self.indicatorShapeLayer.position; 221 | CGPoint leftPoint = leftCircle.position; 222 | CGPoint rightPoint = rightCircle.position; 223 | 224 | NSArray *vertexs = @[[NSValue valueWithCGPoint:topPoint], 225 | [NSValue valueWithCGPoint:leftPoint], 226 | [NSValue valueWithCGPoint:rightPoint]]; 227 | 228 | CAKeyframeAnimation *key0 = [self keyFrameAnimationWithPath:[self trianglePathWithStartPoint:topPoint vertexs:vertexs] duration:1.5]; 229 | [self.indicatorShapeLayer addAnimation:key0 forKey:key0.keyPath]; 230 | 231 | CAKeyframeAnimation *key1 = [self keyFrameAnimationWithPath:[self trianglePathWithStartPoint:leftPoint vertexs:vertexs] duration:1.5]; 232 | [rightCircle addAnimation:key1 forKey:key1.keyPath]; 233 | 234 | CAKeyframeAnimation *key2 = [self keyFrameAnimationWithPath:[self trianglePathWithStartPoint:rightPoint vertexs:vertexs] duration:1.5]; 235 | [leftCircle addAnimation:key2 forKey:key2.keyPath]; 236 | break; 237 | } 238 | } 239 | } 240 | 241 | - (void)stopAnimating{ 242 | [self.indicatorShapeLayer removeAllAnimations]; 243 | 244 | switch (self.animationStyle) { 245 | case KafkaReplicatorLayerAnimationStyleWoody: 246 | case KafkaReplicatorLayerAnimationStyleAllen: 247 | case KafkaReplicatorLayerAnimationStyleCircle: 248 | case KafkaReplicatorLayerAnimationStyleDot:{ 249 | break; 250 | } 251 | case KafkaReplicatorLayerAnimationStyleArc:{ 252 | self.indicatorShapeLayer.strokeEnd = 0.1; 253 | break; 254 | } 255 | case KafkaReplicatorLayerAnimationStyleTriangle:{ 256 | CAShapeLayer *leftCircle = objc_getAssociatedObject(self, KafkaLeftDot); 257 | [leftCircle removeAllAnimations]; 258 | CAShapeLayer *rightCircle = objc_getAssociatedObject(self, KafkaRightDot); 259 | [rightCircle removeAllAnimations]; 260 | break; 261 | } 262 | } 263 | 264 | } 265 | 266 | - (CABasicAnimation *)animationKeyPath:(NSString *)keyPath 267 | from:(NSNumber *)fromValue 268 | to:(NSNumber *)toValue 269 | duration:(CFTimeInterval)duration 270 | repeatTime:(CGFloat)repeat { 271 | CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:keyPath]; 272 | animation.fromValue = fromValue; 273 | animation.toValue = toValue; 274 | animation.duration = duration; 275 | animation.repeatCount = repeat; 276 | animation.removedOnCompletion = NO; 277 | return animation; 278 | } 279 | 280 | - (CAKeyframeAnimation *)keyFrameAnimationWithPath:(UIBezierPath *)path 281 | duration:(NSTimeInterval)duration { 282 | CAKeyframeAnimation *animation = [[CAKeyframeAnimation alloc] init]; 283 | animation.keyPath = @"position"; 284 | animation.path = path.CGPath; 285 | animation.duration = duration; 286 | animation.repeatCount = INFINITY; 287 | animation.removedOnCompletion = NO; 288 | return animation; 289 | } 290 | 291 | - (UIBezierPath *)trianglePathWithStartPoint:(CGPoint)startPoint vertexs:(NSArray *)vertexs { 292 | CGPoint topPoint = [[vertexs objectAtIndex:0] CGPointValue]; 293 | CGPoint leftPoint = [[vertexs objectAtIndex:1] CGPointValue]; 294 | CGPoint rightPoint = [[vertexs objectAtIndex:2] CGPointValue]; 295 | 296 | UIBezierPath *path = [UIBezierPath bezierPath]; 297 | 298 | if (CGPointEqualToPoint(startPoint, topPoint) ) { 299 | [path moveToPoint:startPoint]; 300 | [path addLineToPoint:rightPoint]; 301 | [path addLineToPoint:leftPoint]; 302 | } else if (CGPointEqualToPoint(startPoint, leftPoint)) { 303 | [path moveToPoint:startPoint]; 304 | [path addLineToPoint:topPoint]; 305 | [path addLineToPoint:rightPoint]; 306 | } else { 307 | [path moveToPoint:startPoint]; 308 | [path addLineToPoint:leftPoint]; 309 | [path addLineToPoint:topPoint]; 310 | } 311 | 312 | [path closePath]; 313 | 314 | return path; 315 | } 316 | 317 | - (CAShapeLayer *)leftCircle{ 318 | CAShapeLayer *leftCircle = objc_getAssociatedObject(self, KafkaLeftDot); 319 | if (!leftCircle) { 320 | leftCircle = [CAShapeLayer layer]; 321 | leftCircle.backgroundColor = self.indicatorShapeLayer.backgroundColor; 322 | objc_setAssociatedObject(self, KafkaLeftDot, leftCircle, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 323 | } 324 | return leftCircle; 325 | } 326 | 327 | - (CAShapeLayer *)rightCircle{ 328 | CAShapeLayer *rightCircle = objc_getAssociatedObject(self, KafkaRightDot); 329 | if (!rightCircle) { 330 | rightCircle = [CAShapeLayer layer]; 331 | rightCircle.backgroundColor = self.indicatorShapeLayer.backgroundColor; 332 | objc_setAssociatedObject(self, KafkaRightDot, rightCircle, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 333 | } 334 | return rightCircle; 335 | } 336 | 337 | - (CAShapeLayer *)indicatorShapeLayer{ 338 | if (!_indicatorShapeLayer) { 339 | _indicatorShapeLayer = [CAShapeLayer layer]; 340 | _indicatorShapeLayer.contentsScale = [[UIScreen mainScreen] scale]; 341 | _indicatorShapeLayer.lineCap = kCALineCapRound; 342 | } 343 | return _indicatorShapeLayer; 344 | } 345 | 346 | - (CAReplicatorLayer *)replicatorLayer{ 347 | if (!_replicatorLayer) { 348 | _replicatorLayer = [CAReplicatorLayer layer]; 349 | _replicatorLayer.backgroundColor = [UIColor clearColor].CGColor; 350 | _replicatorLayer.shouldRasterize = YES; 351 | _replicatorLayer.rasterizationScale = [[UIScreen mainScreen] scale]; 352 | } 353 | return _replicatorLayer; 354 | } 355 | 356 | @end 357 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 H. H. Hsiang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 H. H. Hsiang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 | 6 |

KafkaRefresh

7 |

8 | Animated, customizable, and flexible pull-to-refresh framework for faster and easier iOS development. 9 |
10 |
11 | Report bug 12 | · 13 | Request feature 14 | · 15 | 中文文档 16 |

17 |

18 |
19 | 20 | ### Status 21 | 23 | [![GitHub license](https://img.shields.io/github/license/xorshine/KafkaRefresh.svg)](https://github.com/xorshine/KafkaRefresh/blob/master/LICENSE) 24 | [![CocoaPods Compatible](https://img.shields.io/cocoapods/v/KafkaRefresh.svg)](https://img.shields.io/cocoapods/v/KafkaRefresh.svg) 25 | ![platform](https://img.shields.io/badge/platform-ios-lightgrey.svg) 26 | ![language](https://img.shields.io/badge/language-objc-orange.svg) 27 | [![Email](https://img.shields.io/badge/e--mail-xorshine%40icloud.com-blue.svg)](mailto:xorshine@icloud.com) 28 | 29 | **** 30 | 31 | ### Screenshots 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 |
KafkaRefreshStyleTop ScreenshotsBottom Screenshots
Native
ReplicatorWoody
ReplicatorAllen
ReplicatorCircle
ReplicatorDot
ReplicatorArc
ReplicatorTriangle
AnimatableRing
AnimatableArrow
84 | 85 | ### Features 86 | 87 | * **Built-in rich animation style, support self-customization** 88 | 89 | 90 | * **Non-refresh state hidden automatically** 91 | 92 | > To avoid developers manually adjust contentInset refresh the appearance of the control after the impact of the visual experience;
93 | the most common situation, the absence of data, the bottom of the refresh control is not hidden, the use of KafkaRefresh to avoid the problem. 94 | 95 | * **Anti-dithering at the end of the refresh** 96 | 97 | >When the refresh control finishes refreshing, if UIScrollView is in a scrolling state, KafkaRefresh will adjust the contntOffset that controls the UIScrollView at this time according to the refresh control. 98 | 99 | * **Support setting the offset threshold to trigger refresh** 100 | 101 | >Setting the value of `stretchOffsetYAxisThreshold` can control the refresh pull distance.This property is a ratio relative to the height of the control and must be set greater than 1.0. 102 | 103 | * **Support global setting** 104 | 105 | >KafkaRefreshDefaults is a singleton for global settings 106 | 107 | * **Support progress callback** 108 | 109 | >Real-time callback Drag the offset ratio, for the expansion of the interface, according to the progress of adjustment animation. 110 | 111 | * **Adaptive contentInset system adjustment and manual adjustment** 112 | 113 | >Adaptive UINavigationController for UIScrollView's contentInset property adjustment, even if the contentInset automatically set value, then KafkaRefresh can still adapt this adjustment. 114 | 115 | * **Solve the section view floating problem when refreshing** 116 | 117 | * **Support horizontal and vertical screen switching adaptive** 118 | 119 | >No need to consider in the horizontal and vertical screen refresh refresh problem. 120 | 121 | * **iOS 7+** 122 | 123 | Support iOS 7 above system. Including iPhone X. 124 | 125 | * Support auto refresh 126 | 127 | When the user slides the scrollview to the bottom, the refresh will be triggered automatically, without the user having to slide to the bottom and pull up the scrollview. This feature is not enabled by default, because most people will use the function without viewing the document. If it is not used correctly, it will be easy to cause the refresh to stop. 128 | 129 | Use the preload feature, please strictly follow the requirements below: 130 | 131 | * ```self.tableView.footRefreshControl.autoRefreshOnFoot = YES;``` please set autoRefreshOnFoot TRUE; 132 | 133 | * in refreshHandler,Strictly follow the logic below! 134 | 135 | ```objective-c 136 | if ({No data needs to be stitched}) { 137 | [weakSelf.tableView.footRefreshControl endRefreshingAndNoLongerRefreshingWithAlertText:@"no more"]; 138 | } else { 139 | [weakSelf.tableView.footRefreshControl endRefreshingWithAlertText:@"did load successfully" completion:nil]; 140 | } 141 | ``` 142 | 143 | * **Document coverage 100%** 144 | 145 | > You can see the use of all methods and classes in the header file. 146 | 147 | 148 | ### Installation 149 | * CocoaPods 150 | ```ruby 151 | pod 'KafkaRefresh' 152 | ``` 153 | 154 | * Carthage 155 | 156 | > If anyone wants to install by *carthage* , please supply a pull request. I'm not using this package manager myself. 157 | 158 | ### Usage 159 | 160 | ```objective-c 161 | #import "KafkaRefresh.h" 162 | ``` 163 | 164 | ##### Initialization 165 | * The first way 166 | ```objective-c 167 | #pragma mark - head 168 | 169 | [self.tableView bindHeadRefreshHandler:^{ 170 | 171 | } themeColor:MainColor refreshStyle:KafkaRefreshStyleAnimatableArrow]; 172 | 173 | #pragma mark - foot 174 | 175 | [self.tableView bindFootRefreshHandler:^{ 176 | 177 | } themeColor:MainColor refreshStyle:KafkaRefreshStyleAnimatableArrow]; 178 | 179 | #pragma mark - auto refresh 180 | 181 | self.tableView.footRefreshControl.autoRefreshOnFoot = YES; 182 | ``` 183 | * The second way 184 | ```objective-c 185 | KafkaArrowHeader * arrow = [[KafkaArrowHeader alloc] init]; 186 | arrow.refreshHandler = ^{ 187 | //to do something... 188 | }; 189 | self.tableView.headRefreshControl = arrow; 190 | ``` 191 | * The third way(global configuration) 192 | ```objective-c 193 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 194 | [[KafkaRefreshDefaults standardRefreshDefaults] setHeaderDefaultStyle:KafkaRefreshStyleAnimatableRing]; 195 | return YES; 196 | } 197 | 198 | #pragma mark - global 199 | 200 | [self.tableView bindGlobalStyleForFootRefreshHandler:^{ 201 | 202 | }]; 203 | ``` 204 | ##### Trigger Refresh Manually 205 | 206 | ```objective-c 207 | [self.tableView.headRefreshControl beginRefreshing]; 208 | [self.tableView.footRefreshControl beginRefreshing]; 209 | ``` 210 | 211 | ##### End Refresh 212 | > When you finish refreshing and don't need to show any hints, or any animation, call the following method. 213 | 214 | ```objective-c 215 | - (void)endRefreshing; 216 | ``` 217 | > When you finish the refresh and need to display the prompt message, call the following method. 218 | 219 | ```objective-c 220 | - (void)endRefreshingWithAlertText:(NSString *)text completion:(dispatch_block_t)completion; 221 | ``` 222 | 223 | >When you end the refresh and no longer need to refresh, call the following method. 224 | 225 | ```objective-c 226 | - (void)endRefreshingAndNoLongerRefreshingWithAlertText:(NSString *)text; 227 | ``` 228 | ##### Resume Refresh Available 229 | ```objective-c 230 | /** 231 | After you call ‘endRefreshingAndNoLongerRefreshingWithAlertText’, 232 | you need to resume refresh available 233 | */ 234 | - (void)resumeRefreshAvailable; 235 | ``` 236 | 237 | ### Customize 238 | 239 | Take KafkaheadRefreshControl as an example 240 | ```objective-c 241 | #import "KafkaheadRefreshControl.h" 242 | @interface CustomHeader : KafkafootRefreshControl 243 | @end 244 | ``` 245 | 246 | ```objective-c 247 | @implementation CustomHeader 248 | 249 | - (void)kafkaDidScrollWithProgress:(CGFloat)progress max:(const CGFloat)max{ 250 | //progress callback 251 | } 252 | 253 | - (void)kafkaRefreshStateDidChange:(KafkaRefreshState)state{ 254 | [super kafkaRefreshStateDidChange:state]; 255 | } 256 | @end 257 | ``` 258 | ### Warnings And Precautions 259 | 260 | * Many people asked me a question that tableView jump after called `insertRowAtIndexPath: withRowAnimation:`,That Is Not Bug Of KafkaRefresh. Setting `tableView.estimatedRowHeight = UITableViewAutomaticDimension;` can solve this question when you initialize tableView; 261 | 262 | 263 | * Please update the latest version! 264 | 265 | 266 | ### Communication 267 | > 1. If you need help,please email . 268 | > 2. If you found a bug,and can provide steps to reliably reproduce it, open an issue. 269 | > 3. Personal energy is limited, Kafka provides callback interface enough to increase the richer UI effect, we welcome you to join together and submit the pull request. 270 | 271 | 272 | ### License 273 | > KafkaRefresh is released under the MIT license. See [LICENSE](https://github.com/xorshine/KafkaRefresh/blob/master/LICENSE) for details. 274 | 275 | 276 | [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FHHHsiang%2FKafkaRefresh.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2FHHHsiang%2FKafkaRefresh?ref=badge_large) -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-time-machine --------------------------------------------------------------------------------