RCTRefreshControl.m 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. /*
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. */
  7. #import "RCTRefreshControl.h"
  8. #import "RCTRefreshableProtocol.h"
  9. #import "RCTUtils.h"
  10. @interface RCTRefreshControl () <RCTRefreshableProtocol>
  11. @end
  12. @implementation RCTRefreshControl {
  13. BOOL _isInitialRender;
  14. BOOL _currentRefreshingState;
  15. UInt64 _currentRefreshingStateClock;
  16. UInt64 _currentRefreshingStateTimestamp;
  17. BOOL _refreshingProgrammatically;
  18. NSString *_title;
  19. UIColor *_titleColor;
  20. }
  21. - (instancetype)init
  22. {
  23. if ((self = [super init])) {
  24. [self addTarget:self action:@selector(refreshControlValueChanged) forControlEvents:UIControlEventValueChanged];
  25. _currentRefreshingStateClock = 1;
  26. _currentRefreshingStateTimestamp = 0;
  27. _isInitialRender = true;
  28. _currentRefreshingState = false;
  29. }
  30. return self;
  31. }
  32. RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : (NSCoder *)aDecoder)
  33. - (void)layoutSubviews
  34. {
  35. [super layoutSubviews];
  36. // Fix for bug #7976
  37. // TODO: Remove when updating to use iOS 10 refreshControl UIScrollView prop.
  38. if (self.backgroundColor == nil) {
  39. self.backgroundColor = [UIColor clearColor];
  40. }
  41. // If the control is refreshing when mounted we need to call
  42. // beginRefreshing in layoutSubview or it doesn't work.
  43. if (_currentRefreshingState && _isInitialRender) {
  44. [self beginRefreshingProgrammatically];
  45. }
  46. _isInitialRender = false;
  47. }
  48. - (void)beginRefreshingProgrammatically
  49. {
  50. UInt64 beginRefreshingTimestamp = _currentRefreshingStateTimestamp;
  51. _refreshingProgrammatically = YES;
  52. // When using begin refreshing we need to adjust the ScrollView content offset manually.
  53. UIScrollView *scrollView = (UIScrollView *)self.superview;
  54. // Fix for bug #24855
  55. [self sizeToFit];
  56. CGPoint offset = {scrollView.contentOffset.x, scrollView.contentOffset.y - self.frame.size.height};
  57. // `beginRefreshing` must be called after the animation is done. This is why it is impossible
  58. // to use `setContentOffset` with `animated:YES`.
  59. [UIView animateWithDuration:0.25
  60. delay:0
  61. options:UIViewAnimationOptionBeginFromCurrentState
  62. animations:^(void) {
  63. [scrollView setContentOffset:offset];
  64. }
  65. completion:^(__unused BOOL finished) {
  66. if (beginRefreshingTimestamp == self->_currentRefreshingStateTimestamp) {
  67. [super beginRefreshing];
  68. [self setCurrentRefreshingState:super.refreshing];
  69. }
  70. }];
  71. }
  72. - (void)endRefreshingProgrammatically
  73. {
  74. // The contentOffset of the scrollview MUST be greater than the contentInset before calling
  75. // endRefreshing otherwise the next pull to refresh will not work properly.
  76. UIScrollView *scrollView = (UIScrollView *)self.superview;
  77. if (_refreshingProgrammatically && scrollView.contentOffset.y < -scrollView.contentInset.top) {
  78. UInt64 endRefreshingTimestamp = _currentRefreshingStateTimestamp;
  79. CGPoint offset = {scrollView.contentOffset.x, -scrollView.contentInset.top};
  80. [UIView animateWithDuration:0.25
  81. delay:0
  82. options:UIViewAnimationOptionBeginFromCurrentState
  83. animations:^(void) {
  84. [scrollView setContentOffset:offset];
  85. }
  86. completion:^(__unused BOOL finished) {
  87. if (endRefreshingTimestamp == self->_currentRefreshingStateTimestamp) {
  88. [super endRefreshing];
  89. [self setCurrentRefreshingState:super.refreshing];
  90. }
  91. }];
  92. } else {
  93. [super endRefreshing];
  94. }
  95. }
  96. - (NSString *)title
  97. {
  98. return _title;
  99. }
  100. - (void)setTitle:(NSString *)title
  101. {
  102. _title = title;
  103. [self _updateTitle];
  104. }
  105. - (void)setTitleColor:(UIColor *)color
  106. {
  107. _titleColor = color;
  108. [self _updateTitle];
  109. }
  110. - (void)_updateTitle
  111. {
  112. if (!_title) {
  113. return;
  114. }
  115. NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
  116. if (_titleColor) {
  117. attributes[NSForegroundColorAttributeName] = _titleColor;
  118. }
  119. self.attributedTitle = [[NSAttributedString alloc] initWithString:_title attributes:attributes];
  120. }
  121. - (void)setRefreshing:(BOOL)refreshing
  122. {
  123. if (_currentRefreshingState != refreshing) {
  124. [self setCurrentRefreshingState:refreshing];
  125. if (refreshing) {
  126. if (!_isInitialRender) {
  127. [self beginRefreshingProgrammatically];
  128. }
  129. } else {
  130. [self endRefreshingProgrammatically];
  131. }
  132. }
  133. }
  134. - (void)setCurrentRefreshingState:(BOOL)refreshing
  135. {
  136. _currentRefreshingState = refreshing;
  137. _currentRefreshingStateTimestamp = _currentRefreshingStateClock++;
  138. }
  139. - (void)refreshControlValueChanged
  140. {
  141. [self setCurrentRefreshingState:super.refreshing];
  142. _refreshingProgrammatically = NO;
  143. if (_onRefresh) {
  144. _onRefresh(nil);
  145. }
  146. }
  147. @end