MG--自定义刷新控件

在iOS开发中,我们经常要用到上拉刷新(加载最新数据)和下拉刷新(加载以前的数据),使用第三方框架MJRefresh虽然很方便,但是你懂得它刷新的原理了吗

下拉刷新原理图:

下拉刷新原理图.png

>上拉刷新原理图:

上拉刷新原理图.png

>为什么不用苹果自带的刷新控件:

为什么不用苹果自带的刷新控件.png

以下我们来看代码的实现

/******** 属性 ********/

#################### pragma mark - 其他属性 ###################
/** 会话管理者(用于网络请求) */
@property (nonatomic,weak) AFHTTPSessionManager *manager;
/** 话题数组(用于存放从服务器返回的数据) */
@property (nonatomic,strong) NSMutableArray *topics;
/** maxtime 用于记录当前最大的数据的ID*/
@property (nonatomic,copy) NSString *maxtime;

#################### pragma mark - 下拉刷新控件 ###################
@property (nonatomic, weak) UIView *header;
/** 下拉刷新控件里面的文字 */
@property (nonatomic, weak) UILabel *headerLabel;
/** 是否为"松开立即刷新" */
@property(nonatomic, assign, getter=isWillLoadingNewData) BOOL willLoadingNewData;
/** 是否为"正在刷新" */
@property(nonatomic, assign, getter=isLoadingNewData) BOOL loadingNewData;

#################### pragma mark - 上拉刷新-footer控件 ###################
/** 上拉刷新控件 */ 
@property (nonatomic, weak) UIView *footer;
/** 上拉刷新控件里面的文字 */
@property (nonatomic, weak) UILabel *footerLabel;
/** 是否正在加载更多数据 */
@property(nonatomic, assign, getter=isLoadingMoreData) BOOL loadingMoreData;

在viewDidLoad方法初始化

- (void)viewDidLoad {
    [super viewDidLoad];
   self.title = @"明哥";
    // 加载数据
    [self loadNewTopicData];
    // 设置刷新控件
    [self setUpRefresh];

/******** 集成刷新控件方法 ********/

- (void)setUpRefresh {
    #################### pragma mark -下拉刷新控件 ###################
    UIView *header = [[UIView alloc] init];
    header.backgroundColor = [UIColor yellowColor];
    header.height = 60;
    header.width = self.tableView.width;
    header.y = - header.height;
    [self.tableView addSubview:header];
    self.header = header;
    // 头部提醒文字headerLabel
    UILabel *headerLabel = [[UILabel alloc] init];
    headerLabel.text = @"下拉可以刷新";
    headerLabel.width = self.tableView.width;
    headerLabel.height = header.height;
    headerLabel.textAlignment = NSTextAlignmentCenter;
    self.header.alpha = 0.0;
    [header addSubview:headerLabel];
    self.headerLabel = headerLabel;
    ################### pragma mark - 上拉刷新控件 ##################
    UIView *footer = [[UIView alloc] init];
    footer.backgroundColor = [UIColor orangeColor];
    footer.height = 35;
    footer.hidden = YES;
    self.tableView.tableFooterView = footer;
    self.footer = footer;
    // 尾部提醒文字footerLabel
    UILabel *footerLabel = [[UILabel alloc] init];
    footerLabel.text = @"上拉可以加载更多";
    footerLabel.width = self.tableView.width;
    footerLabel.height = footer.height;
    footerLabel.textAlignment = NSTextAlignmentCenter;
    [footer addSubview:footerLabel];
    self.footerLabel = footerLabel;
}

UIScrollDelegate方法

#pragma mark - 代理方法
/**
 * 当scrollView在滚动,就会调用这个代理方法
 */
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    // 处理下拉刷新
    [self dealLoadNewData];
    
    // 处理上拉加载更多
    [self dealLoadMoreData];
}

/**
 * 当用户手松开(停止拖拽),就会调用这个代理方法
 */
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
    if (self.isLoadingNewData || self.willLoadingNewData == NO) return;
    
    self.headerLabel.text = @"明哥正在帮你加载";
    self.loadingNewData = YES;
    // 发送请求
    [self loadNewTopicData];
    // 增加顶部的内边距
    [UIView animateWithDuration:1.0 animations:^{
        UIEdgeInsets inset = self.tableView.contentInset;
        inset.top += self.header.height;
        self.tableView.contentInset = inset;
    }completion:^(BOOL finished) {
        [UIView animateWithDuration:0.5 delay:0.5 options:(UIViewAnimationOptionAllowAnimatedContent) animations:^{
            self.headerLabel.text = @"加载完成";
            
            UIEdgeInsets inset = self.tableView.contentInset;
            inset.top -= self.header.height;
            self.tableView.contentInset = inset;
        } completion:^(BOOL finished) {
            // 修改文字
            self.headerLabel.text = @"下拉可以加载数据...";
            // 正在刷新
            self.loadingNewData = NO;
            self.willLoadingNewData = NO;
            self.header.alpha = 0.0;
        }];
     }];
}

处理下拉刷新方法

/**
 * 处理下拉刷新
 */
- (void)dealLoadNewData
{
    // 如果是正在刷新数据,则直接返回
    if (self.loadingNewData) return;

    // BSNavBarH导航栏的高度64
    // 设置固定偏移量offsetY
    CGFloat offsetY =  - (BSNavBarH  + self.header.height);
    // 设置透明度
    CGFloat alpha = (self.tableView.contentOffset.y + BSNavBarH  + self.header.height)/self.header.height;
    if (alpha>1) {
        alpha = 1;
    }
    self.header.alpha = 1- alpha;
     //如果Y轴偏移量与offsetY比较大小,进行一些操作
    if (self.tableView.contentOffset.y <= offsetY) {
        self.headerLabel.text = @"松开立即刷新";
        self.willLoadingNewData = YES;
    } else {
        self.headerLabel.text = @"下拉可以刷新";
        self.willLoadingNewData = NO;
    }
}

处理上拉加载更多

/**
 * 处理上拉加载更多
 */
- (void)dealLoadMoreData
{
    // 显示上拉刷新控件
    self.footer.hidden = NO;
    // 如果没有数据 或者 正在上拉刷新, 直接返回
    if (self.topics.count == 0 || self.loadingMoreData) return;
    
    CGFloat offsetY = self.tableView.contentSize.height + self.tableView.contentInset.bottom - self.tableView.height;
    if (self.tableView.contentOffset.y >= offsetY) {
        self.loadingMoreData = YES;
        
        // 更改文字
        self.footerLabel.text = @"正在加载更多的数据...";
        
      // 加载更多的帖子数据
      [UIView animateWithDuration:0.25 delay:0.25 options:(UIViewAnimationOptionAllowAnimatedContent) animations:^{
           [self loadMoreTopicData];
      } completion:^(BOOL finished) {
          self.loadingMoreData = NO;
          // 更改文字
          self.footerLabel.text = @"下拉加载更多的数据...";
      }];
    }
}

pragma mark - 数据处理

#################### pragma mark - 加载最新的帖子数据(新数据)###################
/**
 * 加载最新的帖子数据
 */
- (void)loadNewTopicData{
    // 1.创建会话管理者
//    self.manager = [AFHTTPSessionManager manager];
    
    // 2.拼接请求参数
    NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
    parameters[@"a"] = @"list";
    parameters[@"c"] = @"data";
    parameters[@"type"] = @"1";
    
    [self.manager GET:BSUrl parameters:parameters success:^(NSURLSessionDataTask * _Nonnull task, id  _Nonnull responseObject) {
        if (responseObject == nil) return ;
        // 给话题数组赋值数据
        self.topics = [BSTopicItem mj_objectArrayWithKeyValuesArray:responseObject[@"list"]];
        // 记录上一页最后一个模型的ID
        self.maxtime = responseObject[@"info"][@"maxtime"];
        // 刷新数据
        [self.tableView reloadData];
        
    } failure:^(NSURLSessionDataTask * _Nonnull task, NSError * _Nonnull error) {
        BSLog(@"%@",error);
    }];
}

#################### pragma mark - 加载更多的数据(旧数据)###################
/**
 * 加载最新的帖子数据
 */
- (void)loadMoreTopicData{
    // 1.创建会话管理者
//    self.manager = [AFHTTPSessionManager manager];
    
    // 2.拼接请求参数
    NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
    parameters[@"a"] = @"list";
    parameters[@"c"] = @"data";
    parameters[@"maxtime"] = self.maxtime;
    parameters[@"type"] = @"1";
    
    [self.manager GET:BSUrl parameters:parameters success:^(NSURLSessionDataTask * _Nonnull task, id  _Nonnull responseObject) {
        if (responseObject == nil) return ;
        // 给话题数组赋值数据
        NSArray *moreTopics = [BSTopicItem mj_objectArrayWithKeyValuesArray:responseObject[@"list"]];
        // 将数据以数组的形式添加到数组的后面
        [self.topics addObjectsFromArray:moreTopics];
        // 刷新数据
        [self.tableView reloadData];
    } failure:^(NSURLSessionDataTask * _Nonnull task, NSError * _Nonnull error) {
        BSLog(@"%@",error);
    }];
}

pragma mark - lazy懒加载

// 数据数组
- (NSMutableArray *)topics{
    if (!_topics) {
        _topics = [NSMutableArray array];
    }
    return _topics;
}

// 网络请求管理者
- (AFHTTPSessionManager *)manager{
    if (!_manager) {
        _manager = [AFHTTPSessionManager manager];
    }
    return _manager;
}



!如果没有进行不想发送网络请求加载更多数据,也可以模拟加载更多数据,具体看图:

上拉刷新.png
模拟加载更多数据.png

使用MJRefresh进行刷新,且是自定义的一个类

附注:

如何继承MJRefresh,必须要需要实现什么方法以及需要修改那些属性

继承MJRefresh.png



————————————————————————————————————————————————————————————

使用Swift自定义刷新控件,继承UIRefreshControl

自定义一个类MGRefreshControl,继承UIRefreshControl
import UIKit

class MGRefreshControl: UIRefreshControl {
    // MARK:- 初始化方法
    override init() {
        super.init()
        // 设置子控件
        setupUI()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    // 重写系统的endRefreshing
    override func endRefreshing() {
        super.endRefreshing()
        showTipFlag = false
        refreshingFlag = false
        refreshView.stopAnimation()
    }
    
    // MARK:- 内部控制方法
    private func setupUI() {
        // 1.添加子控件
        addSubview(refreshView)
        
        // 2.布局子控件
        refreshView.frame.origin.x = (UIScreen.mainScreen().bounds.size.width - refreshView.frame.width) * 0.5
        
        // 3.监听下拉刷新控件frame的改变
        addObserver(self, forKeyPath: "frame", options: NSKeyValueObservingOptions.New, context: nil)
    }
    
    // MARK:- 监听下拉刷新控件frame的改变
    /// 定义标记记录是否需要旋转
    var showTipFlag: Bool = false
    /// 定义标记记录是否触发了下拉刷新
    var refreshingFlag: Bool = false
    override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
        // 1.过滤垃圾数据
        if frame.origin.y == 0
        {
            return
        }
        
        // 判断是否已经触发事件
        if refreshing && !refreshingFlag { // ture且false
            refreshingFlag = true
            refreshView.tipView.hidden = true
            refreshView.startAnimation()
        }
        
        // 越往下Y越小
        // 越往上Y越大
        
        // 2. 往下拉到一定程度, 箭头需要旋转
        if frame.origin.y < -50 && !showTipFlag { // 箭头往上旋转
            showTipFlag = true
            refreshView.startRotation(showTipFlag)
        }else if frame.origin.y > -50 && showTipFlag{  // 箭头往下旋转
            showTipFlag = false
            refreshView.startRotation(showTipFlag)
        }
    }
    
    // 移除KVO的监听者
    deinit{
        removeObserver(self, forKeyPath: "frame")
    }
    
    // MARK: - 懒加载
    /** 刷新的View */
    private lazy var refreshView: MGRefreshView = MGRefreshView.refreshView()
}

自定义一个类MGRefreshView,继承UIView
class MGRefreshView: UIView {
    // MARK:- 属性
    /** 提示视图 */
    @IBOutlet weak var tipView: UIView!
    /** 箭头视图 */
    @IBOutlet weak var arrowImage: UIImageView!
    /** 菊花视图 */
    @IBOutlet weak var loadingView: UIImageView!
    
    // 从xib创建方法
    class func refreshView() -> MGRefreshView {
        return NSBundle.mainBundle().loadNibNamed("MGRefreshView", owner: nil, options: nil).last as! MGRefreshView
    }
    
    // MARK:- 内部控制方法
    /**
    *   执行箭头旋转动画
    */
    func startRotation(flag: Bool) {
        /**
        *   默认:是按照顺时针旋转
        *   原则:就近原则
        */
        var angle = CGFloat(M_PI)
        angle += flag ? -0.001 : +0.001
        UIView.animateWithDuration(0.4) { () -> Void in
            self.arrowImage.transform = CGAffineTransformRotate(self.arrowImage.transform, angle)
        }
    }
    
    /**
    *   执行刷新旋转动画
    */
    func startAnimation(){
        // 1.创建动画对象
        let baseAnimation = CABasicAnimation(keyPath: "transform.rotation")
        
        // 2.设置动画属性
        baseAnimation.toValue = 2 * M_PI
        baseAnimation.duration = 2.0
        baseAnimation.repeatCount = MAXFLOAT
        
        // 设置动画不自动移除, 等到view销毁的时候才移除
        baseAnimation.removedOnCompletion = false
        
        // 3.将动画添加到图层上
        loadingView.layer.addAnimation(baseAnimation, forKey: nil)
    }
    
    /**
    *   停止转盘旋转动画
    */
    private func stopAnimation()
    {
        tipView.hidden = false
        loadingView.layer.removeAllAnimations()
    }
}

MGRefreshxib与MGRefreshView对应的图片:

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

推荐阅读更多精彩内容