MJRefresh源码地址:CoderMJLee/MJRefresh · GitHub
一、基本结构
github上有MJ详细的分析
二、MJRefresh最基本的刷新代码
1、MJRefreshNormalHeader一般应用
__weak UITableView *tableView = self.tableView;
MJRefreshNormalHeader *normal = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
// 模拟延迟加载数据,因此2秒后才调用(真实开发中,可以移除这段gcd代码)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 结束刷新
[tableView.header endRefreshing];
});
}];
// 下拉刷新
tableView.header = normal;
tableView默认是没有header属性的,这里MJ使用分类(runtime的关联对象)给UIScrollView添加了一个header和footer属性;同时,给header和footer手动增加了KVO监听。
看代码:
static const char MJRefreshHeaderKey = '\0';
- (void)setHeader:(MJRefreshHeader *)header
{
if (header != self.header) {
// 删除旧的,添加新的
[self.header removeFromSuperview];
[self addSubview:header];
// 存储新的
[self willChangeValueForKey:@"header"]; // KVO
objc_setAssociatedObject(self, &MJRefreshHeaderKey,
header, OBJC_ASSOCIATION_ASSIGN);
[self didChangeValueForKey:@"header"]; // KVO
}
}
这里关联对象使用OBJC_ASSOCIATION_ASSIGN,我认为是和使用@property(nonmotic, weak)UIView *view; 中的weak一个意思的。放在view上的控件,不需要再次对其强引用。
- (MJRefreshHeader *)header
{
return objc_getAssociatedObject(self, &MJRefreshHeaderKey);
}
你只需要拖动tableView,或者手动调用beginRefreshing方法,均可以达到刷新的效果。
2、将MJRefreshNormalHeader对象赋值给header时执行的操作
因为MJRefreshNormalHeader继承MJRefreshComponent,在MJRefreshComponent内实例化过程中,先在initWithFrame方法中调用了prepare方法;
然后,在执行addSubView时,在方法layoutSubviews中执行了placeSubviews方法,这几简要说明下layoutSubviews在什么时候会调用。
layoutSubviews是否被调用的情况:
1、init初始化不会触发layoutSubviews。
2、addSubview会触发layoutSubviews。
3、设置view的Frame会触发layoutSubviews,当然前提是frame的值设置前后发生了变化。
4、滚动一个UIScrollView会触发layoutSubviews。
5、旋转Screen会触发父UIView上的layoutSubviews事件。
6、改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件。
疑问:为什么要额外准备prepare和placeSubviews给子类调用,系统提供的方法完全够用了。
其次,调用willMoveToSuperview方法,执行一些基本的操作,添加了对scrollView的几个参数KVO监听;代码如下
- (void)willMoveToSuperview:(UIView *)newSuperview
{
[super willMoveToSuperview:newSuperview];
// 如果不是UIScrollView,不做任何事情
if (newSuperview && ![newSuperview isKindOfClass:[UIScrollView class]]) return;
// 旧的父控件移除监听
[self removeObservers];
if (newSuperview) { // 新的父控件
// 设置宽度
self.mj_w = newSuperview.mj_w;
// 设置位置
self.mj_x = 0;
// 记录UIScrollView
_scrollView = (UIScrollView *)newSuperview;
// 设置永远支持垂直弹簧效果
_scrollView.alwaysBounceVertical = YES;
// 记录UIScrollView最开始的contentInset
_scrollViewOriginalInset = _scrollView.contentInset;
// 添加监听
[self addObservers];
}
}
三、往下拉tableView(包括其它UIScrollView)可以出现效果,并且在拉伸一定程度放手之后,能实现刷新的效果
1、在MJRefreshComponent内部实现了对scrollView的contentOffset、contentSize、state的KVO监听,见代码:
#pragma mark - KVO监听
- (void)addObservers
{
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.scrollView addObserver:self forKeyPath:MJRefreshKeyPathContentOffset options:options context:nil];
[self.scrollView addObserver:self forKeyPath:MJRefreshKeyPathContentSize options:options context:nil];
self.pan = self.scrollView.panGestureRecognizer;
[self.pan addObserver:self forKeyPath:MJRefreshKeyPathPanState options:options context:nil];
}
2、对这三个值的监听,同时执行以下相应的方法
- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change{}
- (void)scrollViewContentSizeDidChange:(NSDictionary *)change{}
- (void)scrollViewPanStateDidChange:(NSDictionary *)change{}
这三个方法在MJRefreshComponent内均没有具体的实现,作用是为了交给子类实现。