怎樣實(shí)現(xiàn)餓了么中選餐時(shí)兩級(jí)tableView聯(lián)動(dòng)效果?
先上效果圖讓大家感受一下:

聯(lián)動(dòng)效果
具體實(shí)現(xiàn)步驟:
首先分解一下,實(shí)現(xiàn)這個(gè)需求主要是兩點(diǎn),一是點(diǎn)擊左邊tableView,同時(shí)滾動(dòng)右邊tableView到具體的位置。二是拖動(dòng)右邊tableView選中左邊tableView對(duì)應(yīng)的某一行。要實(shí)現(xiàn)這個(gè)需求有一點(diǎn)很重要:左邊的tableView每一行對(duì)應(yīng)的是右邊tableView的每個(gè)分區(qū),OK,Just Do It.
實(shí)現(xiàn)點(diǎn)擊左邊tableView同時(shí)滾動(dòng)右邊tableView,很簡單,只需要實(shí)現(xiàn)tableView的代理方法- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;然后在代理方法里邊拿到右邊的tableView,實(shí)現(xiàn)讓其滾動(dòng)到第indexPath.row分區(qū),第0行即可,代碼如下:
- (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath{// 如果點(diǎn)擊的是右邊的tableView,不做任何處理if(tableView ==self.rightTableView)return;// 點(diǎn)擊左邊的tableView,設(shè)置選中右邊的tableView某一行。左邊的tableView的每一行對(duì)應(yīng)右邊tableView的每個(gè)分區(qū)[self.rightTableView selectRowAtIndexPath:[NSIndexPathindexPathForRow:0inSection:indexPath.row] animated:YESscrollPosition:UITableViewScrollPositionTop];}
我們這里不處理右邊tableView的點(diǎn)擊事件,所以if (tableView == self.rightTableView) return;
接下來我們實(shí)現(xiàn) 拖動(dòng)右邊tableView選中左邊tableView對(duì)應(yīng)的某一行,我們要?jiǎng)討B(tài)選中左邊的tableView,就需要拿到現(xiàn)在滾動(dòng)到了那個(gè)分區(qū),UITableView有兩個(gè)代理方法,- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section // 一個(gè)頭標(biāo)題即將顯示的時(shí)候掉用和- (void)tableView:(UITableView *)tableView didEndDisplayingHeaderView:(UIView *)view forSection:(NSInteger)section // 一個(gè)頭標(biāo)題即將消失的時(shí)候掉用
利用這兩個(gè)方法就可以拿到當(dāng)前所在分區(qū)實(shí)現(xiàn)這個(gè)功能了。
但是我總覺得這個(gè)方法不好,還有個(gè)更簡單的方法,其實(shí)tableView有個(gè)極不常用,但很牛X的方法,叫做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.
簡單意思就是,它返回一個(gè)裝著目前屏幕上可見的cell的indexPath集合。
好的,重點(diǎn)來了,拿到這個(gè)集合,不就能拿到目前屏幕上頂端的cell的indexpath了嗎,那就如愿以償?shù)哪玫浆F(xiàn)在所在第indexpath.section個(gè)分區(qū)了。
說了這么多,上代碼:
#pragma mark - UIScrollViewDelegate-(void)scrollViewDidScroll:(UIScrollView*)scrollView{// 監(jiān)聽tableView滑動(dòng)// 如果現(xiàn)在滑動(dòng)的是左邊的tableView,不做任何處理if((UITableView*)scrollView ==self.leftTableView)return;// 滾動(dòng)右邊tableView,設(shè)置選中左邊的tableView某一行。indexPathsForVisibleRows屬性返回屏幕上可見的cell的indexPath數(shù)組,利用這個(gè)屬性就可以找到目前所在的分區(qū)[self.leftTableView selectRowAtIndexPath:[NSIndexPathindexPathForRow:self.rightTableView.indexPathsForVisibleRows.firstObject.section inSection:0] animated:YESscrollPosition:UITableViewScrollPositionMiddle];}
稍微解釋一下,首先監(jiān)聽scrollView的拖動(dòng),本demo不處理左邊tableView的滾動(dòng),所以if ((UITableView *)scrollView == self.leftTableView) return;
self.rightTableView.indexPathsForVisibleRows.firstObject.section這句是拿到當(dāng)前屏幕上可見cell的第一行cell所在的分區(qū),然后讓左邊的tableView選中第0分區(qū)(它只有一個(gè)分區(qū))的這一行就OK了。
補(bǔ)充下:下邊評(píng)論提到說點(diǎn)擊左邊tableView的時(shí)候會(huì)有陰影效果,其實(shí)是這樣的,點(diǎn)擊左邊的tableView,右邊的tableView是從當(dāng)前位置動(dòng)畫滾動(dòng)到相應(yīng)位置的,既然有滾動(dòng),就會(huì)調(diào)- (void)scrollViewDidScroll:(UIScrollView *)scrollView這個(gè)代理方法,說白了就是拖動(dòng)了右邊tableView,拖動(dòng)右邊的過程中會(huì)陸續(xù)選中左邊。那我想大家就明白了。
如果不想要這個(gè)效果,有兩個(gè)辦法,一個(gè)是直接吧- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath中的動(dòng)畫滾動(dòng)的屬性animated值改成NO
- (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath{// 如果點(diǎn)擊的是右邊的tableView,不做任何處理if(tableView ==self.rightTableView)return;// 點(diǎn)擊左邊的tableView,設(shè)置選中右邊的tableView某一行。左邊的tableView的每一行對(duì)應(yīng)右邊tableView的每個(gè)分區(qū)[self.rightTableView selectRowAtIndexPath:[NSIndexPathindexPathForRow:0inSection:indexPath.row] animated:NOscrollPosition:UITableViewScrollPositionTop];}
這樣做右邊的tableView就是無動(dòng)畫滾動(dòng)了,也就不會(huì)再調(diào)scrollViewDidScroll:方法。但是如果還想右邊tableViewyou滾動(dòng)效果,另一種解決方法是:把- (void)scrollViewDidScroll:(UIScrollView *)scrollView方法換成- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView這個(gè)代理方法方法就行了。餓了么有的界面好像就是這樣做的,但是有bug(估計(jì)餓了么沒測(cè)出來),這個(gè)方法的注釋為
// called when scroll view grinds to a halt 當(dāng)滾動(dòng)視圖戛然而止
根據(jù)~.~親測(cè),拖拽之后,這個(gè)方法調(diào)用與否在于你的手指是否在動(dòng)畫停止之前離開了屏幕,如果在動(dòng)畫結(jié)束之前手指離開屏幕,此方法調(diào)用沒什么問題。but,如果動(dòng)畫已經(jīng)停止,再把手指拿開,這個(gè)方法是不會(huì)調(diào)的。有圖有真相:

WZBLinkageTableViewGif.gif
解決這個(gè)bug的關(guān)鍵在于,讓手指離開的時(shí)候手動(dòng)調(diào)一次這個(gè)代理方法,那怎么才能知道手指什么時(shí)候離開呢?scrollView給我們了另一個(gè)代理方法:- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset這個(gè)方法在結(jié)束拖拽的時(shí)候調(diào),正好解決了我們的問題:
- (void)scrollViewWillEndDragging:(UIScrollView*)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inoutCGPoint*)targetContentOffset{// 推拽將要結(jié)束的時(shí)候手動(dòng)調(diào)一下這個(gè)方法[selfscrollViewDidEndDecelerating:scrollView];}
OK \(^o^)/~解決問題!
加本菇?jīng)鯭群562984021一起交流咯,大家一起學(xué)習(xí)討論,共同進(jìn)步吧!~.~
(機(jī)會(huì)永遠(yuǎn)是留給那些有準(zhǔn)備的人)? ? ??謝謝大家支持 ?。?!