应用情景
情景一:
说明:是不是和tableView的Plain类型一样,其实这个是由两个列表实现的
情景二:
说明:此时,就可以发现和普通的列表有些不一样了
情景三:
说明:笔者最初就是为了实现这种情况,由于项目需求,需要防QQ空间,不同的是需要类型的切换,当时没想到好的解决方案,最后受同事启发,在其demo上进行修改,使得tableView可以满足大部分的悬停需求
思路说明
1、由于是两个tableView嵌套实现,所以首要就是兼容手势,使得我们的拖拽手势可以向下传递
2、通过改变列表的contentOffset来让列表是否"滚动"
3、子列表默认不滚动,当父列表滚动到需要悬停的位置时,父列表"停止"滚动,子列表开始滚动
4、当子列表下拉,contentOffset.y小于0时,子列表"停止滚动",父列表开始"滚动"
5、使用通知来接收滚动消息
主要实现过程(代码)
首先继承UITableView新建LolitaTableView类,并定义三种类型
typedef NS_ENUM(NSInteger , LLNestedTableViewType) {
LLNestedTableViewTypeNormal, //该类型和 UITableView 一致,未做其他设置
LLNestedTableViewTypeMain, //主列表的类型
LLNestedTableViewTypeSub //子列表的类型
};
1、兼容手势
/// 向下传递手势,触发主列表的滚动
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
if (self.typeNested == LLNestedTableViewTypeMain) { // 主table类型的需要兼容手势
return YES;
}
return NO;
}
2、注册通知
// 监听列表滚动的通知
[NSNotificationCenter.defaultCenter addObserverForName:LLNestedTableViewStopNotification object:nil queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) {
// 这里触发手动控制主从列表的滚动与否
}];
3、重写setContentOffset方法
/// 重写,参与滚动事件
-(void)setContentOffset:(CGPoint)contentOffset{
[super setContentOffset:contentOffset];
if (self.typeNested == LLNestedTableViewTypeNormal) {
return; // 普通类型不做修改
}
CGFloat y = contentOffset.y;
switch (self.typeNested) {
// 主列表类型
case LLNestedTableViewTypeMain:
{
CGFloat stayPosition = 0;
// 获取到停留的位置
if ([self.delegateNested respondsToSelector:@selector(llNestedTableViewStayPosition:)]) {
stayPosition = [self.delegateNested llNestedTableViewStayPosition:self];
}
if (self.canScroll) {
// 当主列表滚动位置超过预设时,我们发出通知,让子列表不能滚动
if (y > stayPosition) {
contentOffset.y = stayPosition;
[super setContentOffset:contentOffset];
self.canScroll = NO;
[NSNotificationCenter.defaultCenter postNotificationName:LLNestedTableViewStopNotification object:self];
} else {
[super setContentOffset:contentOffset];
}
} else {
contentOffset.y = stayPosition; // 让其“停止”在预设位置,取消动画,否则会因为时间差一直循环
[super setContentOffset:contentOffset animated:NO];
}
}
break;
// 子列表类型
case LLNestedTableViewTypeSub:
{
if (self.canScroll) {
// 当子列表被下拉到最初位置时,我们让其“停止”,并发送通知,让主列表可以滚动
if (y < 0) {
[super setContentOffset:CGPointZero];
self.canScroll = NO;
[NSNotificationCenter.defaultCenter postNotificationName:LLNestedTableViewStopNotification object:self];
} else {
[super setContentOffset:contentOffset];
}
} else {
[super setContentOffset:CGPointZero];
}
}
break;
default:
break;
}
}
4、通知处理
// 这里触发手动控制主从列表的滚动与否
LLNestedTableView* table = note.object;
if (self.typeNested == LLNestedTableViewTypeNormal ||
![table isKindOfClass:UITableView.class]||
(self.flag.length && ![self.flag isEqualToString:table.flag]))
{ return; }
// 当发送通知方和当前对象不一致,则表示当前对象需要开启滚动
if (self != table) { self.canScroll = YES; }
// 把其他所有的sub都移动到顶部,除去主的,其他table皆不能滚动
if (table.typeNested == LLNestedTableViewTypeSub && self.typeNested == LLNestedTableViewTypeSub) {
[self setContentOffset:CGPointZero];
self.canScroll = NO;
}
使用
1、父类tableView
@property (strong ,nonatomic) LLNestedTableView *mainTable;
// 初始化父类列表
-(LLNestedTableView *)mainTable{
if (_mainTable==nil) {
_mainTable = [[LLNestedTableView alloc] initWithFrame:CGRectMake(0, 64, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height-64) style:UITableViewStyleGrouped];
_mainTable.delegate = self;
_mainTable.dataSource = self;
_mainTable.delegateNested = self; // 悬停代理
_mainTable.tableFooterView = [UIView new];
_mainTable.showsVerticalScrollIndicator = NO;
_mainTable.typeNested = LLNestedTableViewTypeMain; // 列表类型
}
return _mainTable;
}
注:需要将子列表加到列表上,最好是最后一个section的cell上,这样比较灵活;其他位置也可以添加,主要是需要配合悬停的位置使用
// !!!: 悬停的位置
- (CGFloat)llNestedTableViewStayPosition:(LLNestedTableView *)tableView{
return tableView.tableHeaderView.frame.size.height;
}
2、子类列表
-(LLNestedTableView *)table{
if (_table==nil) {
_table = [[LLNestedTableView alloc] initWithFrame:CGRectZero];
_table.delegate = self;
_table.dataSource = self;
_table.showsVerticalScrollIndicator = NO;
_table.tableFooterView = [UIView new];
_table.typeNested = LLNestedTableViewTypeSub; // 除了类型要设置为子类,用法和系统类型一样
}
return _table;
}
2019-12-16 补充:
支持了 Cocoapods
,方便集成 : pod 'LLNestedTableView'
;
修复了 iOS 13 下滚动异常的问题;
新增了联动的标识,防止多个页面错误通知;
新增了 KVO
的形式进行联动。
2020-07-27 补充:
重构了 LLNestedTableView,支持 列表视图 + 集合视图 的联动。
注意:
1、悬停位置的设置改用回调的方式实现
2、如果你的内容视图非上述两个视图,如 UIScrollView,请自行转换成上述两个视图,或者根据 列表视图 或者 集合视图实现的方式自行实现,大体思路不变
3、关于内容视图 和 主列表 同时滚动的问题,目前解决方案是,手动禁止某些滚动视图的滚动事件。PS:另一种思路是改写关键方法 -gestureRecognizer: shouldRecognizeSimultaneouslyWithGestureRecognizer:
的值,笔者目前还没有踩坑,只提供一种思路
4、UITableView/UICollectionView 需要弹性效果,UITableView 竖直方向默认是开启的,UICollectionView 在数量少,内容视图小于可见视图时,弹性是关闭的,你需要设置 alwaysBounceVertical 为 YES/true