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.
434 lines
16 KiB
434 lines
16 KiB
![]()
2 years ago
|
//
|
||
|
// 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
|