iOS UITableView的嵌套和sectionHeader
2017-09-26 本文已影响1288人
小白进城
应用情景
情景一:
说明:是不是和tableView的Plain类型一样,其实这个是由两个列表实现的
情景二:
说明:此时,就可以发现和普通的列表有些不一样了
情景三:
说明:笔者最初就是为了实现这种情况,由于项目需求,需要防QQ空间,不同的是需要类型的切换,当时没想到好的解决方案,最后受同事启发,在其demo上进行修改,使得tableView可以满足大部分的悬停需求
思路说明
1、由于是两个tableView嵌套实现,所以首要就是兼容手势,使得我们的拖拽手势可以向下传递
2、通过改变列表的contentOffset来让列表是否"滚动"
3、子列表默认不滚动,当父列表滚动到需要悬停的位置时,父列表"停止"滚动,子列表开始滚动
4、当子列表下拉,contentOffset.y小于0时,子列表"停止滚动",父列表开始"滚动"
5、使用通知来接收滚动消息
主要实现过程(代码)
首先继承UITableView新建LolitaTableView类,并定义两种类型
typedef NS_ENUM(NSInteger , LolitaTableViewType) {
LolitaTableViewTypeMain, // 父列表
LolitaTableViewTypeSub // 子列表
};
1、兼容手势
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
if (self.type==LolitaTableViewTypeMain) { // 父table类型需要兼容手势
return YES;
}
return NO;
}
2、注册通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(scrollStop:) name:kScrollStopNotificationName object:nil];
3、重写setContentOffset方法
- (void)setContentOffset:(CGPoint)contentOffset{
[super setContentOffset:contentOffset];
CGFloat y = contentOffset.y;
if(self.type == LolitaTableViewTypeMain){ // main类型
CGFloat stayPosition = self.tableHeaderView.frame.size.height; // 默认停留的位置
if ([self.delegate_StayPosition respondsToSelector:@selector(lolitaTableViewHeightForStayPosition:)]) {
stayPosition = [self.delegate_StayPosition lolitaTableViewHeightForStayPosition:self]; // 获取到停留的位置
}
if(self.canScroll == YES){
if(y > stayPosition){
contentOffset.y = stayPosition;
[super setContentOffset:contentOffset];
self.canScroll = NO;
// 发送通知,主类不可滚动
[[NSNotificationCenter defaultCenter] postNotificationName:kScrollStopNotificationName object:self userInfo:nil];
}else{ // main正常滚动
[super setContentOffset:contentOffset];
}
}else{ // main禁止滚动
contentOffset.y = stayPosition;
[super setContentOffset:contentOffset];
}
}else if(self.type == LolitaTableViewTypeSub){ // sub类型
if(self.canScroll == YES){
if(y < 0){
contentOffset.y = 0;
[super setContentOffset:contentOffset];
self.canScroll = NO;
// 发送通知,子类不可滚动
[[NSNotificationCenter defaultCenter] postNotificationName:kScrollStopNotificationName object:self userInfo:nil];
}else{ // sub正常滚动
[super setContentOffset:contentOffset];
}
}else{ // sub禁止滚动
contentOffset.y = 0;
[super setContentOffset:contentOffset];
}
}else{
[super setContentOffset:contentOffset];
}
}
4、通知处理
- (void)scrollStop:(NSNotification *)notification{
LolitaTableView *table = notification.object;
// 把其他所有的sub都移动到顶部
if (table.type == LolitaTableViewTypeSub && self.type == LolitaTableViewTypeSub) {
[self setContentOffset:CGPointZero];
}
if(self != table){ // 发送通知的table和当前self不是同一个时,则需要滚动
self.canScroll = YES;
}
}
使用
1、父类tableView
@property (strong ,nonatomic) LolitaTableView *mainTable;
// 初始化父类列表
-(LolitaTableView *)mainTable{
if (_mainTable==nil) {
_mainTable = [[LolitaTableView 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.delegate_StayPosition = self; // 悬停代理
_mainTable.tableFooterView = [UIView new];
_mainTable.showsVerticalScrollIndicator = NO;
_mainTable.type = LolitaTableViewTypeMain; // 列表类型
}
return _mainTable;
}
注:需要将子列表加到列表上,最好是最后一个section的cell上,这样比较灵活;其他位置也可以添加,主要是需要配合悬停的位置使用
// 实现悬停代理
// !!!: 悬停的位置
-(CGFloat)lolitaTableViewHeightForStayPosition:(LolitaTableView *)tableView{
// 悬停在第3个secion.y处
return [tableView rectForSection:2].origin.y;
}
2、子类列表
-(LolitaTableView *)table{
if (_table==nil) {
_table = [[LolitaTableView alloc] initWithFrame:CGRectZero];
_table.delegate = self;
_table.dataSource = self;
_table.showsVerticalScrollIndicator = NO;
_table.tableFooterView = [UIView new];
_table.type = LolitaTableViewTypeSub; // 除了类型要设置为子类,用法和系统类型一样
}
return _table;
}
2018-4-3 补充:
经测试,Sub类型的table需要放到Main类型的cell上,其他位置,如tableHeader,secionHeader等位置,系统并不调用setContentOffset方法,导致sub列表不能正常下拉,请注意