支付宝首页tableView的大概有如下几个特点:
- 下拉tableVIew,”headerView”不随之下滑
- 上拉tableView,”headerView”随之下滑
- 不够力,自动滑回
而单纯的tableHeaderView,sectionHeader啊,是无法实现这两个功能的。
以下是我的实现思路:
1 创建一个View,作为“headerView”
- (void)initHeaderView
{
_headerView = [[MyHeaderView alloc] initWithFrame:CGRectMake(0, 64, DRScreenWidth, HeaderViewHeight)];
_headerView.backgroundColor = [UIColor yellowColor];
[self.view addSubview:_headerView];
}
2 创建tableView,将其contentInset设在headerView下沿
- (void)initTableView
{
_tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, DRScreenWidth, DRScreenHeight) style:UITableViewStylePlain];
_tableView.dataSource = self;
_tableView.delegate = self;
_tableView.backgroundColor = [UIColor purpleColor];
_tableView.contentInset = UIEdgeInsetsMake(HeaderViewHeight, 0, 0, 0);
[_tableView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
[self.view addSubview:_tableView];
}
看到这里,可能有的读者会有疑问,
_tableView.contentInset = UIEdgeInsetsMake(HeaderViewHeight, 0, 0, 0);
这句有何意义呢?干嘛不直接把tableView的frame放在headerView下面就好了?
其实这句大有玄机。我们继续看HeaderView里面究竟实现了什么。
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self initBtn];
[self addObserver];
}
return self;
}
- (void)addObserver
{
[self addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew context:nil];
}
#pragma mark - KVO
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
CGRect rect = [change[NSKeyValueChangeNewKey] CGRectValue];
NSLog(@"head view height = %f",rect.size.height);
CGFloat height = rect.size.height;
if (height < visibleOffset) {
_btn.hidden = YES;
_aView.hidden = YES;
} else {
_btn.hidden = NO;
_aView.hidden = NO;
}
}
这里的kvo主要是为了通过监听frame的变化来控制subView的显影。
而下面这句才能解释上面那个问题:
#pragma mark - touch
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *view = [super hitTest:point withEvent:event];
if ([view isKindOfClass:[self class]]) {
return nil;
}
return view;
}
重写了hittest,反正让用户怎么也摸不到自身!就是为了解决当触摸点在headerView上面的时候,hitTest返回对象是跌在他下面的tableView,来让tableView响应滑动。
好了,回到正题。
我们好像还没实现初衷的3个特性。
第1,2点也是通过KVO来改变headerView的height或origin.y实现的。
首先看看改变height:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
if ([keyPath isEqualToString:@"contentOffset"]) {
CGPoint offset = [change[NSKeyValueChangeNewKey] CGPointValue];
NSLog(@"KVO:%f",offset.y);
if (offset.y > -(HeaderViewHeight +navigationBarHeightPlusStatusBarHeight) && offset.y < -navigationBarHeightPlusStatusBarHeight) {
CGRect frame = _headerView.frame;
frame.size.height = - navigationBarHeightPlusStatusBarHeight - offset.y;
_headerView.frame = frame;
} else if (offset.y <= -(HeaderViewHeight +navigationBarHeightPlusStatusBarHeight)) {
//tableView下滑,headerView保持不变
_headerView.frame = CGRectMake(0, navigationBarHeightPlusStatusBarHeight, DRScreenWidth, HeaderViewHeight);
} else if (offset.y >= -navigationBarHeightPlusStatusBarHeight) {
//tableView上滑,直到两者上沿重叠,headerView高度设0
_headerView.frame = CGRectMake(0, navigationBarHeightPlusStatusBarHeight, DRScreenWidth, 0);
}
}
}
上面的逻辑很清楚了,主要是通过改变headerView的高度,来造成headerView随tableView滑动的错觉。
当然,也可以改变HeaderView的y来实现。
看代码:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
if ([keyPath isEqualToString:@"contentOffset"]) {
CGPoint offset = [change[NSKeyValueChangeNewKey] CGPointValue];
NSLog(@"KVO:%f",offset.y);
if (offset.y > -(HeaderViewHeight +navigationBarHeightPlusStatusBarHeight) && offset.y < -navigationBarHeightPlusStatusBarHeight) {
CGRect frame = _headerView.frame;
frame.origin.y = fabs(offset.y) - HeaderViewHeight;
_headerView.frame = frame;
} else if (offset.y <= -(HeaderViewHeight +navigationBarHeightPlusStatusBarHeight)) {
_headerView.frame = CGRectMake(0, navigationBarHeightPlusStatusBarHeight, DRScreenWidth, HeaderViewHeight);
} else if (offset.y >= -navigationBarHeightPlusStatusBarHeight) {
_headerView.frame = CGRectMake(0, -HeaderViewHeight+navigationBarHeightPlusStatusBarHeight, DRScreenWidth, HeaderViewHeight);
}
}
}
最后,就是第3个特性了:
#pragma mark - scrollview delegate
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
// NSLog(@"willENdDragging:%f",scrollView.contentOffset.y);
if (targetContentOffset->y > -132 && targetContentOffset->y < -navigationBarHeightPlusStatusBarHeight) {
*targetContentOffset = CGPointMake(0, -navigationBarHeightPlusStatusBarHeight);
} else if (targetContentOffset->y <= -132 && targetContentOffset->y > -(navigationBarHeightPlusStatusBarHeight + HeaderViewHeight)) {
*targetContentOffset = CGPointMake(0, -(navigationBarHeightPlusStatusBarHeight + HeaderViewHeight));
}
}
实现这个代理就👌。关于这个代理详细可看我上一篇文章UITableView串烧