UIScrollView+NTESPullToRefresh.m 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722
  1. //
  2. // UIScrollView+NTESPullToRefresh.m
  3. //
  4. // Created by chris on 15/2/12.
  5. // Copyright (c) 2015年 Netease. All rights reserved.
  6. //
  7. //
  8. #import <QuartzCore/QuartzCore.h>
  9. #import "UIScrollView+NTESPullToRefresh.h"
  10. #define fequal(a,b) (fabs((a) - (b)) < FLT_EPSILON)
  11. #define fequalzero(a) (fabs(a) < FLT_EPSILON)
  12. static CGFloat const NTESPullToRefreshViewHeight = 60;
  13. @interface NTESPullToRefreshArrow : UIView
  14. @property (nonatomic, strong) UIColor *arrowColor;
  15. @end
  16. @interface NTESPullToRefreshView ()
  17. @property (nonatomic, copy) void (^pullToRefreshActionHandler)(void);
  18. @property (nonatomic, strong) NTESPullToRefreshArrow *arrow;
  19. @property (nonatomic, strong) UIActivityIndicatorView *activityIndicatorView;
  20. @property (nonatomic, strong, readwrite) UILabel *titleLabel;
  21. @property (nonatomic, strong, readwrite) UILabel *subtitleLabel;
  22. @property (nonatomic, readwrite) NTESPullToRefreshState state;
  23. @property (nonatomic, readwrite) NTESPullToRefreshPosition position;
  24. @property (nonatomic, strong) NSMutableArray *titles;
  25. @property (nonatomic, strong) NSMutableArray *subtitles;
  26. @property (nonatomic, strong) NSMutableArray *viewForState;
  27. @property (nonatomic, weak) UIScrollView *scrollView;
  28. @property (nonatomic, readwrite) CGFloat originalTopInset;
  29. @property (nonatomic, readwrite) CGFloat originalBottomInset;
  30. @property (nonatomic, assign) BOOL wasTriggeredByUser;
  31. @property (nonatomic, assign) BOOL showsPullToRefresh;
  32. @property (nonatomic, assign) BOOL showsDateLabel;
  33. @property(nonatomic, assign) BOOL isObserving;
  34. - (void)resetScrollViewContentInset;
  35. - (void)setScrollViewContentInsetForLoading;
  36. - (void)setScrollViewContentInset:(UIEdgeInsets)insets;
  37. - (void)rotateArrow:(float)degrees hide:(BOOL)hide;
  38. @end
  39. #pragma mark - UIScrollView (NTESPullToRefresh)
  40. #import <objc/runtime.h>
  41. static char UIScrollViewPullToRefreshView;
  42. @implementation UIScrollView (NTESPullToRefresh)
  43. @dynamic pullToRefreshView, showsPullToRefresh;
  44. - (void)addPullToRefreshWithActionHandler:(void (^)(void))actionHandler position:(NTESPullToRefreshPosition)position {
  45. if(!self.pullToRefreshView) {
  46. CGFloat yOrigin;
  47. switch (position) {
  48. case NTESPullToRefreshPositionTop:
  49. yOrigin = -NTESPullToRefreshViewHeight;
  50. break;
  51. case NTESPullToRefreshPositionBottom:
  52. yOrigin = self.contentSize.height;
  53. break;
  54. default:
  55. return;
  56. }
  57. NTESPullToRefreshView *view = [[NTESPullToRefreshView alloc] initWithFrame:CGRectMake(0, yOrigin, self.bounds.size.width, NTESPullToRefreshViewHeight)];
  58. view.pullToRefreshActionHandler = actionHandler;
  59. view.scrollView = self;
  60. [self addSubview:view];
  61. view.originalTopInset = self.contentInset.top;
  62. view.originalBottomInset = self.contentInset.bottom;
  63. view.position = position;
  64. self.pullToRefreshView = view;
  65. self.showsPullToRefresh = YES;
  66. }
  67. }
  68. - (void)addPullToRefreshWithActionHandler:(void (^)(void))actionHandler {
  69. [self addPullToRefreshWithActionHandler:actionHandler position:NTESPullToRefreshPositionTop];
  70. }
  71. - (void)triggerPullToRefresh {
  72. self.pullToRefreshView.state = NTESPullToRefreshStateTriggered;
  73. [self.pullToRefreshView startAnimating];
  74. }
  75. - (void)setPullToRefreshView:(NTESPullToRefreshView *)pullToRefreshView {
  76. [self willChangeValueForKey:@"SVPullToRefreshView"];
  77. objc_setAssociatedObject(self, &UIScrollViewPullToRefreshView,
  78. pullToRefreshView,
  79. OBJC_ASSOCIATION_ASSIGN);
  80. [self didChangeValueForKey:@"SVPullToRefreshView"];
  81. }
  82. - (NTESPullToRefreshView *)pullToRefreshView {
  83. return objc_getAssociatedObject(self, &UIScrollViewPullToRefreshView);
  84. }
  85. - (void)setShowsPullToRefresh:(BOOL)showsPullToRefresh {
  86. self.pullToRefreshView.hidden = !showsPullToRefresh;
  87. if(!showsPullToRefresh) {
  88. if (self.pullToRefreshView.isObserving) {
  89. [self removeObserver:self.pullToRefreshView forKeyPath:@"contentOffset"];
  90. [self removeObserver:self.pullToRefreshView forKeyPath:@"contentSize"];
  91. [self removeObserver:self.pullToRefreshView forKeyPath:@"frame"];
  92. [self.pullToRefreshView resetScrollViewContentInset];
  93. self.pullToRefreshView.isObserving = NO;
  94. }
  95. }
  96. else {
  97. if (!self.pullToRefreshView.isObserving) {
  98. [self addObserver:self.pullToRefreshView forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
  99. [self addObserver:self.pullToRefreshView forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew context:nil];
  100. [self addObserver:self.pullToRefreshView forKeyPath:@"frame" options:NSKeyValueObservingOptionNew context:nil];
  101. self.pullToRefreshView.isObserving = YES;
  102. CGFloat yOrigin = 0;
  103. switch (self.pullToRefreshView.position) {
  104. case NTESPullToRefreshPositionTop:
  105. yOrigin = -NTESPullToRefreshViewHeight;
  106. break;
  107. case NTESPullToRefreshPositionBottom:
  108. yOrigin = self.contentSize.height;
  109. break;
  110. }
  111. self.pullToRefreshView.frame = CGRectMake(0, yOrigin, self.bounds.size.width, NTESPullToRefreshViewHeight);
  112. }
  113. }
  114. }
  115. - (BOOL)showsPullToRefresh {
  116. return !self.pullToRefreshView.hidden;
  117. }
  118. @end
  119. #pragma mark - NTESPullToRefresh
  120. @implementation NTESPullToRefreshView
  121. // public properties
  122. @synthesize pullToRefreshActionHandler, arrowColor, textColor, activityIndicatorViewColor, activityIndicatorViewStyle;
  123. @synthesize state = _state;
  124. @synthesize scrollView = _scrollView;
  125. @synthesize showsPullToRefresh = _showsPullToRefresh;
  126. @synthesize arrow = _arrow;
  127. @synthesize activityIndicatorView = _activityIndicatorView;
  128. @synthesize titleLabel = _titleLabel;
  129. - (id)initWithFrame:(CGRect)frame {
  130. if(self = [super initWithFrame:frame]) {
  131. // default styling values
  132. self.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;
  133. self.textColor = [UIColor darkGrayColor];
  134. self.autoresizingMask = UIViewAutoresizingFlexibleWidth;
  135. self.state = NTESPullToRefreshStateStopped;
  136. self.showsDateLabel = NO;
  137. self.titles = [NSMutableArray arrayWithObjects:NSLocalizedString(@"下拉加载...",),
  138. NSLocalizedString(@"松开刷新...",),
  139. NSLocalizedString(@"加载中...",),
  140. nil];
  141. self.subtitles = [NSMutableArray arrayWithObjects:@"", @"", @"", @"", nil];
  142. self.viewForState = [NSMutableArray arrayWithObjects:@"", @"", @"", @"", nil];
  143. self.wasTriggeredByUser = YES;
  144. }
  145. return self;
  146. }
  147. - (void)willMoveToSuperview:(UIView *)newSuperview {
  148. if (self.superview && newSuperview == nil) {
  149. //use self.superview, not self.scrollView. Why self.scrollView == nil here?
  150. UIScrollView *scrollView = (UIScrollView *)self.superview;
  151. if (scrollView.showsPullToRefresh) {
  152. if (self.isObserving) {
  153. //If enter this branch, it is the moment just before "SVPullToRefreshView's dealloc", so remove observer here
  154. [scrollView removeObserver:self forKeyPath:@"contentOffset"];
  155. [scrollView removeObserver:self forKeyPath:@"contentSize"];
  156. [scrollView removeObserver:self forKeyPath:@"frame"];
  157. self.isObserving = NO;
  158. }
  159. }
  160. }
  161. }
  162. - (void)layoutSubviews {
  163. for(id otherView in self.viewForState) {
  164. if([otherView isKindOfClass:[UIView class]])
  165. [otherView removeFromSuperview];
  166. }
  167. id customView = [self.viewForState objectAtIndex:self.state];
  168. BOOL hasCustomView = [customView isKindOfClass:[UIView class]];
  169. self.titleLabel.hidden = hasCustomView;
  170. self.subtitleLabel.hidden = hasCustomView;
  171. self.arrow.hidden = hasCustomView;
  172. if(hasCustomView) {
  173. [self addSubview:customView];
  174. CGRect viewBounds = [customView bounds];
  175. CGPoint origin = CGPointMake(roundf((self.bounds.size.width-viewBounds.size.width)/2), roundf((self.bounds.size.height-viewBounds.size.height)/2));
  176. [customView setFrame:CGRectMake(origin.x, origin.y, viewBounds.size.width, viewBounds.size.height)];
  177. }
  178. else {
  179. switch (self.state) {
  180. case NTESPullToRefreshStateAll:
  181. case NTESPullToRefreshStateStopped:
  182. self.arrow.alpha = 1;
  183. [self.activityIndicatorView stopAnimating];
  184. switch (self.position) {
  185. case NTESPullToRefreshPositionTop:
  186. [self rotateArrow:0 hide:NO];
  187. break;
  188. case NTESPullToRefreshPositionBottom:
  189. [self rotateArrow:(float)M_PI hide:NO];
  190. break;
  191. }
  192. break;
  193. case NTESPullToRefreshStateTriggered:
  194. switch (self.position) {
  195. case NTESPullToRefreshPositionTop:
  196. [self rotateArrow:(float)M_PI hide:NO];
  197. break;
  198. case NTESPullToRefreshPositionBottom:
  199. [self rotateArrow:0 hide:NO];
  200. break;
  201. }
  202. break;
  203. case NTESPullToRefreshStateLoading:
  204. [self.activityIndicatorView startAnimating];
  205. switch (self.position) {
  206. case NTESPullToRefreshPositionTop:
  207. [self rotateArrow:0 hide:YES];
  208. break;
  209. case NTESPullToRefreshPositionBottom:
  210. [self rotateArrow:(float)M_PI hide:YES];
  211. break;
  212. }
  213. break;
  214. }
  215. CGFloat leftViewWidth = MAX(self.arrow.bounds.size.width,self.activityIndicatorView.bounds.size.width);
  216. CGFloat margin = 10;
  217. CGFloat marginY = 2;
  218. CGFloat labelMaxWidth = self.bounds.size.width - margin - leftViewWidth;
  219. self.titleLabel.text = [self.titles objectAtIndex:self.state];
  220. NSString *subtitle = [self.subtitles objectAtIndex:self.state];
  221. self.subtitleLabel.text = subtitle.length > 0 ? subtitle : nil;
  222. CGSize titleSize = [self.titleLabel.text boundingRectWithSize:CGSizeMake(labelMaxWidth,self.titleLabel.font.lineHeight)
  223. options:(NSStringDrawingUsesFontLeading|NSStringDrawingTruncatesLastVisibleLine|NSStringDrawingUsesLineFragmentOrigin)
  224. attributes:@{NSFontAttributeName: self.titleLabel.font}
  225. context:NULL].size;
  226. CGSize subtitleSize = [self.subtitleLabel.text boundingRectWithSize:CGSizeMake(labelMaxWidth,self.subtitleLabel.font.lineHeight)
  227. options:(NSStringDrawingUsesFontLeading|NSStringDrawingTruncatesLastVisibleLine|NSStringDrawingUsesLineFragmentOrigin)
  228. attributes:@{NSFontAttributeName: self.subtitleLabel.font}
  229. context:NULL].size;
  230. CGFloat maxLabelWidth = MAX(titleSize.width,subtitleSize.width);
  231. CGFloat totalMaxWidth;
  232. if (maxLabelWidth) {
  233. totalMaxWidth = leftViewWidth + margin + maxLabelWidth;
  234. } else {
  235. totalMaxWidth = leftViewWidth + maxLabelWidth;
  236. }
  237. CGFloat labelX = (self.bounds.size.width / 2) - (totalMaxWidth / 2) + leftViewWidth + margin;
  238. if(subtitleSize.height > 0){
  239. CGFloat totalHeight = titleSize.height + subtitleSize.height + marginY;
  240. CGFloat minY = (self.bounds.size.height / 2) - (totalHeight / 2);
  241. CGFloat titleY = minY;
  242. self.titleLabel.frame = CGRectIntegral(CGRectMake(labelX, titleY, titleSize.width, titleSize.height));
  243. self.subtitleLabel.frame = CGRectIntegral(CGRectMake(labelX, titleY + titleSize.height + marginY, subtitleSize.width, subtitleSize.height));
  244. }else{
  245. CGFloat totalHeight = titleSize.height;
  246. CGFloat minY = (self.bounds.size.height / 2) - (totalHeight / 2);
  247. CGFloat titleY = minY;
  248. self.titleLabel.frame = CGRectIntegral(CGRectMake(labelX, titleY, titleSize.width, titleSize.height));
  249. self.subtitleLabel.frame = CGRectIntegral(CGRectMake(labelX, titleY + titleSize.height + marginY, subtitleSize.width, subtitleSize.height));
  250. }
  251. CGFloat arrowX = (self.bounds.size.width / 2) - (totalMaxWidth / 2) + (leftViewWidth - self.arrow.bounds.size.width) / 2;
  252. self.arrow.frame = CGRectMake(arrowX,
  253. (self.bounds.size.height / 2) - (self.arrow.bounds.size.height / 2),
  254. self.arrow.bounds.size.width,
  255. self.arrow.bounds.size.height);
  256. self.activityIndicatorView.center = self.arrow.center;
  257. }
  258. }
  259. #pragma mark - Scroll View
  260. - (void)resetScrollViewContentInset {
  261. UIEdgeInsets currentInsets = self.scrollView.contentInset;
  262. switch (self.position) {
  263. case NTESPullToRefreshPositionTop:
  264. currentInsets.top = self.originalTopInset;
  265. break;
  266. case NTESPullToRefreshPositionBottom:
  267. currentInsets.bottom = self.originalBottomInset;
  268. currentInsets.top = self.originalTopInset;
  269. break;
  270. }
  271. [self setScrollViewContentInset:currentInsets];
  272. }
  273. - (void)setScrollViewContentInsetForLoading {
  274. CGFloat offset = MAX(self.scrollView.contentOffset.y * -1, 0);
  275. UIEdgeInsets currentInsets = self.scrollView.contentInset;
  276. switch (self.position) {
  277. case NTESPullToRefreshPositionTop:
  278. currentInsets.top = MIN(offset, self.originalTopInset + self.bounds.size.height);
  279. break;
  280. case NTESPullToRefreshPositionBottom:
  281. currentInsets.bottom = MIN(offset, self.originalBottomInset + self.bounds.size.height);
  282. break;
  283. }
  284. [self setScrollViewContentInset:currentInsets];
  285. }
  286. - (void)setScrollViewContentInset:(UIEdgeInsets)contentInset {
  287. [UIView animateWithDuration:0.3
  288. delay:0
  289. options:UIViewAnimationOptionAllowUserInteraction|UIViewAnimationOptionBeginFromCurrentState
  290. animations:^{
  291. self.scrollView.contentInset = contentInset;
  292. }
  293. completion:NULL];
  294. }
  295. #pragma mark - Observing
  296. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
  297. if([keyPath isEqualToString:@"contentOffset"])
  298. [self scrollViewDidScroll:[[change valueForKey:NSKeyValueChangeNewKey] CGPointValue]];
  299. else if([keyPath isEqualToString:@"contentSize"]) {
  300. [self layoutSubviews];
  301. CGFloat yOrigin;
  302. switch (self.position) {
  303. case NTESPullToRefreshPositionTop:
  304. yOrigin = -NTESPullToRefreshViewHeight;
  305. break;
  306. case NTESPullToRefreshPositionBottom:
  307. yOrigin = MAX(self.scrollView.contentSize.height, self.scrollView.bounds.size.height);
  308. break;
  309. }
  310. self.frame = CGRectMake(0, yOrigin, self.bounds.size.width, NTESPullToRefreshViewHeight);
  311. }
  312. else if([keyPath isEqualToString:@"frame"])
  313. [self layoutSubviews];
  314. }
  315. - (void)scrollViewDidScroll:(CGPoint)contentOffset {
  316. if(self.state != NTESPullToRefreshStateLoading) {
  317. CGFloat scrollOffsetThreshold = 0;
  318. switch (self.position) {
  319. case NTESPullToRefreshPositionTop:
  320. scrollOffsetThreshold = self.frame.origin.y - self.originalTopInset;
  321. break;
  322. case NTESPullToRefreshPositionBottom:
  323. scrollOffsetThreshold = MAX(self.scrollView.contentSize.height - self.scrollView.bounds.size.height, 0.0f) + self.bounds.size.height + self.originalBottomInset;
  324. break;
  325. }
  326. if(!self.scrollView.isDragging && self.state == NTESPullToRefreshStateTriggered)
  327. self.state = NTESPullToRefreshStateLoading;
  328. else if(contentOffset.y < scrollOffsetThreshold && self.scrollView.isDragging && self.state == NTESPullToRefreshStateStopped && self.position == NTESPullToRefreshPositionTop)
  329. self.state = NTESPullToRefreshStateTriggered;
  330. else if(contentOffset.y >= scrollOffsetThreshold && self.state != NTESPullToRefreshStateStopped && self.position == NTESPullToRefreshPositionTop)
  331. self.state = NTESPullToRefreshStateStopped;
  332. else if(contentOffset.y > scrollOffsetThreshold && self.scrollView.isDragging && self.state == NTESPullToRefreshStateStopped && self.position == NTESPullToRefreshPositionBottom)
  333. self.state = NTESPullToRefreshStateTriggered;
  334. else if(contentOffset.y <= scrollOffsetThreshold && self.state != NTESPullToRefreshStateStopped && self.position == NTESPullToRefreshPositionBottom)
  335. self.state = NTESPullToRefreshStateStopped;
  336. } else {
  337. CGFloat offset;
  338. UIEdgeInsets contentInset;
  339. switch (self.position) {
  340. case NTESPullToRefreshPositionTop:
  341. offset = MAX(self.scrollView.contentOffset.y * -1, 0.0f);
  342. offset = MIN(offset, self.originalTopInset + self.bounds.size.height);
  343. contentInset = self.scrollView.contentInset;
  344. self.scrollView.contentInset = UIEdgeInsetsMake(offset, contentInset.left, contentInset.bottom, contentInset.right);
  345. break;
  346. case NTESPullToRefreshPositionBottom:
  347. if (self.scrollView.contentSize.height >= self.scrollView.bounds.size.height) {
  348. offset = MAX(self.scrollView.contentSize.height - self.scrollView.bounds.size.height + self.bounds.size.height, 0.0f);
  349. offset = MIN(offset, self.originalBottomInset + self.bounds.size.height);
  350. contentInset = self.scrollView.contentInset;
  351. self.scrollView.contentInset = UIEdgeInsetsMake(contentInset.top, contentInset.left, offset, contentInset.right);
  352. } else if (self.wasTriggeredByUser) {
  353. offset = MIN(self.bounds.size.height, self.originalBottomInset + self.bounds.size.height);
  354. contentInset = self.scrollView.contentInset;
  355. self.scrollView.contentInset = UIEdgeInsetsMake(-offset, contentInset.left, contentInset.bottom, contentInset.right);
  356. }
  357. break;
  358. }
  359. }
  360. }
  361. #pragma mark - Getters
  362. - (NTESPullToRefreshArrow *)arrow {
  363. if(!_arrow) {
  364. _arrow = [[NTESPullToRefreshArrow alloc]initWithFrame:CGRectMake(0, self.bounds.size.height-54, 22, 48)];
  365. _arrow.backgroundColor = [UIColor clearColor];
  366. [self addSubview:_arrow];
  367. }
  368. return _arrow;
  369. }
  370. - (UIActivityIndicatorView *)activityIndicatorView {
  371. if(!_activityIndicatorView) {
  372. _activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
  373. _activityIndicatorView.hidesWhenStopped = YES;
  374. [self addSubview:_activityIndicatorView];
  375. }
  376. return _activityIndicatorView;
  377. }
  378. - (UILabel *)titleLabel {
  379. if(!_titleLabel) {
  380. _titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 210, 20)];
  381. _titleLabel.text = NSLocalizedString(@"Pull to refresh...",);
  382. _titleLabel.font = [UIFont boldSystemFontOfSize:14];
  383. _titleLabel.backgroundColor = [UIColor clearColor];
  384. _titleLabel.textColor = textColor;
  385. [self addSubview:_titleLabel];
  386. }
  387. return _titleLabel;
  388. }
  389. - (UILabel *)subtitleLabel {
  390. if(!_subtitleLabel) {
  391. _subtitleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 210, 20)];
  392. _subtitleLabel.font = [UIFont systemFontOfSize:12];
  393. _subtitleLabel.backgroundColor = [UIColor clearColor];
  394. _subtitleLabel.textColor = textColor;
  395. [self addSubview:_subtitleLabel];
  396. }
  397. return _subtitleLabel;
  398. }
  399. - (UILabel *)dateLabel {
  400. return self.showsDateLabel ? self.subtitleLabel : nil;
  401. }
  402. - (UIColor *)arrowColor {
  403. return self.arrow.arrowColor; // pass through
  404. }
  405. - (UIColor *)textColor {
  406. return self.titleLabel.textColor;
  407. }
  408. - (UIColor *)activityIndicatorViewColor {
  409. return self.activityIndicatorView.color;
  410. }
  411. - (UIActivityIndicatorViewStyle)activityIndicatorViewStyle {
  412. return self.activityIndicatorView.activityIndicatorViewStyle;
  413. }
  414. #pragma mark - Setters
  415. - (void)setArrowColor:(UIColor *)newArrowColor {
  416. self.arrow.arrowColor = newArrowColor; // pass through
  417. [self.arrow setNeedsDisplay];
  418. }
  419. - (void)setTitle:(NSString *)title forState:(NTESPullToRefreshState)state {
  420. if(!title)
  421. title = @"";
  422. if(state == NTESPullToRefreshStateAll)
  423. [self.titles replaceObjectsInRange:NSMakeRange(0, 3) withObjectsFromArray:@[title, title, title]];
  424. else
  425. [self.titles replaceObjectAtIndex:state withObject:title];
  426. [self setNeedsLayout];
  427. }
  428. - (void)setSubtitle:(NSString *)subtitle forState:(NTESPullToRefreshState)state {
  429. if(!subtitle)
  430. subtitle = @"";
  431. if(state == NTESPullToRefreshStateAll)
  432. [self.subtitles replaceObjectsInRange:NSMakeRange(0, 3) withObjectsFromArray:@[subtitle, subtitle, subtitle]];
  433. else
  434. [self.subtitles replaceObjectAtIndex:state withObject:subtitle];
  435. [self setNeedsLayout];
  436. }
  437. - (void)setCustomView:(UIView *)view forState:(NTESPullToRefreshState)state {
  438. id viewPlaceholder = view;
  439. if(!viewPlaceholder)
  440. viewPlaceholder = @"";
  441. if(state == NTESPullToRefreshStateAll)
  442. [self.viewForState replaceObjectsInRange:NSMakeRange(0, 3) withObjectsFromArray:@[viewPlaceholder, viewPlaceholder, viewPlaceholder]];
  443. else
  444. [self.viewForState replaceObjectAtIndex:state withObject:viewPlaceholder];
  445. [self setNeedsLayout];
  446. }
  447. - (void)setTextColor:(UIColor *)newTextColor {
  448. textColor = newTextColor;
  449. self.titleLabel.textColor = newTextColor;
  450. self.subtitleLabel.textColor = newTextColor;
  451. }
  452. - (void)setActivityIndicatorViewColor:(UIColor *)color {
  453. self.activityIndicatorView.color = color;
  454. }
  455. - (void)setActivityIndicatorViewStyle:(UIActivityIndicatorViewStyle)viewStyle {
  456. self.activityIndicatorView.activityIndicatorViewStyle = viewStyle;
  457. }
  458. #pragma mark -
  459. - (void)startAnimating{
  460. switch (self.position) {
  461. case NTESPullToRefreshPositionTop:
  462. if(fequalzero(self.scrollView.contentOffset.y)) {
  463. [self.scrollView setContentOffset:CGPointMake(self.scrollView.contentOffset.x, -self.frame.size.height) animated:YES];
  464. self.wasTriggeredByUser = NO;
  465. }
  466. else
  467. self.wasTriggeredByUser = YES;
  468. break;
  469. case NTESPullToRefreshPositionBottom:
  470. if((fequalzero(self.scrollView.contentOffset.y) && self.scrollView.contentSize.height < self.scrollView.bounds.size.height)
  471. || fequal(self.scrollView.contentOffset.y, self.scrollView.contentSize.height - self.scrollView.bounds.size.height)) {
  472. [self.scrollView setContentOffset:(CGPoint){.y = MAX(self.scrollView.contentSize.height - self.scrollView.bounds.size.height, 0.0f) + self.frame.size.height} animated:YES];
  473. self.wasTriggeredByUser = NO;
  474. }
  475. else
  476. self.wasTriggeredByUser = YES;
  477. break;
  478. }
  479. self.state = NTESPullToRefreshStateLoading;
  480. }
  481. - (void)stopAnimating {
  482. self.state = NTESPullToRefreshStateStopped;
  483. switch (self.position) {
  484. case NTESPullToRefreshPositionTop:
  485. if(!self.wasTriggeredByUser)
  486. [self.scrollView setContentOffset:CGPointMake(self.scrollView.contentOffset.x, -self.originalTopInset) animated:YES];
  487. break;
  488. case NTESPullToRefreshPositionBottom:
  489. if(!self.wasTriggeredByUser)
  490. [self.scrollView setContentOffset:CGPointMake(self.scrollView.contentOffset.x, self.scrollView.contentSize.height - self.scrollView.bounds.size.height + self.originalBottomInset) animated:YES];
  491. break;
  492. }
  493. }
  494. - (void)setState:(NTESPullToRefreshState)newState {
  495. if(_state == newState)
  496. return;
  497. NTESPullToRefreshState previousState = _state;
  498. _state = newState;
  499. [self setNeedsLayout];
  500. [self layoutIfNeeded];
  501. switch (newState) {
  502. case NTESPullToRefreshStateAll:
  503. case NTESPullToRefreshStateStopped:
  504. [self resetScrollViewContentInset];
  505. break;
  506. case NTESPullToRefreshStateTriggered:
  507. break;
  508. case NTESPullToRefreshStateLoading:
  509. [self setScrollViewContentInsetForLoading];
  510. if(previousState == NTESPullToRefreshStateTriggered && pullToRefreshActionHandler)
  511. pullToRefreshActionHandler();
  512. break;
  513. }
  514. }
  515. - (void)rotateArrow:(float)degrees hide:(BOOL)hide {
  516. [UIView animateWithDuration:0.2 delay:0 options:UIViewAnimationOptionAllowUserInteraction animations:^{
  517. self.arrow.layer.transform = CATransform3DMakeRotation(degrees, 0, 0, 1);
  518. self.arrow.layer.opacity = !hide;
  519. //[self.arrow setNeedsDisplay];//ios 4
  520. } completion:NULL];
  521. }
  522. @end
  523. #pragma mark - NTESPullToRefreshArrow
  524. @implementation NTESPullToRefreshArrow
  525. @synthesize arrowColor;
  526. - (UIColor *)arrowColor {
  527. if (arrowColor) return arrowColor;
  528. return [UIColor grayColor]; // default Color
  529. }
  530. - (void)drawRect:(CGRect)rect {
  531. CGContextRef c = UIGraphicsGetCurrentContext();
  532. // the rects above the arrow
  533. CGContextAddRect(c, CGRectMake(5, 0, 12, 4)); // to-do: use dynamic points
  534. CGContextAddRect(c, CGRectMake(5, 6, 12, 4)); // currently fixed size: 22 x 48pt
  535. CGContextAddRect(c, CGRectMake(5, 12, 12, 4));
  536. CGContextAddRect(c, CGRectMake(5, 18, 12, 4));
  537. CGContextAddRect(c, CGRectMake(5, 24, 12, 4));
  538. CGContextAddRect(c, CGRectMake(5, 30, 12, 4));
  539. // the arrow
  540. CGContextMoveToPoint(c, 0, 34);
  541. CGContextAddLineToPoint(c, 11, 48);
  542. CGContextAddLineToPoint(c, 22, 34);
  543. CGContextAddLineToPoint(c, 0, 34);
  544. CGContextClosePath(c);
  545. CGContextSaveGState(c);
  546. CGContextClip(c);
  547. // Gradient Declaration
  548. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  549. CGFloat alphaGradientLocations[] = {0, 0.8f};
  550. CGGradientRef alphaGradient = nil;
  551. if([[[UIDevice currentDevice] systemVersion]floatValue] >= 5){
  552. NSArray* alphaGradientColors = [NSArray arrayWithObjects:
  553. (id)[self.arrowColor colorWithAlphaComponent:0].CGColor,
  554. (id)[self.arrowColor colorWithAlphaComponent:1].CGColor,
  555. nil];
  556. alphaGradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)alphaGradientColors, alphaGradientLocations);
  557. }else{
  558. const CGFloat * components = CGColorGetComponents([self.arrowColor CGColor]);
  559. size_t numComponents = CGColorGetNumberOfComponents([self.arrowColor CGColor]);
  560. CGFloat colors[8];
  561. switch(numComponents){
  562. case 2:{
  563. colors[0] = colors[4] = components[0];
  564. colors[1] = colors[5] = components[0];
  565. colors[2] = colors[6] = components[0];
  566. break;
  567. }
  568. case 4:{
  569. colors[0] = colors[4] = components[0];
  570. colors[1] = colors[5] = components[1];
  571. colors[2] = colors[6] = components[2];
  572. break;
  573. }
  574. }
  575. colors[3] = 0;
  576. colors[7] = 1;
  577. alphaGradient = CGGradientCreateWithColorComponents(colorSpace,colors,alphaGradientLocations,2);
  578. }
  579. CGContextDrawLinearGradient(c, alphaGradient, CGPointZero, CGPointMake(0, rect.size.height), 0);
  580. CGContextRestoreGState(c);
  581. CGGradientRelease(alphaGradient);
  582. CGColorSpaceRelease(colorSpace);
  583. }
  584. @end