MG--自定义刷新控件
2016-04-10 本文已影响690人
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.png如何继承MJRefresh,必须要需要实现什么方法以及需要修改那些属性
————————————————————————————————————————————————————————————
使用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](http://upload-images.jianshu.io/upload_images/1429890-0b9ad613b91fc2ea.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)```
# MGRefreshxib与MGRefreshView对应的图片:
![MGRefreshxib与MGRefreshView对应的图片.png](http://upload-images.jianshu.io/upload_images/1429890-0b9ad613b91fc2ea.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)