SVPullToRefresh 的接口设计的很简明
@interface UIScrollView (SVPullToRefresh)
- (void)addPullToRefreshWithActionHandler:(void (^)(void))actionHandler;
- (void)triggerPullToRefresh;
@property (nonatomic, strong, readonly) SVPullToRefreshView *pullToRefreshView;
@property (nonatomic, assign) BOOL showsPullToRefresh;
@end
只暴漏了两个方法及两个属性。
只需一行代码就可以为scrollView添加下拉刷新功能。
[_tableView addPullToRefreshWithActionHandler:^{
//代码块
}];
接下来我们来说下SVPullToRefresh实现的原理,及源码的一些注意细节。
默认情况下,当scrollView下拉时,显示一个View,pullToRefresh... ,这个View在调用addPull...时就被添加到了ScrollView中。
- (void)addPullToRefreshWithActionHandler:(void (^)(void))actionHandler{
if(!self.pullToRefreshView) {
CGFloat yOrigin = -SVPullToRefreshViewHeight;
CGFloat width = [UIScreen mainScreen].bounds.size.width;
SVPullToRefreshView *view = [[SVPullToRefreshView alloc] initWithFrame:CGRectMake(0, yOrigin, width, SVPullToRefreshViewHeight)];
view.pullToRefreshActionHandler = actionHandler;
view.scrollView = self;
[self addSubview:view];
view.originalTopInset = self.contentInset.top;
view.originalBottomInset = self.contentInset.bottom;
self.pullToRefreshView = view;
self.showsPullToRefresh = YES;
}
}
KVO - ( NSKeyValueObserving )(其他篇章会讲,或者自行搜索知识点)
我们知道 ScrollView 有几个重要的属性:
CGSize contentSize 是scrollview可以滚动的区域
CGPoint contentOffset 是scrollview当前显示区域顶点相对于frame顶点的偏移量
UIEdgeInsets contentInset 是scrollview的contentview的顶点相对于scrollview的位置
当scrollView 滚动时,contentOffSet的值会改变,就是利用了这点,以及 KVO 的observer(默认情况下showsPullToRefresh = YES 代码addPull...中)
- (void)setShowsPullToRefresh:(BOOL)showsPullToRefresh {
self.pullToRefreshView.hidden = !showsPullToRefresh;
if(showsPullToRefresh) {
if (!self.pullToRefreshView.isObserving) {
[self addObserver:self.pullToRefreshView forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
[self addObserver:self.pullToRefreshView forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew context:nil];
[self addObserver:self.pullToRefreshView forKeyPath:@"frame" options:NSKeyValueObservingOptionNew context:nil];
self.pullToRefreshView.isObserving = YES;
CGFloat yOrigin = -SVPullToRefreshViewHeight;
self.pullToRefreshView.frame = CGRectMake(0, yOrigin, self.bounds.size.width, SVPullToRefreshViewHeight);
}
}
else {
if (self.pullToRefreshView.isObserving) {
[self removeObserver:self.pullToRefreshView forKeyPath:@"contentOffset"];
[self removeObserver:self.pullToRefreshView forKeyPath:@"contentSize"];
[self removeObserver:self.pullToRefreshView forKeyPath:@"frame"];
[self.pullToRefreshView resetScrollViewContentInset];
self.pullToRefreshView.isObserving = NO;
}
}
}
由于observer被指定给了pullToRefreshView,所以在view中找到相应的observe方法。
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if([keyPath isEqualToString:@"contentOffset"])
[self scrollViewDidScroll:[[change valueForKey:NSKeyValueChangeNewKey] CGPointValue]];
else if([keyPath isEqualToString:@"contentSize"]) {
[self layoutSubviews];
CGFloat yOrigin = -SVPullToRefreshViewHeight;
self.frame = CGRectMake(0, yOrigin, self.bounds.size.width, SVPullToRefreshViewHeight);
}
else if([keyPath isEqualToString:@"frame"])
[self layoutSubviews];
}
当contentOffset的值改变(及下拉时),就会调用相应的 scrollViewDidScroll 方法,,来实现各种状态的显示。
- (void)scrollViewDidScroll:(CGPoint)contentOffset {
if(self.state != SVPullToRefreshStateLoading) {
CGFloat scrollOffsetThreshold = self.frame.origin.y-self.originalTopInset;
if(!self.scrollView.isDragging && self.state == SVPullToRefreshStateTriggered)
self.state = SVPullToRefreshStateLoading;
else if(contentOffset.y < scrollOffsetThreshold && self.scrollView.isDragging && self.state == SVPullToRefreshStateStopped)
self.state = SVPullToRefreshStateTriggered;
else if(contentOffset.y >= scrollOffsetThreshold && self.state != SVPullToRefreshStateStopped)
self.state = SVPullToRefreshStateStopped;
} else {
CGFloat offset = MAX(self.scrollView.contentOffset.y * -1, 0.0f);
offset = MIN(offset, self.originalTopInset + self.bounds.size.height);
UIEdgeInsets contentInset = self.scrollView.contentInset;
self.scrollView.contentInset = UIEdgeInsetsMake(offset, contentInset.left, contentInset.bottom, contentInset.right);
}
}
基本流程如上:
下面来说一下代码中德一些细节点:
(时间到了,要先睡觉去了,未完待续....)
objc_setAssociatedObject(
- (void)setPullToRefreshView:(SVPullToRefreshView *)pullToRefreshView {
[self willChangeValueForKey:@"SVPullToRefreshView"];
objc_setAssociatedObject(self, &UIScrollViewPullToRefreshView,
pullToRefreshView,
OBJC_ASSOCIATION_ASSIGN);
[self didChangeValueForKey:@"SVPullToRefreshView"];
}
未完待续