已經(jīng)是2020年了,多層UIScrollView嵌套,也已是iOS中老生常談的問題。
Apple官方不建議將ScrollView嵌套使用,可是產(chǎn)品和設(shè)計師們就是戒不掉這個癮,沒辦法,既然是打工,拿人錢財替人解憂。
網(wǎng)上有眾多實現(xiàn)此功能的方案,但是魚龍混雜,而且大部分也不完善,完善的代碼又特別復(fù)雜,整合工作量較大,這兩天正好有時間和機(jī)會,好好研究和思考了一下最簡單的解決方案。
先上個效果圖

解決的代碼基本只在ScrollViewDidScroll里做了邏輯處理,對于已經(jīng)做好了XIB的小伙伴很友好
大概思路就是在內(nèi)層的UIScrollView(UITableView也算是UIScrollView)的ScrollViewDidScroll代理方法里面做邏輯判斷,看是否允許自己滾動。
首先我的層級結(jié)構(gòu)如下圖(藍(lán)色的內(nèi)ScrollView,會橫向平鋪多個)

通常這種情況,在XIB中畫好了外部框之后,內(nèi)部的這個UITableVIew(UIScrollView)是由根據(jù)服務(wù)器返回的多少個分類,由代碼編寫后橫向平鋪的,這也就方便了我們使用自定義類。所以,首先我們還是逃不掉重寫一個UITableView,像這樣
QCTableView.h
@interface QCTableView : UITableView
///記錄一下坐標(biāo),為了等下滑動的時候判斷向上還是向下
@property (nonatomic, assign) CGFloat tableY;
@end
QCTableView.m
@implementation QCTableView
// 返回YES表示可以繼續(xù)傳遞觸摸事件,這樣便可實現(xiàn)了兩個嵌套的scrollView同時滾動。
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
@end
沒錯,就重寫一個方法+一個屬性。
重寫這個是免不了的,我已經(jīng)試過很多方法,最后都只有重寫這個shouldRecognizeSimultaneouslyWithGestureRecognizer才能完美地將ScrollView接到的滑動手勢傳給下一層
然后就是當(dāng)前持有主ScrollView和內(nèi)ScrollView(或TableView)的Controller的ScrollViewDidScroll代理方法了
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if([scrollView isKindOfClass:[UITableView class]]){
//內(nèi)部ScrollView
NSLog(@"滾動UITableView");
QCTableView *qctv = (QCTableView*)scrollView;
//判斷向上滑動還是向下滑動
if (scrollView.contentOffset.y - qctv.tableY > 0) {
//向上
//_main_scrollView就是我的外層主ScrollView,當(dāng)它小于116或者大于0的時候,這時候還在向上滑動就說明在移動它,這時候禁止內(nèi)部ScrollView滾動,一直設(shè)置內(nèi)部ScrollView的Y軸為最后一次記錄的Y軸,以禁止它的滾動。
if (_main_scrollView.contentOffset.y<116&&scrollView.contentOffset.y>0) {
[scrollView setContentOffset:CGPointMake(0, qctv.tableY)];
}
/*mainScrollContentY是我在全局聲明的變量
{
CGFloat mainScrollContentY;
}
有條件的可以將它寫在主ScrollView的重寫對象里,我這里沒有為主Scroll重寫對象,就放在當(dāng)前Controller里了為了簡單
*/
//只在主ScrollView滾動了的條件下記錄它的坐標(biāo)。
mainScrollContentY = _main_scrollView.contentOffset.y;
}else if(scrollView.contentOffset.y - qctv.tableY < 0){
//向下
//內(nèi)部ScrollView的Y大于0說明內(nèi)部ScrollView沒到頂,這時候向下移動也應(yīng)該將_main_scrollView固定在最上方,一直設(shè)置外部_main_scrollView的Y軸為最后一次記錄的Y軸,以禁止它的滾動。
if (scrollView.contentOffset.y>=0) {
[_main_scrollView setContentOffset:CGPointMake(0, mainScrollContentY)];
}else if (_main_scrollView.contentOffset.y>0) {
//如果內(nèi)部Scroll的ContentOffset的Y小于0,說明內(nèi)部ScrollView到頂了,外部_main_scrollView的ContentOffset的Y又大于0,這時候向下移動應(yīng)該將外部_main_scrollView拉下來,一直設(shè)置內(nèi)部ScrollView的Y軸為0,以禁止它的滾動。
[scrollView setContentOffset:CGPointMake(0, 0)];
}
//只在主ScrollView滾動了的條件下記錄它的坐標(biāo)。
mainScrollContentY = _main_scrollView.contentOffset.y;
}
qctv.tableY = scrollView.contentOffset.y;
}else if(scrollView==_main_scrollView){
//外部主ScrollView
//這里主要是設(shè)置邊界,也就是頭部的高度,不允許_main_scrollView的contentOffset的Y軸大于116或小于0。
NSLog(@"滾動MainScroll");
if (scrollView.contentOffset.y>116) {
[scrollView setContentOffset:CGPointMake(0, 116)];
}else if(scrollView.contentOffset.y<0){
[scrollView setContentOffset:CGPointMake(0, 0)];
}
}
}
注釋寫的很詳細(xì)了,以上就是所有的代碼了,其實UITableView中的tableY和mainScrollContentY是在實現(xiàn)了基本功能的基礎(chǔ)上加的,為的是修復(fù)一些坐標(biāo)上的BUG,理論上不應(yīng)該對任何一個ScrollView通過一直設(shè)置為固定的Y軸值(116或0)來禁止它的滾動,而是應(yīng)該通過一直設(shè)置它為最后一次允許滾動時記錄的Y軸,來避免跳動現(xiàn)象。但是為了代碼簡單明了,就先不做過多處理了。
Demo在這里,已上傳到GitHub
https://github.com/mmMrz/YXSccrollView
做這篇文章時,主要是因為我已經(jīng)用XIB寫好了這個“主Scroll”套“橫向Scroll”又套“多層UITableView”的XIB了,界面畫好了,不想再用github上完善的的實現(xiàn)庫去重新開發(fā)了,這樣的方法簡單明了,易用,移植性高。目前是我能想到的最簡單又相對完善的實現(xiàn)方法了。有更好的方法或者更簡單套用的三方庫,歡迎評論探討。
QQ群:653317062 (失蹤的新華社)