應(yīng)用情景
情景一:
說(shuō)明:是不是和tableView的Plain類(lèi)型一樣驳规,其實(shí)這個(gè)是由兩個(gè)列表實(shí)現(xiàn)的
情景二:
說(shuō)明:此時(shí)冒滩,就可以發(fā)現(xiàn)和普通的列表有些不一樣了
情景三:
說(shuō)明:筆者最初就是為了實(shí)現(xiàn)這種情況,由于項(xiàng)目需求绒疗,需要防QQ空間油挥,不同的是需要類(lèi)型的切換,當(dāng)時(shí)沒(méi)想到好的解決方案番捂,最后受同事啟發(fā),在其demo上進(jìn)行修改江解,使得tableView可以滿足大部分的懸停需求
思路說(shuō)明
1设预、由于是兩個(gè)tableView嵌套實(shí)現(xiàn),所以首要就是兼容手勢(shì)犁河,使得我們的拖拽手勢(shì)可以向下傳遞
2鳖枕、通過(guò)改變列表的contentOffset來(lái)讓列表是否"滾動(dòng)"
3魄梯、子列表默認(rèn)不滾動(dòng),當(dāng)父列表滾動(dòng)到需要懸停的位置時(shí)宾符,父列表"停止"滾動(dòng)酿秸,子列表開(kāi)始滾動(dòng)
4、當(dāng)子列表下拉魏烫,contentOffset.y小于0時(shí)辣苏,子列表"停止?jié)L動(dòng)",父列表開(kāi)始"滾動(dòng)"
5哄褒、使用通知來(lái)接收滾動(dòng)消息
主要實(shí)現(xiàn)過(guò)程(代碼)
首先繼承UITableView新建LolitaTableView類(lèi)稀蟋,并定義三種類(lèi)型
typedef NS_ENUM(NSInteger , LLNestedTableViewType) {
LLNestedTableViewTypeNormal, //該類(lèi)型和 UITableView 一致,未做其他設(shè)置
LLNestedTableViewTypeMain, //主列表的類(lèi)型
LLNestedTableViewTypeSub //子列表的類(lèi)型
};
1呐赡、兼容手勢(shì)
/// 向下傳遞手勢(shì)退客,觸發(fā)主列表的滾動(dòng)
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
if (self.typeNested == LLNestedTableViewTypeMain) { // 主table類(lèi)型的需要兼容手勢(shì)
return YES;
}
return NO;
}
2、注冊(cè)通知
// 監(jiān)聽(tīng)列表滾動(dòng)的通知
[NSNotificationCenter.defaultCenter addObserverForName:LLNestedTableViewStopNotification object:nil queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) {
// 這里觸發(fā)手動(dòng)控制主從列表的滾動(dòng)與否
}];
3罚舱、重寫(xiě)setContentOffset方法
/// 重寫(xiě)井辜,參與滾動(dòng)事件
-(void)setContentOffset:(CGPoint)contentOffset{
[super setContentOffset:contentOffset];
if (self.typeNested == LLNestedTableViewTypeNormal) {
return; // 普通類(lèi)型不做修改
}
CGFloat y = contentOffset.y;
switch (self.typeNested) {
// 主列表類(lèi)型
case LLNestedTableViewTypeMain:
{
CGFloat stayPosition = 0;
// 獲取到停留的位置
if ([self.delegateNested respondsToSelector:@selector(llNestedTableViewStayPosition:)]) {
stayPosition = [self.delegateNested llNestedTableViewStayPosition:self];
}
if (self.canScroll) {
// 當(dāng)主列表滾動(dòng)位置超過(guò)預(yù)設(shè)時(shí),我們發(fā)出通知管闷,讓子列表不能滾動(dòng)
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; // 讓其“停止”在預(yù)設(shè)位置粥脚,取消動(dòng)畫(huà),否則會(huì)因?yàn)闀r(shí)間差一直循環(huán)
[super setContentOffset:contentOffset animated:NO];
}
}
break;
// 子列表類(lèi)型
case LLNestedTableViewTypeSub:
{
if (self.canScroll) {
// 當(dāng)子列表被下拉到最初位置時(shí)包个,我們讓其“停止”刷允,并發(fā)送通知,讓主列表可以滾動(dòng)
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碧囊、通知處理
// 這里觸發(fā)手動(dòng)控制主從列表的滾動(dòng)與否
LLNestedTableView* table = note.object;
if (self.typeNested == LLNestedTableViewTypeNormal ||
![table isKindOfClass:UITableView.class]||
(self.flag.length && ![self.flag isEqualToString:table.flag]))
{ return; }
// 當(dāng)發(fā)送通知方和當(dāng)前對(duì)象不一致树灶,則表示當(dāng)前對(duì)象需要開(kāi)啟滾動(dòng)
if (self != table) { self.canScroll = YES; }
// 把其他所有的sub都移動(dòng)到頂部,除去主的,其他table皆不能滾動(dòng)
if (table.typeNested == LLNestedTableViewTypeSub && self.typeNested == LLNestedTableViewTypeSub) {
[self setContentOffset:CGPointZero];
self.canScroll = NO;
}
使用
1糯而、父類(lèi)tableView
@property (strong ,nonatomic) LLNestedTableView *mainTable;
// 初始化父類(lèi)列表
-(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; // 列表類(lèi)型
}
return _mainTable;
}
注:需要將子列表加到列表上天通,最好是最后一個(gè)section的cell上,這樣比較靈活熄驼;其他位置也可以添加像寒,主要是需要配合懸停的位置使用
// !!!: 懸停的位置
- (CGFloat)llNestedTableViewStayPosition:(LLNestedTableView *)tableView{
return tableView.tableHeaderView.frame.size.height;
}
2、子類(lèi)列表
-(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; // 除了類(lèi)型要設(shè)置為子類(lèi)瓜贾,用法和系統(tǒng)類(lèi)型一樣
}
return _table;
}
2019-12-16 補(bǔ)充:
支持了 Cocoapods
诺祸,方便集成 : pod 'LLNestedTableView'
;
修復(fù)了 iOS 13 下滾動(dòng)異常的問(wèn)題祭芦;
新增了聯(lián)動(dòng)的標(biāo)識(shí)筷笨,防止多個(gè)頁(yè)面錯(cuò)誤通知;
新增了 KVO
的形式進(jìn)行聯(lián)動(dòng)。
2020-07-27 補(bǔ)充:
重構(gòu)了 LLNestedTableView胃夏,支持 列表視圖 + 集合視圖 的聯(lián)動(dòng)轴或。
注意:
1、懸停位置的設(shè)置改用回調(diào)的方式實(shí)現(xiàn)
2构订、如果你的內(nèi)容視圖非上述兩個(gè)視圖侮叮,如 UIScrollView,請(qǐng)自行轉(zhuǎn)換成上述兩個(gè)視圖悼瘾,或者根據(jù) 列表視圖 或者 集合視圖實(shí)現(xiàn)的方式自行實(shí)現(xiàn),大體思路不變
3审胸、關(guān)于內(nèi)容視圖 和 主列表 同時(shí)滾動(dòng)的問(wèn)題亥宿,目前解決方案是,手動(dòng)禁止某些滾動(dòng)視圖的滾動(dòng)事件砂沛。PS:另一種思路是改寫(xiě)關(guān)鍵方法 -gestureRecognizer: shouldRecognizeSimultaneouslyWithGestureRecognizer:
的值烫扼,筆者目前還沒(méi)有踩坑,只提供一種思路
4碍庵、UITableView/UICollectionView 需要彈性效果映企,UITableView 豎直方向默認(rèn)是開(kāi)啟的,UICollectionView 在數(shù)量少静浴,內(nèi)容視圖小于可見(jiàn)視圖時(shí)堰氓,彈性是關(guān)閉的,你需要設(shè)置 alwaysBounceVertical 為 YES/true