图片 4

实现外卖选餐时两级tableView联动效果,tableView联动你就看我

最近实现了下饿了么中选餐时两级tableView联动效果,先上效果图,大家感受一下:

让我们共同学习一下tableView联动,我这也是从简书上看来的一篇文章,来亲自实现一下。学习文章地址:http://www.jianshu.com/p/dfb73aa08602

让我们共同学习一下tableView联动,我这也是从简书上看来的一篇文章,来亲自实现一下。学习文章地址:

图片 1联动效果

先上图:

先上图:

下面说下具体实现步骤:首先分解一下,实现这个需求主要是两点,一是点击左边tableView,同时滚动右边tableView到具体的位置。二是拖动右边tableView选中左边tableView对应的某一行。要实现这个需求有一点很重要:左边的tableView每一行对应的是右边tableView的每个分区,OK,Just
Do It.

[图片上传失败…(image-e5c7d1-1513741413430)]

图片 21212.gif

实现点击左边tableView同时滚动右边tableView,很简单,只需要实现tableView的代理方法- tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;然后在代理方法里边拿到右边的tableView,实现让其滚动到第indexPath.row分区,第0行即可,代码如下:

功能需求(两点):
功能需求:
- tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ // 如果点击的是右边的tableView,不做任何处理 if (tableView == self.rightTableView) return; // 点击左边的tableView,设置选中右边的tableView某一行。左边的tableView的每一行对应右边tableView的每个分区 [self.rightTableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:indexPath.row] animated:YES scrollPosition:UITableViewScrollPositionTop];}
  • 点击左边tableVIew的cell,右边的tableView滑动至指定位置。

  • 滑动右边tableView的cell,左边的tableView滑动至指定位置。

  • 点击左边tableVIew的cell,右边的tableView滑动至指定位置。
  • 滑动右边tableView的cell,左边的tableView滑动至指定位置。

我们这里不处理右边tableView的点击事件,所以if (tableView == self.rightTableView) return;

具体思路:
具体思路:

接下来我们实现
拖动右边tableView选中左边tableView对应的某一行,我们要动态选中左边的tableView,就需要拿到现在滚动到了那个分区,UITableView有两个代理方法,- tableView:(UITableView *)tableView willDisplayHeaderView:view forSection:(NSInteger)section // 一个头标题即将显示的时候掉用- tableView:(UITableView *)tableView didEndDisplayingHeaderView:view forSection:(NSInteger)section // 一个头标题即将消失的时候掉用利用这两个方法就可以拿到当前所在分区实现这个功能了。

  • 实现点击左边tableView同时滚动右边tableView,只需要实现tableView的代理方法。

    • (void)tableView:(UITableView *)tableView
      didSelectRowAtIndexPath:(NSIndexPath *)indexPath;

    然后在代理方法里边拿到右边的tableView,实现让其滚动到第indexPath.row分区,第0行即可,代码如下:

    • (void)tableView:(UITableView *)tableView
      didSelectRowAtIndexPath:(NSIndexPath *)indexPath{

    // 如果点击的是右边的tableView,不做任何处理

    if (tableView == self.rightTableView) return;

    // 点击左边的tableView,设置选中右边的tableView某一行。

    左边的tableView的每一行对应右边tableView的每个组第0行

    [self.rightTableView selectRowAtIndexPath:[NSIndexPath
    indexPathForRow:0 inSection:indexPath.row] animated:YES
    scrollPosition:UITableViewScrollPositionTop];

    }

    我们这里不处理右边tableView的点击事件,所以

    if (tableView == self.rightTableView) return;

    接下来,拖动右边的tableView,来找到左边tableView对应的某一行。

    我们要动态选中左边的tableView,就需要拿到右边tableView现在滚动到了那个组。UITableView有两个代理方法:

    // 一个头标题即将显示的时候调用

    • (void)tableView:(UITableView *)tableView
      willDisplayHeaderView:(UIView *)view
      forSection:(NSInteger)section

    // 一个头标题即将消失的时候掉用

    • (void)tableView:(UITableView *)tableView
      didEndDisplayingHeaderView:(UIView *)view
      forSection:(NSInteger)section

    利用这两个方法就可以拿到当前所在哪个组实现这个功能了。

  • 实现点击左边tableView同时滚动右边tableView,只需要实现tableView的代理方法。-
    tableView:(UITableView *)tableView
    didSelectRowAtIndexPath:(NSIndexPath
    *)indexPath;然后在代理方法里边拿到右边的tableView,实现让其滚动到第indexPath.row分区,第0行即可,代码如下:-
    tableView:(UITableView *)tableView
    didSelectRowAtIndexPath:(NSIndexPath *)indexPath{//
    如果点击的是右边的tableView,不做任何处理if (tableView ==
    self.rightTableView) return;//
    点击左边的tableView,设置选中右边的tableView某一行。左边的tableView的每一行对应右边tableView的每个组第0行[self.rightTableView
    selectRowAtIndexPath:[NSIndexPath indexPathForRow:0
    inSection:indexPath.row] animated:YES
    scrollPosition:UITableViewScrollPositionTop];}我们这里不处理右边tableView的点击事件,所以if
    (tableView == self.rightTableView)
    return;接下来,拖动右边的tableView,来找到左边tableView对应的某一行。我们要动态选中左边的tableView,就需要拿到右边tableView现在滚动到了那个组。UITableView有两个代理方法://
    一个头标题即将显示的时候调用- tableView:(UITableView *)tableView
    willDisplayHeaderView:view forSection:(NSInteger)section//
    一个头标题即将消失的时候掉用- tableView:(UITableView *)tableView
    didEndDisplayingHeaderView:view
    forSection:(NSInteger)section利用这两个方法就可以拿到当前所在哪个组实现这个功能了。

但是我总觉得这个方法不好,还有个更简单的方法,其实tableView有个极不常用,但很牛X的方法,叫做indexPathsForVisibleRows,官方文档解释是:

还有一个更简单的方法,在tableView中,是不常用的,叫做,indexPathsForVisibleRows官方文档解释是:

还有一个更简单的方法,在tableView中,是不常用的,叫做,indexPathsForVisibleRows官方文档解释是:

The value of this property is an array of NSIndexPath objects each
representing a row index and section index that together identify a
visible row in the table view. If no rows are visible, the value is
nil.

The value of this property is an array of NSIndexPath objects each
representing a row index and section index that together identify a
visible row in the table view. If no rows are visible, the value is
nil.

大概意思是:返回一个屏幕上可见的cell的indexPath集合

The value of this property is an array of NSIndexPath objects each
representing a row index and section index that together identify a
visible row in the table view. If no rows are visible, the value is
nil.大概意思是:返回一个屏幕上可见的cell的indexPath集合

简单意思就是,它返回一个装着目前屏幕上可见的cell的indexPath集合。

好的,重点来了,拿到这个集合,不就能拿到目前屏幕上顶端的cell的indexpath了吗,那就如愿以偿的拿到现在所在第indexpath.section个分区了。

好的,重点来了,拿到这个集合,不就能拿到目前屏幕上顶端的cell的indexpath了吗,那就如愿以偿的拿到现在所在第indexpath.section个分区了。上代码:#pragma
mark – UIScrollViewDelegate-scrollViewDidScroll:(UIScrollView
*)scrollView{// 监听tableView滑动//
如果现在滑动的是左边的tableView,不做任何处理if ((UITableView
*)scrollView == self.leftTableView) return;//
滚动右边tableView,设置选中左边的tableView某一行。indexPathsForVisibleRows属性返回屏幕上可见的cell的indexPath数组,利用这个属性就可以找到目前所在的分区[self.leftTableView
selectRowAtIndexPath:[NSIndexPath
indexPathForRow:self.rightTableView.indexPathsForVisibleRows.firstObject.section
inSection:0] animated:YES
scrollPosition:UITableViewScrollPositionMiddle];}[详解]:

好的,重点来了,拿到这个集合,不就能拿到目前屏幕上顶端的cell的indexpath了吗,那就如愿以偿的拿到现在所在第indexpath.section个分区了。

上代码:

  • 首先监听scrollView的拖动,本demo不处理左边tableView的滚动,所以if
    ((UITableView *)scrollView == self.leftTableView) return;
  • 下一句代码的意思是,拿到当前屏幕上可见cell的第一行cell所在的分区,然后让左边的tableView选中第0分区的这一行就OK了。self.rightTableView.indexPathsForVisibleRows.firstObject.sectiondemo地址:

说了这么多,上代码:

pragma mark – UIScrollViewDelegate

-(void)scrollViewDidScroll:(UIScrollView *)scrollView{

// 监听tableView滑动

// 如果现在滑动的是左边的tableView,不做任何处理

if ((UITableView *)scrollView == self.leftTableView) return;

// 滚动右边tableView,设置选中左边的tableView某一行。

indexPathsForVisibleRows属性返回屏幕上可见的cell的indexPath数组,

利用这个属性就可以找到目前所在的分区

[self.leftTableView selectRowAtIndexPath:[NSIndexPath
indexPathForRow:self.rightTableView.indexPathsForVisibleRows.firstObject.section
inSection:0] animated:YES
scrollPosition:UITableViewScrollPositionMiddle];

}

[详解]:

  • 首先监听scrollView的拖动,本demo不处理左边tableView的滚动,所以

    if ((UITableView *)scrollView == self.leftTableView) return;

  • 下一句代码的意思是,拿到当前屏幕上可见cell的第一行cell所在的分区,然后让左边的tableView选中第0分区(它只有一个分区)的这一行就OK了。

    self.rightTableView.indexPathsForVisibleRows.firstObject.section

    demo地址:https://github.com/RenZhengYang/ZYLinkageTableView


————————–补充——————————-

1.点击左边tableView的时候会有阴影效果

  • 点击左边的tableView,右边的tableView是从当前位置动画滚动到相应位置的,既然有滚动,就会调

    • (void)scrollViewDidScroll:(UIScrollView *)scrollView

    这个代理方法,说白了就是拖动了右边tableView,拖动右边的过程中会陆续选中左边。

如果不想要这个效果,有两个办法,一个是直接把

  • (void)tableView:(UITableView *)tableView
    didSelectRowAtIndexPath:(NSIndexPath *)indexPath

中的动画滚动的属性animated值改成NO

  • (void)tableView:(UITableView *)tableView
    didSelectRowAtIndexPath:(NSIndexPath *)indexPath{

// 如果点击的是右边的tableView,不做任何处理

if (tableView == self.rightTableView) return;

// 点击左边的tableView,设置选中右边的tableView某一行。

左边的tableView的每一行对应右边tableView的每个分区

[self.rightTableView selectRowAtIndexPath:[NSIndexPath
indexPathForRow:0 inSection:indexPath.row] animated:NO
scrollPosition:UITableViewScrollPositionTop];

}

这样做右边的tableView就是无动画滚动了,也就不会再调scrollViewDidScroll:方法。

但是如果还想右边tableViewyou滚动效果,另一种解决方法是把

  • (void)scrollViewDidScroll:(UIScrollView *)scrollView

方法换成

  • (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

这个代理方法方法就行了。有的界面好像就是这样做的,但是有bug(估计饿了么没测出来),这个方法的注释为

// called when scroll view grinds to a halt
当滚动视图戛然而止--有道翻译如是说

这个方法调用与否在于你的手指是否在动画停止之前离开了屏幕,如果在动画结束之前手指离开屏幕,此方法调用没什么问题。but,如果动画已经停止,再把手指拿开,这个方法是不会调的。

解决这个bug的关键在于,让手指离开的时候手动调一次这个代理方法,那怎么才能知道手指什么时候离开呢?scrollView给我们了另一个代理方法:

  • (void)scrollViewWillEndDragging:(UIScrollView *)scrollView
    withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint
    *)targetContentOffset

这个方法在结束拖拽的时候调,正好解决了我们的问题:

  • (void)scrollViewWillEndDragging:(UIScrollView *)scrollView
    withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint
    *)targetContentOffset{

// 推拽将要结束的时候手动调一下这个方法

[self scrollViewDidEndDecelerating:scrollView];

}


1.// 默认选择左边tableView的第一行

[_leftTableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] animated:YES scrollPosition:UITableViewScrollPositionTop];

必须在tableView数据加载完毕后,才会调用。否则会报错。

————————–补充——————————-

 #pragma mark - UIScrollViewDelegate-scrollViewDidScroll:(UIScrollView *)scrollView{ // 监听tableView滑动 // 如果现在滑动的是左边的tableView,不做任何处理 if ((UITableView *)scrollView == self.leftTableView) return; // 滚动右边tableView,设置选中左边的tableView某一行。indexPathsForVisibleRows属性返回屏幕上可见的cell的indexPath数组,利用这个属性就可以找到目前所在的分区 [self.leftTableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:self.rightTableView.indexPathsForVisibleRows.firstObject.section inSection:0] animated:YES scrollPosition:UITableViewScrollPositionMiddle];}

1.点击左边tableView的时候会有阴影效果

稍微解释一下,首先监听scrollView的拖动,本demo不处理左边tableView的滚动,所以if ((UITableView *)scrollView == self.leftTableView) return;``self.rightTableView.indexPathsForVisibleRows.firstObject.section这句是拿到当前屏幕上可见cell的第一行cell所在的分区,然后让左边的tableView选中第0分区的这一行就OK了。

  • 点击左边的tableView,右边的tableView是从当前位置动画滚动到相应位置的,既然有滚动,就会调-
    scrollViewDidScroll:(UIScrollView
    *)scrollView这个代理方法,说白了就是拖动了右边tableView,拖动右边的过程中会陆续选中左边。

欢迎收藏本文章,最后附上demo ,Github传送门:demo

如果不想要这个效果,有两个办法,一个是直接把- tableView:(UITableView
)tableView didSelectRowAtIndexPath:(NSIndexPath)indexPath中的动画滚动的属性*animated值改成NO\*-
tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath
*)indexPath{// 如果点击的是右边的tableView,不做任何处理if (tableView
== self.rightTableView) return;//
点击左边的tableView,设置选中右边的tableView某一行。左边的tableView的每一行对应右边tableView的每个分区[self.rightTableView
selectRowAtIndexPath:[NSIndexPath indexPathForRow:0
inSection:indexPath.row] animated:NO
scrollPosition:UITableViewScrollPositionTop];}这样做右边的tableView就是无动画滚动了,也就不会再调scrollViewDidScroll:方法。但是如果还想右边tableViewyou滚动效果,另一种解决方法是把-
scrollViewDidScroll:(UIScrollView *)scrollView方法换成-
scrollViewDidEndDecelerating:(UIScrollView
*)scrollView这个代理方法方法就行了。有的界面好像就是这样做的,但是有bug(估计饿了么没测出来),这个方法的注释为

--------------这叫分割线---------------补充下:下边评论提到说点击左边tableView的时候会有阴影效果,其实是这样的,点击左边的tableView,右边的tableView是从当前位置动画滚动到相应位置的,既然有滚动,就会调- scrollViewDidScroll:(UIScrollView *)scrollView这个代理方法,说白了就是拖动了右边tableView,拖动右边的过程中会陆续选中左边。那我想大家就明白了。

// called when scroll view grinds to a halt
当滚动视图戛然而止--有道翻译如是说

如果不想要这个效果,有两个办法,一个是直接吧- tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath中的动画滚动的属性animated值改成NO

这个方法调用与否在于你的手指是否在动画停止之前离开了屏幕,如果在动画结束之前手指离开屏幕,此方法调用没什么问题。but,如果动画已经停止,再把手指拿开,这个方法是不会调的。

- tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ // 如果点击的是右边的tableView,不做任何处理 if (tableView == self.rightTableView) return; // 点击左边的tableView,设置选中右边的tableView某一行。左边的tableView的每一行对应右边tableView的每个分区 [self.rightTableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:indexPath.row] animated:NO scrollPosition:UITableViewScrollPositionTop];}

解决这个bug的关键在于,让手指离开的时候手动调一次这个代理方法,那怎么才能知道手指什么时候离开呢?scrollView给我们了另一个代理方法:-
scrollViewWillEndDragging:(UIScrollView *)scrollView
withVelocity:velocity targetContentOffset:(inout CGPoint
*)targetContentOffset这个方法在结束拖拽的时候调,正好解决了我们的问题:-
scrollViewWillEndDragging:(UIScrollView *)scrollView
withVelocity:velocity targetContentOffset:(inout CGPoint
*)targetContentOffset{// 推拽将要结束的时候手动调一下这个方法[self
scrollViewDidEndDecelerating:scrollView];}

这样做右边的tableView就是无动画滚动了,也就不会再调scrollViewDidScroll:方法。但是如果还想右边tableViewyou滚动效果,另一种解决方法是:把- scrollViewDidScroll:(UIScrollView *)scrollView方法换成- scrollViewDidEndDecelerating:(UIScrollView *)scrollView这个代理方法方法就行了。饿了么有的界面好像就是这样做的,但是有bug(估计饿了么没测出来),这个方法的注释为

  • 学习小记:1.// 默认选择左边tableView的第一行[_leftTableView
    selectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]
    animated:YES
    scrollPosition:UITableViewScrollPositionTop];必须在tableView数据加载完毕后,才会调用。否则会报错。

// called when scroll view grinds to a halt
当滚动视图戛然而止--有道翻译如是说

本篇文章,与学习作者文章相同,只不过排版,被我改了,demo中的写法,被我改了。整理一下,学习备用。若有侵权,请及时联系,我会做更改。如有欠缺,或有好的想法,请即时提出。互相交流学习,欢迎点星,鼓励!!!

根据本人亲测,拖拽之后,这个方法调用与否在于你的手指是否在动画停止之前离开了屏幕,如果在动画结束之前手指离开屏幕,此方法调用没什么问题。but,如果动画已经停止,再把手指拿开,这个方法是不会调的。有图有真相:

图片 3WZBLinkageTableViewGif.gif

解决这个bug的关键在于,让手指离开的时候手动调一次这个代理方法,那怎么才能知道手指什么时候离开呢?scrollView给我们了另一个代理方法:- scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:velocity targetContentOffset:(inout CGPoint *)targetContentOffset这个方法在结束拖拽的时候调,正好解决了我们的问题:

- scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:velocity targetContentOffset:(inout CGPoint *)targetContentOffset{ // 推拽将要结束的时候手动调一下这个方法 [self scrollViewDidEndDecelerating:scrollView];}

OK,解决问题!后续,github也会跟进的,感谢大家的喜欢和支持!

Github已经更新,最新demo在这 demo

您还可以加入我们的群,大家庭期待您的加入!

图片 4我们的社区