自定义有逼格的刷新控件

demo.gif

一.下拉刷新控件

要点:利用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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容