iOS UITableView/UICollectionView的嵌套和悬停的解决方案

应用情景

情景一:

情景一

说明:是不是和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;
}

Demo地址


2019-12-16 补充:

支持了 Cocoapods ,方便集成 : pod 'LLNestedTableView'
修复了 iOS 13 下滚动异常的问题;
新增了联动的标识,防止多个页面错误通知;
新增了 KVO 的形式进行联动。


2020-07-27 补充:

重构了 LLNestedTableView,支持 列表视图 + 集合视图 的联动。

tableView_CollectionView.gif

注意:

1、悬停位置的设置改用回调的方式实现
2、如果你的内容视图非上述两个视图,如 UIScrollView,请自行转换成上述两个视图,或者根据 列表视图 或者 集合视图实现的方式自行实现,大体思路不变
3、关于内容视图 和 主列表 同时滚动的问题,目前解决方案是,手动禁止某些滚动视图的滚动事件。PS:另一种思路是改写关键方法 -gestureRecognizer: shouldRecognizeSimultaneouslyWithGestureRecognizer: 的值,笔者目前还没有踩坑,只提供一种思路
4、UITableView/UICollectionView 需要弹性效果,UITableView 竖直方向默认是开启的,UICollectionView 在数量少,内容视图小于可见视图时,弹性是关闭的,你需要设置 alwaysBounceVertical 为 YES/true

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

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,622评论 4 59
  • 7:00市场采购 10:00完成二次加工 爱与美食的诱惑 懂比吃更重要
    Q同学会阅读 226评论 0 0
  • 千人千面,你看到的也许根本就不是真的。 ——乔安 ...
    乔安同学阅读 228评论 0 1
  • 生活,我没有办法做到面面具全,没有办法永远的忍让和压抑,没有办法为了别人开心而开心,因为人生太短暂……我已...
    kany阅读 178评论 0 0
  • 我家在西南的一个小镇 她是一个美丽的姑娘 她有蓝蓝的天空和绿绿的青山 她还有美丽的白云 她如此美丽 我怎能不爱她 ...
    一朵小云儿阅读 431评论 11 8