最近實現了下餓了麼中選餐時兩級tableView聯動效果,先上效果圖,大家感受一下:
下面說下具體實現步驟:
首先分解一下,實現這個需求主要是兩點,一是點選左邊tableView,同時滾動右邊tableView到具體的位置。二是拖動右邊tableView選中左邊tableView對應的某一行。要實現這個需求有一點很重要:左邊的tableView每一行對應的是右邊tableView的每個分割槽,OK,Just Do It.
實現點選左邊tableView同時滾動右邊tableView,很簡單,只需要實現tableView的代理方法- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
然後在代理方法裡邊拿到右邊的tableView,實現讓其滾動到第indexPath.row分割槽,第0行即可,程式碼如下:
1 2 3 4 5 6 |
- (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:YES scrollPosition:UITableViewScrollPositionTop]; } |
我們這裡不處理右邊tableView的點選事件,所以if (tableView == self.rightTableView) return;
接下來我們實現 拖動右邊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有個極不常用,但很牛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.
簡單意思就是,它返回一個裝著目前螢幕上可見的cell的indexPath集合。
好的,重點來了,拿到這個集合,不就能拿到目前螢幕上頂端的cell的indexpath了嗎,那就如願以償的拿到現在所在第indexpath.section個分割槽了。
說了這麼多,上程式碼:
1 2 3 4 5 6 7 |
#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;
self.rightTableView.indexPathsForVisibleRows.firstObject.section
這句是拿到當前螢幕上可見cell的第一行cell所在的分割槽,然後讓左邊的tableView選中第0分割槽(它只有一個分割槽)的這一行就OK了。
歡迎收藏本文章,最後附上demo ,Github傳送門:demo
--------------這叫分割線---------------
補充下:下邊評論提到說點選左邊tableView的時候會有陰影效果,其實是這樣的,點選左邊的tableView,右邊的tableView是從當前位置動畫滾動到相應位置的,既然有滾動,就會調- (void)scrollViewDidScroll:(UIScrollView *)scrollView
這個代理方法,說白了就是拖動了右邊tableView,拖動右邊的過程中會陸續選中左邊。那我想大家就明白了。
如果不想要這個效果,有兩個辦法,一個是直接吧- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
中的動畫滾動的屬性animated值改成NO
1 2 3 4 5 6 |
- (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
這個方法在結束拖拽的時候調,正好解決了我們的問題:
1 2 3 4 |
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset{ // 推拽將要結束的時候手動調一下這個方法 [self scrollViewDidEndDecelerating:scrollView]; } |
OK,解決問題!
後續,github也會跟進的,感謝大家的喜歡和支援!
Github已經更新,最新demo在這 demo