iOS開發(fā):多級列表的一種實現(xiàn)思路

去年項目改版前有個頁面需要一個二級列表,本來想做出一個多級列表demo出來,但是當時因為時間比較趕就沒有施行。最近正好有同學問起來,就抽時間來完成下這個拖了這么久的想法。
先放一下粗略的效果圖(主要提供一下思路,如果使用的話得根據需求自己來進行修改了)和鏈接DEMO鏈接

多級列表效果圖.gif

當時寫二級列表的時候使用的是UITableView的headView和分組來實現(xiàn)的,但是因為這次想要實現(xiàn)的是一個多級列表,并不確定是多少級,可能是三級、四級甚至是十級(當然移動端不太可能出現(xiàn)這么多級的列表),想來想去使用headView好像都滿足不了這個需求(可能是我的思維局限)。思考了一下覺得不使用headView,只要處理好模型的結構和源數據的管理問題,應該是可以實現(xiàn)的。

下面來說一下大概的思路:
首先創(chuàng)建UITableView必不可少,在不使用headView的情況下數據的結構就顯得尤為重要。
多級列表大概的數據結構應該是這樣的:



由圖中不難看出,每一個節(jié)點都有名字,每一個節(jié)點都有可能會包含若干的子節(jié)點,并且每個節(jié)點會有兩個:狀態(tài)展開或者關閉(當然這是最基本的,可能實際上會需要更多的屬性)。我們根據這些必須屬性來建立model,代碼如下

@interface TableViewModel : NSObject
//數據名稱
@property (copy, nonatomic) NSString *name;
//狀態(tài) YES展開、NO收起
@property (assign, nonatomic) BOOL open;
//子節(jié)點
@property (strong, nonatomic) NSMutableArray *array;
@end

有了最基本的model以后,我們先來構造一組四層的假數據(用遞歸也可以,這里我就偷懶硬寫了四層)。

//構造模擬數據
- (void)initData {
    //構造父節(jié)點
    for (int i = 0; i<5; i++) {
        TableViewModel *model = [[TableViewModel alloc]init];
        model.name = [NSString stringWithFormat:@"%i",i];
        model.array = [NSMutableArray array];
        //構造子節(jié)點
        for (int j = 6; j<10; j++) {
            TableViewModel *childModel = [[TableViewModel alloc]init];
            childModel.name = [NSString stringWithFormat:@"  %i",j];
            childModel.array = [NSMutableArray array];
            //構造孫子節(jié)點
            for (int k = 11; k<14; k++) {
                TableViewModel *grandsonModel = [[TableViewModel alloc]init];
                grandsonModel.name = [NSString stringWithFormat:@"      %i",k];
                grandsonModel.array = [NSMutableArray array];
                //構造曾孫節(jié)點
                for (int l = 15; l<17; l++) {
                    TableViewModel *grandsonSonModel = [[TableViewModel alloc]init];
                    grandsonSonModel.name = [NSString stringWithFormat:@"          %i",l];
                     //構造曾孫節(jié)點
                    [grandsonModel.array addObject:grandsonSonModel];
                }
                //孫子節(jié)點
                [childModel.array addObject:grandsonModel];
            }
            //子節(jié)點
            [model.array addObject:childModel];
        }
        //父節(jié)點
        [self.countArray addObject:model];
    }
}

構造完成后我們可以得到一個數組,這個數組里面的結構是這樣的:


數組的結構

數據構造完成以后的任務就是顯示了,那么怎么才能把這些數據按照我們要求的形式展現(xiàn)出來呢?
在不使用headView的情況下,tableView基本上就相當于一個分組。那么在控制器中建立一個數組,這個數組用來存儲將要顯示在tableView上的數據。只需管理這個數組中元素的個數與順序就可以實現(xiàn)我們的需求。

接著創(chuàng)建一個可變數組用來管理要顯示的數據。

@property (strong, nonatomic) NSMutableArray *countArray;

可以注意到,在構建數據的時候我首先將父節(jié)點加入到了這個數組中。將這些節(jié)點加入數組中后,將countArray設為UITableView的數據源,此時顯示為父節(jié)點。

有了基本的思路,接下來就要開始考慮點擊展開和點擊收起需要怎么實現(xiàn)了。
我們先來考慮點擊展開的問題:
當點擊tableView時,我們需要獲得當前點擊項下所有的未展開節(jié)點、已展開節(jié)點、以及展開節(jié)點的子節(jié)點。因為不曉得該節(jié)點下存在多少層子節(jié)點所以這里需要用到遞歸來取出子節(jié)點(有點像遍歷鏈表的感覺),取出子節(jié)點之后相應的按照順序(這里創(chuàng)建了屬性rowCount來記錄點擊行的下標,文章最后會說明為什么不使用參數傳入。)加入到countArray中,然后刷新tableView將節(jié)點顯示出來。代碼如下:

//展開所有子節(jié)點
- (void)insertData:(NSMutableArray *)array {
    for (int i = 0; i<array.count; i++) {
        TableViewModel *model = [array objectAtIndex:i];
        self.rowCount++;
        [self.countArray insertObject:model atIndex:self.rowCount];
        if (model.array && model.open) {
            [self insertData:model.array];
        }
    }
}

有了展開,那么接下來就需要寫關閉了。點擊關閉時,同樣的需要將所有的子節(jié)點全部從countArray中刪除,具體方法和思路跟點擊展開基本相同。代碼如下:

//收起所有子節(jié)點
- (void)deleteData:(NSMutableArray *)array {
    for (int i = 0; i<array.count; i++) {
        TableViewModel *model = [array objectAtIndex:i];
        if (model.array) {
            [self deleteData:model.array];
        }
        [self.countArray removeObject:model];
    }  
}

至此,多級列表就完成了。
下面是同學要的另外一個小的附加功能,也順便來介紹下實現(xiàn)思路。
大家應該看到了,剛開始放的圖上面還有一個紅色的浮窗,下面來介紹下這個浮窗的實現(xiàn)思路。
同學給的紅色懸浮框的需求那,是當列表滑動時將將要顯示的并且是展開狀態(tài)分組的根節(jié)點,顯示為懸浮框展示出來。那么首先通過看UITableViewDelegate中的API,發(fā)現(xiàn)里面有兩個可以拿來使用的方法。

//獲取到將要顯示的cell
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath ;
//獲取到消失的cell
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath NS_AVAILABLE_IOS(6_0);

根據需求我們只需要屏幕上部cell的顯示和消失情況,那么可以知道當用戶向上滑動時,我們應該獲取的為將要消失的cell;當用戶將要向下滑動時,我們需要獲取將要顯示的cell。那么我們就需要監(jiān)聽tableView的滑動方向。
方法如下


-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
     //如果當前位移大于緩存位移,說明scrollView向上滑動
    //state 0初始 1下 2上
    if (scrollView.contentOffset.y > oldY) {
        state = 2;
    }else{
        state = 1;
    }
    //將當前位移變成緩存位移
    oldY = scrollView.contentOffset.y;
    //當用戶滑至頂部時,需要隱藏懸浮框
    if (scrollView.contentOffset.y <= 0) {
        self.label.hidden = YES;
    }
}

當用戶向上滑動時,頂部cell在不斷消失,我們只需拿到消失的cell所對應的model,當model展開狀態(tài)時,將它顯示出來即可。代碼如下:

   TableViewModel *model = [self.countArray objectAtIndex:indexPath.row];
    if(model.open) {
          self.label.hidden = NO;
          self.label.text = model.name
    }

當用戶向下滑動時,頂部cell在不斷的顯示出來,我們這個時候需要倒序遍歷模型數組,拿到最近的一個展開狀態(tài)的model,將它顯示出來即可。如果遍歷結束沒有找到展開狀態(tài)的model,那么將懸浮框隱藏。代碼如下:

 for (NSInteger i = indexPath.row ; i>=0; i--) {
            TableViewModel *model = [self.countArray objectAtIndex:i];
            if (model.open) {
                self.label.hidden = NO;
                self.label.text = model.name;
                return;
            }
  }
  self.label.hidden = YES;

此時,基本已經完工了。但是如果去使用的時候會發(fā)現(xiàn),懸浮框顯示狀態(tài)時,再點擊展開列表,那么懸浮框上的數字會發(fā)生改變。原因是因為每次點擊展開后,我會將數據相應的加入相應的數組然后刷新tableView,這個時候是會進入上面兩個tableView的代理方法的,所以就會出現(xiàn)顯示錯亂的問題。此時,變量state就又起到了作用,只需在每次刷新時重置狀態(tài),并且在相應位置加上判斷即可避免這種情況。同樣的收起tableView的時候,只需在scrollViewDidScroll中進行判斷即可。

如有錯誤歡迎大家指正。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容

  • 發(fā)現(xiàn) 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,179評論 4 61
  • 百花詩 100 百日草 一莖圓葉雙雙綠,五彩嬌花步步高。 秋艷春紅爭日月,天長地久共逍遙。
    PikeTalk閱讀 678評論 0 3
  • 我是一名大四的歷史學專業(yè)的學生,近期在準備考研,所以整天沉浸在密密麻麻的專業(yè)書里,非常枯燥。今天突然有個想法就...
    jc一蓑煙雨閱讀 1,011評論 0 8
  • 余近數月之反省錄,實無反省。此有乖余立此博客之初旨,或為每日都記此日記之故也。昨日與人閒談,同席者曰余有救世主心態(tài)...
    寒窗寄傲生閱讀 216評論 0 0