Sin descripción

FKRefreshControl.m 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. //
  2. // RefreshControl.m
  3. //
  4. // Copyright (c) 2014 YDJ ( https://github.com/ydj/RefreshControl )
  5. //
  6. // Permission is hereby granted, free of charge, to any person obtaining a copy
  7. // of this software and associated documentation files (the "Software"), to deal
  8. // in the Software without restriction, including without limitation the rights
  9. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. // copies of the Software, and to permit persons to whom the Software is
  11. // furnished to do so, subject to the following conditions:
  12. //
  13. // The above copyright notice and this permission notice shall be included in
  14. // all copies or substantial portions of the Software.
  15. //
  16. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. // THE SOFTWARE.
  23. #import "FKRefreshControl.h"
  24. #import "FKRefreshBottomView.h"
  25. #import "FKRefreshViewDelegate.h"
  26. #import "FKRefreshTopView.h"
  27. @interface FKRefreshControl ()
  28. @property (nonatomic,weak)id<RefreshControlDelegate>delegate;
  29. @property (nonatomic,strong)UIView <FKRefreshViewDelegate> * topView;
  30. @property (nonatomic,strong)UIView <FKRefreshViewDelegate> * bottomView;
  31. @property (nonatomic,copy)NSString * topClass;
  32. @property (nonatomic,copy)NSString * bottomClass;
  33. @end
  34. @implementation FKRefreshControl
  35. - (void)registerClassForTopView:(Class)topClass
  36. {
  37. if ([topClass conformsToProtocol:@protocol(FKRefreshViewDelegate)]) {
  38. self.topClass=NSStringFromClass([topClass class]);
  39. }
  40. else{
  41. self.topClass=NSStringFromClass([FKRefreshTopView class]);
  42. }
  43. }
  44. - (void)registerClassForBottomView:(Class)bottomClass
  45. {
  46. if ([bottomClass conformsToProtocol:@protocol(FKRefreshViewDelegate)]) {
  47. self.bottomClass=NSStringFromClass([bottomClass class]);
  48. }
  49. else{
  50. self.bottomClass=NSStringFromClass([FKRefreshBottomView class]);
  51. }
  52. }
  53. - (instancetype)initWithScrollView:(UIScrollView *)scrollView delegate:(id<RefreshControlDelegate>)delegate
  54. {
  55. self=[super init];
  56. if (self)
  57. {
  58. _scrollView=scrollView;
  59. _delegate=delegate;
  60. _topClass=NSStringFromClass([FKRefreshTopView class]);
  61. _bottomClass=NSStringFromClass([FKRefreshBottomView class]);
  62. self.enableInsetTop=65.0;
  63. self.enableInsetBottom=65.0;
  64. self.enableTopEngage = YES;
  65. self.enableBottomEngage = YES;
  66. self.bottomRefreshOffsetY = 0;
  67. [_scrollView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:NULL];
  68. [_scrollView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld|NSKeyValueObservingOptionPrior context:NULL];
  69. }
  70. return self;
  71. }
  72. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
  73. {
  74. if([keyPath isEqual:@"contentSize"])
  75. {
  76. if (self.topEnabled)
  77. {
  78. [self initTopView];
  79. }
  80. if (self.bottomEnabled)
  81. {
  82. [self initBottonView];
  83. }
  84. }
  85. else if([keyPath isEqualToString:@"contentOffset"])
  86. {
  87. if (_refreshingDirection==RefreshingDirectionNone) {
  88. [self _drogForChange:change];
  89. }
  90. }
  91. }
  92. - (void)_drogForChange:(NSDictionary *)change
  93. {
  94. if ( self.topEnabled && self.scrollView.contentOffset.y<0)
  95. {
  96. if(self.scrollView.contentOffset.y<-self.enableInsetTop)
  97. {
  98. if (self.autoRefreshTop || ( self.scrollView.decelerating && self.scrollView.dragging==NO)) {
  99. [self _engageRefreshDirection:RefreshDirectionTop];
  100. }
  101. else {
  102. [self _canEngageRefreshDirection:RefreshDirectionTop];
  103. }
  104. }
  105. else
  106. {
  107. [self _didDisengageRefreshDirection:RefreshDirectionTop];
  108. }
  109. }
  110. if ( self.bottomEnabled && self.scrollView.contentOffset.y>0 )
  111. {
  112. if(self.scrollView.contentOffset.y>(self.scrollView.contentSize.height+self.enableInsetBottom-self.scrollView.bounds.size.height) )
  113. {
  114. if(self.autoRefreshBottom || (self.scrollView.decelerating && self.scrollView.dragging==NO)){
  115. [self _engageRefreshDirection:RefreshDirectionBottom];
  116. }
  117. else{
  118. [self _canEngageRefreshDirection:RefreshDirectionBottom];
  119. }
  120. }
  121. else {
  122. [self _didDisengageRefreshDirection:RefreshDirectionBottom];
  123. }
  124. }
  125. }
  126. - (void)_canEngageRefreshDirection:(RefreshDirection) direction
  127. {
  128. if (direction==RefreshDirectionTop)
  129. {
  130. if ([self.topView respondsToSelector:@selector(canEngageRefreshWithOffsetY:)]) {
  131. [self.topView canEngageRefreshWithOffsetY:self.scrollView.contentOffset.y];
  132. }
  133. }
  134. else if (direction==RefreshDirectionBottom)
  135. {
  136. if ([self.bottomView respondsToSelector:@selector(canEngageRefreshWithOffsetY:)]){
  137. [self.bottomView canEngageRefreshWithOffsetY:self.scrollView.contentOffset.y];
  138. }
  139. }
  140. }
  141. - (void)_didDisengageRefreshDirection:(RefreshDirection) direction
  142. {
  143. if (direction==RefreshDirectionTop)
  144. {
  145. if ([self.topView respondsToSelector:@selector(didDisengageRefreshWithOffsetY:)]){
  146. [self.topView didDisengageRefreshWithOffsetY:self.scrollView.contentOffset.y];
  147. }
  148. }
  149. else if (direction==RefreshDirectionBottom)
  150. {
  151. if ([self.bottomView respondsToSelector:@selector(didDisengageRefreshWithOffsetY:)]){
  152. [self.bottomView didDisengageRefreshWithOffsetY:self.scrollView.contentOffset.y];
  153. }
  154. }
  155. }
  156. - (void)_engageRefreshDirection:(RefreshDirection) direction
  157. {
  158. UIEdgeInsets edge = UIEdgeInsetsZero;
  159. if (direction==RefreshDirectionTop)
  160. {
  161. _refreshingDirection=RefreshingDirectionTop;
  162. float topH=self.enableInsetTop<45?45:self.enableInsetTop;
  163. edge=UIEdgeInsetsMake(topH, 0, 0, 0);///enableInsetTop
  164. if (_enableTopEngage) _scrollView.contentInset=edge;
  165. }
  166. else if (direction==RefreshDirectionBottom)
  167. {
  168. float botomH=self.enableInsetBottom<50?50:self.enableInsetBottom;
  169. edge=UIEdgeInsetsMake(0, 0, botomH, 0);///self.enableInsetBottom
  170. _refreshingDirection=RefreshingDirectionBottom;
  171. if (_enableBottomEngage) _scrollView.contentInset=edge;
  172. }
  173. // _scrollView.contentInset=edge;
  174. [self _didEngageRefreshDirection:direction];
  175. }
  176. - (void)_didEngageRefreshDirection:(RefreshDirection) direction
  177. {
  178. if (direction==RefreshDirectionTop)
  179. {
  180. [self.topView performSelector:@selector(startRefreshing)];
  181. //[self.topView startRefreshing];
  182. }
  183. else if (direction==RefreshDirectionBottom)
  184. {
  185. [self.bottomView performSelector:@selector(startRefreshing)];
  186. // [self.bottomView startRefreshing];
  187. }
  188. if ([self.delegate respondsToSelector:@selector(refreshControl:didEngageRefreshDirection:)])
  189. {
  190. [self.delegate refreshControl:self didEngageRefreshDirection:direction];
  191. }
  192. }
  193. - (void)_startRefreshingDirection:(RefreshDirection)direction animation:(BOOL)animation
  194. {
  195. CGPoint point =CGPointZero;
  196. if (direction==RefreshDirectionTop)
  197. {
  198. float topH=self.enableInsetTop<45?45:self.enableInsetTop;
  199. point=CGPointMake(0, -topH);//enableInsetTop
  200. }
  201. else if (direction==RefreshDirectionBottom)
  202. {
  203. float height=MAX(self.scrollView.contentSize.height, self.scrollView.frame.size.height);
  204. float bottomH=self.enableInsetBottom<45?45:self.enableInsetBottom;
  205. point=CGPointMake(0, height-self.scrollView.bounds.size.height+bottomH);///enableInsetBottom
  206. }
  207. __weak typeof(self)weakSelf=self;
  208. [_scrollView setContentOffset:point animated:YES];
  209. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  210. __strong typeof(self)strongSelf=weakSelf;
  211. [strongSelf _engageRefreshDirection:direction];
  212. });
  213. }
  214. - (void)_finishRefreshingDirection1:(RefreshDirection)direction animation:(BOOL)animation
  215. {
  216. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.8 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  217. [UIView animateWithDuration:0.25 animations:^{
  218. _scrollView.contentInset=UIEdgeInsetsZero;
  219. } completion:^(BOOL finished) {
  220. }];
  221. _refreshingDirection=RefreshingDirectionNone;
  222. if (direction==RefreshDirectionTop)
  223. {
  224. [self.topView performSelector:@selector(finishRefreshing)];
  225. //[self.topView finishRefreshing];
  226. }
  227. else if(direction==RefreshDirectionBottom)
  228. {
  229. [self.bottomView performSelector:@selector(finishRefreshing)];
  230. //[self.bottomView finishRefreshing];
  231. }
  232. });
  233. }
  234. - (void)dealloc
  235. {
  236. [_scrollView removeObserver:self forKeyPath:@"contentSize"];
  237. [_scrollView removeObserver:self forKeyPath:@"contentOffset"];
  238. }
  239. - (void)initTopView
  240. {
  241. if (!CGRectIsEmpty(self.scrollView.frame))
  242. {
  243. float topOffsetY=self.enableInsetTop+45*6;
  244. if (self.topView==nil)
  245. {
  246. Class className=NSClassFromString(self.topClass);
  247. _topView=[[className alloc] initWithFrame:CGRectMake(0, -topOffsetY, self.scrollView.frame.size.width, topOffsetY)];
  248. _topView.backgroundColor = UIColorFromRGB(0xf4f4f4);
  249. [self.scrollView addSubview:self.topView];
  250. [self.topView resetLayoutSubViews];
  251. }
  252. else{
  253. _topView.frame=CGRectMake(0, -topOffsetY, self.scrollView.frame.size.width, topOffsetY);
  254. [_topView performSelector:@selector(resetLayoutSubViews)];
  255. //[_topView resetLayoutSubViews];
  256. }
  257. }
  258. }
  259. - (void)initBottonView
  260. {
  261. if (!CGRectIsNull(self.scrollView.frame))
  262. {
  263. float y=MAX(self.scrollView.bounds.size.height, self.scrollView.contentSize.height);
  264. if (self.bottomView==nil)
  265. {
  266. Class className=NSClassFromString(self.bottomClass);
  267. _bottomView=[[className alloc] initWithFrame:CGRectMake(0,y + self.bottomRefreshOffsetY, self.scrollView.bounds.size.width, self.enableInsetBottom+45)];
  268. [self.scrollView addSubview:_bottomView];
  269. }
  270. else{
  271. _bottomView.frame=CGRectMake(0,y + self.bottomRefreshOffsetY, self.scrollView.bounds.size.width, self.enableInsetBottom+45*6);
  272. [self.bottomView performSelector:@selector(resetLayoutSubViews)];
  273. // [self.bottomView resetLayoutSubViews];
  274. }
  275. }
  276. }
  277. - (void)setTopEnabled:(BOOL)topEnabled
  278. {
  279. _topEnabled=topEnabled;
  280. if (_topEnabled)
  281. {
  282. if (self.topView==nil)
  283. {
  284. [self initTopView];
  285. }
  286. }
  287. else{
  288. [self.topView removeFromSuperview];
  289. self.topView=nil;
  290. }
  291. }
  292. - (void)setBottomEnabled:(BOOL)bottomEnabled
  293. {
  294. _bottomEnabled=bottomEnabled;
  295. if (_bottomEnabled)
  296. {
  297. if (_bottomView==nil)
  298. {
  299. [self initBottonView];
  300. }
  301. }
  302. else{
  303. [_bottomView removeFromSuperview];
  304. _bottomView=nil;
  305. }
  306. }
  307. - (void)startRefreshingDirection:(RefreshDirection)direction
  308. {
  309. [self _startRefreshingDirection:direction animation:YES];
  310. }
  311. - (void)finishRefreshingDirection:(RefreshDirection)direction
  312. {
  313. [self _finishRefreshingDirection1:direction animation:YES];
  314. }
  315. @end