一.下拉刷新控件
要点:利用UIScrollView的分类实现;如何监听下拉刷新控件三种状态;通过分类反向获取控件;不影响UIScrollView代理调用。
1.区分下拉控件的三种状态
typedef NS_ENUM(NSInteger, RefreshState) {
RefreshState_ready, //NSLog(@"下拉去刷新");
RefreshState_will, //NSLog(@"松开去释放");
RefreshState_loading //NSLog(@"加载中");
};
利用UIScrollView的contentOffset和是否在拖拽的状态,可以区分出三种状态。
比如利用UIScrollView的代理方法(但是不能使用代理,一会解释):
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
//isDragging且>contentOffset.y>临界值->RefreshState_will
//isDragging且>contentOffset.y<临界值->RefreshState_ready
}
-(void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
//!isDragging&&RefreshState_will->RefreshState_loading
}
2.通过分类反向获取控件
通过runtime在分类中绑定RefreshView实例,为了直接可以通过分类获取到该RefreshView,调用EndRefresh方法。
#import <objc/runtime.h>
static char UIScrollViewPullToRefreshView;
@implementation UIScrollView (ZSRefresh)
-(void)setZsrefresh:(ZSRefreshView * _Nonnull)zsrefresh
{
[self willChangeValueForKey:@"ZSRefreshView"];
objc_setAssociatedObject(self, &UIScrollViewPullToRefreshView,
zsrefresh,
OBJC_ASSOCIATION_ASSIGN);
[self didChangeValueForKey:@"ZSRefreshView"];
}
-(ZSRefreshView *)zsrefresh
{
return objc_getAssociatedObject(self, &UIScrollViewPullToRefreshView);
}
3.不影响UIScrollView代理调用
如果在分类中使用之前的两个代理方法来区分下拉控件三种状态,那么在使用该控件的时候,再去使用这两个代理方法将会失效,因为delegate只能指定一个对象(一对一),所以应该使用KVO的方式监听contentOffset。
4.分类代码和使用方式
UIScrollView+ZSRefreshView.h
@interface ZSRefreshView:UIView
-(void)startRefresh;
-(void)endRefresh;
@end
typedef void(^LoadingBlock)(void);
@interface UIScrollView (ZSRefresh)
@property (nonatomic, strong, readonly) ZSRefreshView *zsrefresh;
-(ZSRefreshView*)addPull:(LoadingBlock)loadingblock;
@end
UIScrollView+ZSRefreshView.m
#import "UIScrollView+ZSRefresh.h"
#define BallWidth 10
#define RefreshHeight 60
#define LoadingTime 1.0
typedef NS_ENUM(NSInteger, RefreshState) {
RefreshState_ready, //NSLog(@"下拉去刷新");
RefreshState_will, //NSLog(@"松开去释放");
RefreshState_loading //NSLog(@"加载中");
};
@interface ZSRefreshView()<UITableViewDelegate>
@property(nonatomic,copy) LoadingBlock loadingBlock;
@property(nonatomic,assign) RefreshState refreshstate;
@property(nonatomic,assign) float ratio;
@property(nonatomic,weak) UIScrollView *scrollview;
@property(nonatomic,strong) UIView * ballview1;
@property(nonatomic,strong) UIView * ballview2;
@property(nonatomic,strong) UIView * ballview3;
@end
@implementation ZSRefreshView
-(instancetype)initWithFrame:(CGRect)frame
{
if(self=[super initWithFrame:frame])
{
self.ballview1=[[UIView alloc]init];
self.ballview1.layer.cornerRadius=BallWidth/2;
self.ballview1.backgroundColor=SDColor(245, 191, 20);
self.ballview2=[[UIView alloc]init];
self.ballview2.layer.cornerRadius=BallWidth/2;
self.ballview2.backgroundColor=ThemeColor;
self.ballview3=[[UIView alloc]init];
self.ballview3.layer.cornerRadius=BallWidth/2;
self.ballview3.backgroundColor=SDColor(235, 1, 6);
[self addSubview:self.ballview1];
[self addSubview:self.ballview2];
[self addSubview:self.ballview3];
}
return self;
}
-(void)setScrollview:(UIScrollView *)scrollview
{
_scrollview=scrollview;
}
- (void)loading{
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"opacity"];
anim.duration = LoadingTime;
anim.fromValue=@(1.0);
anim.toValue = @(0.3);
anim.repeatCount = MAXFLOAT;
[self.ballview1.layer addAnimation:anim forKey:@"anim1"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(LoadingTime/3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.ballview2.layer addAnimation:anim forKey:@"anim2"];
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(LoadingTime*2.0/3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.ballview3.layer addAnimation:anim forKey:@"anim3"];
});
}
-(void)startRefresh
{
self.refreshstate=RefreshState_loading;
self.ballview1.frame=CGRectMake((self.scrollview.width-BallWidth)/2-20,(RefreshHeight-10)-(RefreshHeight/2-10), BallWidth, BallWidth);
self.ballview2.frame=CGRectMake((self.scrollview.width-BallWidth)/2, (RefreshHeight-10)-(RefreshHeight/2-10), BallWidth, BallWidth);
self.ballview3.frame=CGRectMake((self.scrollview.width-BallWidth)/2+20,(RefreshHeight-10)-(RefreshHeight/2-10), BallWidth, BallWidth);
[self loading];
[UIView animateWithDuration:0.3 animations:^{
//自动刷新需要设置setContentOffset才生效
self.scrollview.contentOffset=CGPointMake(0, -RefreshHeight);
self.scrollview.contentInset=UIEdgeInsetsMake(RefreshHeight, 0, 0, 0);
} completion:^(BOOL finished) {
}];
if(self.loadingBlock)
{
self.loadingBlock();
}
}
-(void)endRefresh
{
self.refreshstate=RefreshState_ready;
[UIView animateWithDuration:0.3 animations:^{
self.scrollview.contentInset=UIEdgeInsetsMake(0, 0, 0, 0);
} completion:^(BOOL finished) {
[self.ballview1.layer removeAnimationForKey:@"anim1"];
[self.ballview2.layer removeAnimationForKey:@"anim2"];
[self.ballview3.layer removeAnimationForKey:@"anim3"];
}];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
if([keyPath isEqualToString:@"contentOffset"])
{
CGPoint point=[[change valueForKey:NSKeyValueChangeNewKey]CGPointValue];
[self scrollViewScrolling:point];
}
}
- (void)scrollViewScrolling:(CGPoint)contentOffset{
if(self.scrollview.contentOffset.y<=0.0)
{
if(self.scrollview.isDragging&&self.scrollview.contentOffset.y>=-RefreshHeight)
{
self.refreshstate=RefreshState_ready;
[self.ballview1.layer removeAnimationForKey:@"anim1"];
[self.ballview2.layer removeAnimationForKey:@"anim2"];
[self.ballview3.layer removeAnimationForKey:@"anim3"];
self.ratio=MIN(fabs(self.scrollview.contentOffset.y)/RefreshHeight, 1.0);
self.ballview1.frame=CGRectMake((self.scrollview.width-BallWidth)/2-20*self.ratio,(RefreshHeight-10)-(RefreshHeight/2-10)*self.ratio, BallWidth, BallWidth);
self.ballview2.frame=CGRectMake((self.scrollview.width-BallWidth)/2, (RefreshHeight-10)-(RefreshHeight/2-10)*self.ratio, BallWidth, BallWidth);
self.ballview3.frame=CGRectMake((self.scrollview.width-BallWidth)/2+20*self.ratio,(RefreshHeight-10)-(RefreshHeight/2-10)*self.ratio, BallWidth, BallWidth);
}else if(self.scrollview.isDragging&&self.scrollview.contentOffset.y<-RefreshHeight)
{
self.refreshstate=RefreshState_will;
}else if(!self.scrollview.isDragging&&self.refreshstate==RefreshState_will)
{
self.refreshstate=RefreshState_loading;
[self loading];
[UIView animateWithDuration:0.3 animations:^{
self.scrollview.contentInset=UIEdgeInsetsMake(RefreshHeight, 0, 0, 0);
} completion:^(BOOL finished) {
}];
if(self.loadingBlock)
{
self.loadingBlock();
}
}
}
}
@end
#import <objc/runtime.h>
static char UIScrollViewPullToRefreshView;
@implementation UIScrollView (ZSRefresh)
-(ZSRefreshView *)addPull:(LoadingBlock)loadingblock
{
ZSRefreshView * zsrefreshview=[[ZSRefreshView alloc]initWithFrame:CGRectMake(0, -RefreshHeight,[UIScreen mainScreen].bounds.size.width, RefreshHeight)];
zsrefreshview.refreshstate=RefreshState_ready;
zsrefreshview.scrollview=self;
zsrefreshview.loadingBlock=loadingblock;
zsrefreshview.backgroundColor=[UIColor clearColor];
[self addSubview:zsrefreshview];
self.zsrefresh=zsrefreshview;
[self addObserver:self.zsrefresh forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
return zsrefreshview;
}
-(void)setZsrefresh:(ZSRefreshView * _Nonnull)zsrefresh
{
[self willChangeValueForKey:@"ZSRefreshView"];
objc_setAssociatedObject(self, &UIScrollViewPullToRefreshView,
zsrefresh,
OBJC_ASSOCIATION_ASSIGN);
[self didChangeValueForKey:@"ZSRefreshView"];
}
-(ZSRefreshView *)zsrefresh
{
return objc_getAssociatedObject(self, &UIScrollViewPullToRefreshView);
}
@end
使用
[self.tableview addPull:^{
[self.tableview.zsrefresh endRefresh];
}];
二.中心加载HUD
要点:直接添加到UIApplication.keywindow上
JSXLoadingView.h
@interface JSXLoadingView : CommonViewFromXib
@property (weak, nonatomic) IBOutlet UIView *bgview;
+(instancetype)shareOnceView;
-(void)show;
-(void)hide;
@end
JSXLoadingView.m
#import "JSXLoadingView.h"
@interface JSXLoadingView()
{
UIView * ball1;
UIView * ball2;
UIView * ball3;
CAAnimationGroup * group;
CAAnimationGroup * group2;
CAAnimationGroup * group3;
}
@end
@implementation JSXLoadingView
#pragma mark - 保证JSXLoadingView单例
// 创建静态对象 防止外部访问
static JSXLoadingView *_instance;
#pragma mark - 初始化JSXLoadingView
+(instancetype)shareOnceView
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (_instance == nil) {
_instance = [[[NSBundle mainBundle]loadNibNamed:NSStringFromClass(self) owner:self options:nil]lastObject];
}
});
return _instance;
}
-(void)awakeFromNib
{
[super awakeFromNib];
self.bgview.backgroundColor=[UIColor clearColor];
ball1=[[UIView alloc]initWithFrame:CGRectMake(20, 10, 10, 10)];
ball1.backgroundColor=SDColor(245, 191, 20);
ball1.layer.cornerRadius=5;
[self.bgview addSubview:ball1];
ball2=[[UIView alloc]initWithFrame:CGRectMake(10, 30, 10, 10)];
ball2.backgroundColor=ThemeColor;
ball2.layer.cornerRadius=5;
[self.bgview addSubview:ball2];
ball3=[[UIView alloc]initWithFrame:CGRectMake(30, 30, 10, 10)];
ball3.backgroundColor=SDColor(235, 1, 6);
ball3.layer.cornerRadius=5;
[self.bgview addSubview:ball3];
NSValue * point1=[NSValue valueWithCGPoint:CGPointMake(25, 15)];
NSValue * point2=[NSValue valueWithCGPoint:CGPointMake(35, 35)];
NSValue * point3=[NSValue valueWithCGPoint:CGPointMake(15, 35)];
CAKeyframeAnimation * opanim=[CAKeyframeAnimation animationWithKeyPath:@"opacity"];
opanim.values=[NSArray arrayWithObjects:@1.0,@0.5,@1.0,@0.5,@1.0,@0.5,nil];
opanim.autoreverses = NO;
CAKeyframeAnimation * anim=[CAKeyframeAnimation animationWithKeyPath:@"position"];
anim.values=[NSArray arrayWithObjects:point1,point2,point3,point1, nil];
anim.autoreverses = NO;
anim.timingFunctions = @[[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
CAKeyframeAnimation * anim2=[CAKeyframeAnimation animationWithKeyPath:@"position"];
anim2.values=[NSArray arrayWithObjects:point2,point3,point1,point2, nil];
anim2.autoreverses = NO;
anim2.timingFunctions = @[[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
CAKeyframeAnimation * anim3=[CAKeyframeAnimation animationWithKeyPath:@"position"];
anim3.values=[NSArray arrayWithObjects:point3,point1,point2,point3,nil];
anim3.autoreverses = NO;
anim3.timingFunctions = @[[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
group=[CAAnimationGroup animation];
group.animations=@[opanim,anim];
group.duration = 2;
group.repeatCount = MAXFLOAT;
group2=[CAAnimationGroup animation];
group2.animations=@[opanim,anim2];
group2.duration = 2;
group2.repeatCount = MAXFLOAT;
group3=[CAAnimationGroup animation];
group3.animations=@[opanim,anim3];
group3.duration = 2;
group3.repeatCount = MAXFLOAT;
self.frame=CGRectMake(SDScreenWidth/2-50, SDScreenHeight/2-50, 100, 100);
[[UIApplication sharedApplication].keyWindow addSubview:self];
}
-(void)show
{
[ball1.layer addAnimation:group forKey:@"ani1"];
[ball2.layer addAnimation:group2 forKey:@"ani2"];
[ball3.layer addAnimation:group3 forKey:@"ani3"];
self.hidden=NO;
}
-(void)hide
{
[ball1.layer removeAllAnimations];
[ball2.layer removeAllAnimations];
[ball3.layer removeAllAnimations];
self.hidden=YES;
}
@end