You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
433 lines
16 KiB
433 lines
16 KiB
// |
|
// XHRefreshControl.m |
|
// MessageDisplayExample |
|
// |
|
// Created by 曾 宪华 on 14-6-6. |
|
// Copyright (c) 2014年 曾宪华 开发团队(http://iyilunba.com ) 本人QQ:543413507 本人QQ群(142557668). All rights reserved. |
|
// |
|
|
|
#import "XHRefreshControl.h" |
|
|
|
#import "XHRefreshView.h" |
|
#import "XHLoadMoreView.h" |
|
|
|
#define fequal(a,b) (fabs((a) - (b)) < FLT_EPSILON) |
|
#define fequalzero(a) (fabs(a) < FLT_EPSILON) |
|
|
|
#define kXHDefaultRefreshTotalPixels 60 |
|
|
|
#define kXHAutoLoadMoreRefreshedCount 5 |
|
|
|
|
|
typedef NS_ENUM(NSInteger, XHRefreshState) { |
|
XHRefreshStatePulling = 0, |
|
XHRefreshStateNormal = 1, |
|
XHRefreshStateLoading = 2, |
|
XHRefreshStateStopped = 3, |
|
}; |
|
|
|
@interface XHRefreshControl () |
|
|
|
@property (nonatomic, weak) id <XHRefreshControlDelegate> delegate; |
|
|
|
// getter |
|
@property (nonatomic, strong) XHRefreshView *refreshView; |
|
@property (nonatomic, strong) XHLoadMoreView *loadMoreView; |
|
@property (nonatomic, assign) BOOL isPullDownRefreshed; |
|
@property (nonatomic, assign) BOOL isLoadMoreRefreshed; |
|
@property (nonatomic, assign) CGFloat refreshTotalPixels; |
|
@property (nonatomic, assign) NSInteger autoLoadMoreRefreshedCount; |
|
@property (nonatomic, assign) XHRefreshViewLayerType refreshViewLayerType; |
|
|
|
// recoder |
|
@property (nonatomic, readwrite) CGFloat originalTopInset; |
|
|
|
// target scrollview |
|
@property (nonatomic, strong) UIScrollView *scrollView; |
|
|
|
// target state |
|
@property (nonatomic, assign) XHRefreshState refreshState; |
|
|
|
// controll the loading and auto loading |
|
@property (nonatomic, assign) NSInteger loadMoreRefreshedCount; |
|
@property (nonatomic, assign) BOOL pullDownRefreshing; |
|
@property (nonatomic, assign) BOOL loadMoreRefreshing; |
|
|
|
|
|
@end |
|
|
|
@implementation XHRefreshControl |
|
|
|
#pragma mark - Pull Down Refreshing Method |
|
|
|
- (void)startPullDownRefreshing { |
|
if (self.isPullDownRefreshed) { |
|
self.pullDownRefreshing = YES; |
|
|
|
NSDate *date = [self.delegate lastUpdateTime]; |
|
if ([date isKindOfClass:[NSDate class]] || date) { |
|
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; |
|
|
|
[dateFormatter setDateFormat:@"MM-dd HH:mm"]; |
|
|
|
NSString *destDateString = [dateFormatter stringFromDate:date]; |
|
self.refreshView.timeLabel.text = [NSString stringWithFormat:@"上次刷新:%@", destDateString]; |
|
} |
|
|
|
self.refreshState = XHRefreshStatePulling; |
|
|
|
self.refreshState = XHRefreshStateLoading; |
|
} |
|
} |
|
|
|
- (void)animationRefreshCircleView { |
|
if (self.refreshView.refreshCircleView.offsetY != kXHDefaultRefreshTotalPixels - kXHRefreshCircleViewHeight) { |
|
self.refreshView.refreshCircleView.offsetY = kXHDefaultRefreshTotalPixels - kXHRefreshCircleViewHeight; |
|
[self.refreshView.refreshCircleView setNeedsDisplay]; |
|
} |
|
// 先去除所有动画 |
|
[self.refreshView.refreshCircleView.layer removeAllAnimations]; |
|
// 添加旋转的动画 |
|
[self.refreshView.refreshCircleView.layer addAnimation:[XHRefreshCircleView repeatRotateAnimation] forKey:@"rotateAnimation"]; |
|
|
|
[self callBeginPullDownRefreshing]; |
|
} |
|
|
|
- (void)callBeginPullDownRefreshing { |
|
self.loadMoreRefreshedCount = 0; |
|
[self.delegate beginPullDownRefreshing]; |
|
} |
|
|
|
- (void)endPullDownRefreshing { |
|
if (self.isPullDownRefreshed) { |
|
self.pullDownRefreshing = NO; |
|
self.refreshState = XHRefreshStateStopped; |
|
|
|
[self resetScrollViewContentInset]; |
|
} |
|
} |
|
|
|
#pragma mark - Load More Refreshing Method |
|
|
|
- (void)startLoadMoreRefreshing { |
|
if (self.isLoadMoreRefreshed) { |
|
if (self.loadMoreRefreshedCount < self.autoLoadMoreRefreshedCount) { |
|
[self callBeginLoadMoreRefreshing]; |
|
} else { |
|
[self.loadMoreView configuraManualState]; |
|
} |
|
} |
|
} |
|
|
|
- (void)callBeginLoadMoreRefreshing { |
|
if (self.loadMoreRefreshing) |
|
return; |
|
self.loadMoreRefreshing = YES; |
|
self.loadMoreRefreshedCount ++; |
|
self.refreshState = XHRefreshStateLoading; |
|
[self.loadMoreView startLoading]; |
|
[self.delegate beginLoadMoreRefreshing]; |
|
} |
|
|
|
- (void)endLoadMoreRefresing { |
|
if (self.isLoadMoreRefreshed) { |
|
self.loadMoreRefreshing = NO; |
|
self.refreshState = XHRefreshStateNormal; |
|
[self.loadMoreView endLoading]; |
|
} |
|
} |
|
|
|
- (void)loadMoreButtonClciked:(UIButton *)sender { |
|
[self callBeginLoadMoreRefreshing]; |
|
} |
|
|
|
#pragma mark - Scroll View |
|
|
|
- (void)resetScrollViewContentInset { |
|
UIEdgeInsets contentInset = self.scrollView.contentInset; |
|
contentInset.top = self.originalTopInset; |
|
[UIView animateWithDuration:0.3f animations:^{ |
|
[self.scrollView setContentInset:contentInset]; |
|
} completion:^(BOOL finished) { |
|
|
|
self.refreshState = XHRefreshStateNormal; |
|
|
|
self.refreshView.refreshCircleView.offsetY = 0; |
|
[self.refreshView.refreshCircleView setNeedsDisplay]; |
|
|
|
if (self.refreshView.refreshCircleView) { |
|
[self.refreshView.refreshCircleView.layer removeAllAnimations]; |
|
} |
|
}]; |
|
} |
|
|
|
- (void)setScrollViewContentInset:(UIEdgeInsets)contentInset { |
|
[UIView animateWithDuration:0.3 |
|
delay:0 |
|
options:UIViewAnimationOptionAllowUserInteraction|UIViewAnimationOptionBeginFromCurrentState |
|
animations:^{ |
|
self.scrollView.contentInset = contentInset; |
|
} |
|
completion:^(BOOL finished) { |
|
if (self.refreshState == XHRefreshStateStopped) { |
|
self.refreshState = XHRefreshStateNormal; |
|
|
|
if (self.refreshView.refreshCircleView) { |
|
[self.refreshView.refreshCircleView.layer removeAllAnimations]; |
|
} |
|
} |
|
}]; |
|
} |
|
|
|
- (void)setScrollViewContentInsetForLoading { |
|
UIEdgeInsets currentInsets = self.scrollView.contentInset; |
|
currentInsets.top = self.refreshTotalPixels; |
|
[self setScrollViewContentInset:currentInsets]; |
|
} |
|
|
|
- (void)setScrollViewContentInsetForLoadMore { |
|
if (self.pullDownRefreshing) |
|
return; |
|
UIEdgeInsets currentInsets = self.scrollView.contentInset; |
|
currentInsets.bottom = kXHLoadMoreViewHeight; |
|
[self setScrollViewContentInset:currentInsets]; |
|
} |
|
|
|
#pragma mark - Propertys |
|
|
|
- (XHRefreshView *)refreshView { |
|
if (!_refreshView) { |
|
_refreshView = [[XHRefreshView alloc] initWithFrame:CGRectMake(0, (self.refreshViewLayerType == XHRefreshViewLayerTypeOnScrollViews ? -kXHDefaultRefreshTotalPixels : self.originalTopInset), CGRectGetWidth([[UIScreen mainScreen] bounds]), kXHDefaultRefreshTotalPixels)]; |
|
_refreshView.backgroundColor = [UIColor clearColor]; |
|
_refreshView.refreshCircleView.heightBeginToRefresh = kXHDefaultRefreshTotalPixels - kXHRefreshCircleViewHeight; |
|
_refreshView.refreshCircleView.offsetY = 0; |
|
_refreshView.refreshCircleView.isRefreshViewOnTableView = self.refreshViewLayerType; |
|
} |
|
return _refreshView; |
|
} |
|
|
|
- (XHLoadMoreView *)loadMoreView { |
|
if (!_loadMoreView) { |
|
_loadMoreView = [[XHLoadMoreView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth([[UIScreen mainScreen] bounds]), kXHLoadMoreViewHeight)]; |
|
[_loadMoreView.loadMoreButton addTarget:self action:@selector(loadMoreButtonClciked:) forControlEvents:UIControlEventTouchUpInside]; |
|
} |
|
return _loadMoreView; |
|
} |
|
|
|
#pragma mark - Getter Method |
|
|
|
- (BOOL)isLoading { |
|
return [self.delegate isLoading]; |
|
} |
|
|
|
- (BOOL)isPullDownRefreshed { |
|
BOOL pullDowned = YES; |
|
if ([self.delegate respondsToSelector:@selector(isPullDownRefreshed)]) { |
|
pullDowned = [self.delegate isPullDownRefreshed]; |
|
return pullDowned; |
|
} |
|
return YES; |
|
} |
|
|
|
- (BOOL)isLoadMoreRefreshed { |
|
BOOL loadMored = YES; |
|
if ([self.delegate respondsToSelector:@selector(isLoadMoreRefreshed)]) { |
|
loadMored = [self.delegate isLoadMoreRefreshed]; |
|
return loadMored; |
|
} |
|
return loadMored; |
|
} |
|
|
|
- (CGFloat)refreshTotalPixels { |
|
return kXHDefaultRefreshTotalPixels + [self getAdaptorHeight]; |
|
} |
|
|
|
- (CGFloat)getAdaptorHeight { |
|
if ([self.delegate respondsToSelector:@selector(keepiOS7NewApiCharacter)]) { |
|
return ([self.delegate keepiOS7NewApiCharacter] ? 64 : 0); |
|
} else { |
|
return 0; |
|
} |
|
} |
|
|
|
- (NSInteger)autoLoadMoreRefreshedCount { |
|
if ([self.delegate respondsToSelector:@selector(autoLoadMoreRefreshedCountConverManual)]) { |
|
return [self.delegate autoLoadMoreRefreshedCountConverManual]; |
|
} |
|
return kXHAutoLoadMoreRefreshedCount; |
|
} |
|
|
|
- (XHRefreshViewLayerType)refreshViewLayerType { |
|
XHRefreshViewLayerType currentRefreshViewLayerType = XHRefreshViewLayerTypeOnScrollViews; |
|
if ([self.delegate respondsToSelector:@selector(refreshViewLayerType)]) { |
|
currentRefreshViewLayerType = [self.delegate refreshViewLayerType]; |
|
} |
|
return currentRefreshViewLayerType; |
|
} |
|
|
|
#pragma mark - Setter Method |
|
|
|
- (void)setRefreshState:(XHRefreshState)refreshState { |
|
switch (refreshState) { |
|
case XHRefreshStateStopped: |
|
case XHRefreshStateNormal: { |
|
self.refreshView.stateLabel.text = @"下拉刷新"; |
|
break; |
|
} |
|
case XHRefreshStateLoading: { |
|
|
|
if (self.pullDownRefreshing) { |
|
self.refreshView.stateLabel.text = @"正在加载"; |
|
[self setScrollViewContentInsetForLoading]; |
|
|
|
if(_refreshState == XHRefreshStatePulling) { |
|
[self animationRefreshCircleView]; |
|
} |
|
} |
|
break; |
|
} |
|
case XHRefreshStatePulling: |
|
self.refreshView.stateLabel.text = @"释放立即刷新"; |
|
break; |
|
default: |
|
break; |
|
} |
|
|
|
_refreshState = refreshState; |
|
} |
|
|
|
#pragma mark - Life Cycle |
|
|
|
- (void)configuraObserverWithScrollView:(UIScrollView *)scrollView { |
|
[scrollView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil]; |
|
[scrollView addObserver:self forKeyPath:@"contentInset" options:NSKeyValueObservingOptionNew context:nil]; |
|
[scrollView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew context:nil]; |
|
} |
|
|
|
- (void)removeObserverWithScrollView:(UIScrollView *)scrollView { |
|
[scrollView removeObserver:self forKeyPath:@"contentOffset" context:nil]; |
|
[scrollView removeObserver:self forKeyPath:@"contentInset" context:nil]; |
|
[scrollView removeObserver:self forKeyPath:@"contentSize" context:nil]; |
|
} |
|
|
|
- (void)setup { |
|
self.originalTopInset = self.scrollView.contentInset.top; |
|
|
|
|
|
self.refreshState = XHRefreshStateNormal; |
|
|
|
[self configuraObserverWithScrollView:self.scrollView]; |
|
|
|
if (self.refreshViewLayerType == XHRefreshViewLayerTypeOnSuperView) { |
|
self.scrollView.backgroundColor = [UIColor clearColor]; |
|
UIView *currentSuperView = self.scrollView.superview; |
|
if (self.isPullDownRefreshed) { |
|
[currentSuperView insertSubview:self.refreshView belowSubview:self.scrollView]; |
|
} |
|
} else if (self.refreshViewLayerType == XHRefreshViewLayerTypeOnScrollViews) { |
|
if (self.isPullDownRefreshed) { |
|
[self.scrollView addSubview:self.refreshView]; |
|
} |
|
} |
|
|
|
|
|
if (self.isLoadMoreRefreshed) { |
|
[self.scrollView addSubview:self.loadMoreView]; |
|
} |
|
} |
|
|
|
- (id)initWithScrollView:(UIScrollView *)scrollView delegate:(id <XHRefreshControlDelegate>)delegate { |
|
self = [super init]; |
|
if (self) { |
|
self.delegate = delegate; |
|
self.scrollView = scrollView; |
|
[self setup]; |
|
} |
|
return self; |
|
} |
|
|
|
- (void)dealloc { |
|
self.delegate = nil; |
|
[self removeObserverWithScrollView:self.scrollView]; |
|
self.scrollView = nil; |
|
|
|
self.refreshView = nil; |
|
|
|
self.loadMoreView = nil; |
|
} |
|
|
|
#pragma mark - KVO |
|
|
|
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { |
|
if ([keyPath isEqualToString:@"contentOffset"]) { |
|
CGPoint contentOffset = [[change valueForKey:NSKeyValueChangeNewKey] CGPointValue]; |
|
|
|
|
|
if (self.isLoadMoreRefreshed) { |
|
// 上提加载更多的逻辑方法 |
|
int currentPostion = contentOffset.y; |
|
|
|
if (currentPostion > 0) { |
|
|
|
CGRect bounds = self.scrollView.bounds;//边界 |
|
|
|
CGSize size = self.scrollView.contentSize;//滚动视图内容区域size |
|
|
|
UIEdgeInsets inset = self.scrollView.contentInset;//视图周围额外的滚动视图区域 |
|
|
|
float y = currentPostion + bounds.size.height + inset. bottom; |
|
|
|
//判断是否滚动到底部 |
|
if((y - size.height) > kXHLoadMoreViewHeight && self.refreshState != XHRefreshStateLoading && self.isLoadMoreRefreshed && !self.loadMoreRefreshing) { |
|
[self startLoadMoreRefreshing]; |
|
} |
|
} |
|
} |
|
|
|
if (self.isPullDownRefreshed) { |
|
if (!self.loadMoreRefreshing) { |
|
// 下拉刷新的逻辑方法 |
|
if(self.refreshState != XHRefreshStateLoading) { |
|
// 如果不是加载状态的时候 |
|
|
|
if (ABS(self.scrollView.contentOffset.y + [self getAdaptorHeight]) >= kXHRefreshCircleViewHeight) { |
|
self.refreshView.refreshCircleView.offsetY = MIN(ABS(self.scrollView.contentOffset.y + [self getAdaptorHeight]), kXHDefaultRefreshTotalPixels) - kXHRefreshCircleViewHeight; |
|
[self.refreshView.refreshCircleView setNeedsDisplay]; |
|
} |
|
|
|
CGFloat scrollOffsetThreshold; |
|
scrollOffsetThreshold = -(kXHDefaultRefreshTotalPixels + self.originalTopInset); |
|
|
|
if(!self.scrollView.isDragging && self.refreshState == XHRefreshStatePulling) { |
|
self.pullDownRefreshing = YES; |
|
self.refreshState = XHRefreshStateLoading; |
|
} else if(contentOffset.y < scrollOffsetThreshold && self.scrollView.isDragging && self.refreshState == XHRefreshStateStopped) { |
|
self.refreshState = XHRefreshStatePulling; |
|
} else if(contentOffset.y >= scrollOffsetThreshold && self.refreshState != XHRefreshStateStopped) { |
|
self.refreshState = XHRefreshStateStopped; |
|
} |
|
} else { |
|
CGFloat offset; |
|
UIEdgeInsets contentInset; |
|
offset = MAX(self.scrollView.contentOffset.y * -1, 0.0f); |
|
offset = MIN(offset, self.refreshTotalPixels); |
|
contentInset = self.scrollView.contentInset; |
|
self.scrollView.contentInset = UIEdgeInsetsMake(offset, contentInset.left, contentInset.bottom, contentInset.right); |
|
} |
|
} |
|
} |
|
} else if ([keyPath isEqualToString:@"contentInset"]) { |
|
} else if ([keyPath isEqualToString:@"contentSize"]) { |
|
if (self.isLoadMoreRefreshed) { |
|
CGSize contentSize = [[change valueForKey:NSKeyValueChangeNewKey] CGSizeValue]; |
|
if (contentSize.height > CGRectGetHeight(self.scrollView.frame)) { |
|
CGRect loadMoreViewFrame = self.loadMoreView.frame; |
|
loadMoreViewFrame.origin.y = contentSize.height; |
|
self.loadMoreView.frame = loadMoreViewFrame; |
|
[self setScrollViewContentInsetForLoadMore]; |
|
} |
|
} |
|
} |
|
} |
|
|
|
@end
|
|
|