iOS打造一個(gè)靜態(tài)Cell通用的TableViewManager

在App中劝堪,比如側(cè)邊欄秒啦,設(shè)置,個(gè)人界面荐糜,編輯表單葛超,總會(huì)有一些地方用到一些靜態(tài)Cell構(gòu)成的TableView绣张,這些界面的特點(diǎn)是关带,Cell種類繁多宋雏,Model種類繁多,點(diǎn)擊事件處理分散但單調(diào)(頁(yè)面跳轉(zhuǎn)嗦明,編輯textFiled)娶牌,重復(fù)寫tableViewDelegate和datasource的相關(guān)方法馆纳,但不會(huì)用到一些奇怪的datasource和delegate方法鲁驶,而且顯示內(nèi)容一般是固定的,每個(gè)頁(yè)面Cell的數(shù)量不會(huì)很多径荔。像筆者所在的項(xiàng)目里的側(cè)邊欄模塊猖凛,里面有很多tableView绪穆,由于沒(méi)有使用StoryBoard且很久之前寫的,充斥著各種if...else if用model的某個(gè)key值進(jìn)行判斷的邏輯去讓cell去顯示和隱藏某些視圖菠红,已經(jīng)進(jìn)行點(diǎn)擊跳轉(zhuǎn)邏輯试溯,更有甚者遇绞,有if (indexPath == 0)之類的通過(guò)行數(shù)判斷去處理的邏輯。增加新的cell時(shí)蹄咖,便要在各種地方再加上else if的新判斷澜汤,而做新界面時(shí)舵匾,也沒(méi)有很好的方法進(jìn)行復(fù)用坐梯。筆者結(jié)合之前文章進(jìn)行進(jìn)一步思考,抽象出一個(gè)適合這種特點(diǎn)tableView的管理類烛缔,記錄一下思路供參考馏段。

項(xiàng)目里一個(gè)屏幕那么高的CellForRow方法之類的比比皆是.JPG

關(guān)于TableViewDatasource的思考

在之前的文章(iOS通用的DataSource)中,已經(jīng)對(duì)Datasource和SectionObject進(jìn)行了抽象践瓷,datasource本質(zhì)是告訴Cell如何去處理Model的橋梁院喜,之前筆者是通過(guò)維護(hù)一個(gè)字典去一一對(duì)應(yīng)Model的種類和Cell種類的,但后來(lái)發(fā)現(xiàn)晕翠,有時(shí)候Model和Cell的種類并不一定是1對(duì)1的喷舀,比如有些時(shí)候cell只要只要展示一個(gè)title,可能使用者只想寫一個(gè)String去對(duì)應(yīng)淋肾,而不用去建一個(gè)Model,在靜態(tài)不變的Model中樊卓,這種情況更突出拿愧,因?yàn)椴⒉恍枰獜木W(wǎng)絡(luò)解析數(shù)據(jù),不用用反射機(jī)制去進(jìn)行json轉(zhuǎn)model的處理碌尔,有時(shí)候浇辜,一個(gè)NSDictionary甚至一個(gè)NSString或者Bool就可以滿足一個(gè)cell的展示需求券敌,那么對(duì)于這種可能是多Cell對(duì)多Model(String也可以是一種Model)的情況,且每個(gè)TableView的Cell數(shù)量不多(一般設(shè)置頁(yè)不會(huì)超過(guò)20個(gè))柳洋,可以考慮為在字典里每個(gè)model配置對(duì)應(yīng)的Cell類型的鍵值對(duì)待诅。而且因?yàn)镸odel種類不一,我們也需要手動(dòng)Add到SectionObject里熊镣,那么我們可以把這個(gè)字典分在每個(gè)sectionObject里去管理卑雁。在section類里添加以下方法,而model的唯一性可以用hash屬性標(biāo)識(shí)绪囱,默認(rèn)hash屬性就是對(duì)象的內(nèi)存地址测蹲。

- (void)addItem:(id<NSObject>)item cellClass:(Class)cellClass{
    if (!item) {
        return;
    }
    [_items addObject:item];
    if (!cellClass) {
        return;
    }
    NSString *itemHash = [NSString stringWithFormat:@"%tu",item.hash];
    [self.sectionCellDic setObject:cellClass forKey:itemHash];
}

然后再增加外部通過(guò)indexPath讀取對(duì)應(yīng)Cell Class的方法

- (Class)cellClassIndexPath:(NSIndexPath *)indexPath{
    return [self cellClassForItem:[self itemForIndexPath:indexPath]];
}

- (Class)cellClassForItem:(id<NSObject>)item{
    NSString *itemHash = [NSString stringWithFormat:@"%tu",item.hash];
    Class cellClass = [self.sectionCellDic objectForKey:itemHash];
    return cellClass;
}

- (id)itemForIndexPath:(NSIndexPath *)indexPath{
    if (self.items.count > indexPath.row) {
        return self.items[indexPath.row];
    }
    return nil;
}

接管TableViewDelegate

和很多動(dòng)態(tài)Cell會(huì)有一些奇奇怪怪的Delegate不同,靜態(tài)cell主要就是展示和點(diǎn)擊事件毕箍,一般會(huì)用到以下原生delegate

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)sectionIndex;
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)sectionIndex;
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)sectionIndex;
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)sectionIndex;
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;

所以我們可以讓TableViewManager接管這些協(xié)議的實(shí)現(xiàn)弛房,同時(shí)新建一個(gè)TableViewManagerDelegate為UITableViewDelegate的子協(xié)議,并開放這些協(xié)議給使用Manager的人而柑,也就是可以讓使用者覆寫這些delegate。

比如heightForRowAtIndexPath方法荷逞,就可以在判斷外部是否實(shí)現(xiàn)媒咳,否則則內(nèi)部實(shí)現(xiàn)。

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    if ([self.delegate respondsToSelector:@selector(tableView:heightForRowAtIndexPath:)]) {
        return [self.delegate tableView:tableView heightForRowAtIndexPath:indexPath];
    }
    Class cellClass = [self tableView:tableView cellClassForRowAtIndexPath:indexPath];
    id item = [self tableView:tableView itemForRowAtIndexPath:indexPath];
    return (cellClass == [UITableViewCell class])?44.f:[cellClass tableView:tableView rowHeightForItem:item];
}

同理种远,這些協(xié)議的默認(rèn)實(shí)現(xiàn)可以根據(jù)實(shí)際情況作調(diào)整涩澡,比如對(duì)于didSelectRowAtIndexPath,我們也可以帶上其item再返回給外部坠敷,帶上item的好處妙同,等會(huì)會(huì)講到。

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    if ([self.delegate respondsToSelector:@selector(tableView:didSelectItem:atIndexPath:)]) {
        id item = [self tableView:tableView itemForRowAtIndexPath:indexPath];
        [self.delegate tableView:tableView didSelectItem:item atIndexPath:indexPath];
        [tableView deselectRowAtIndexPath:indexPath animated:YES];
    }
    else if ([self.delegate respondsToSelector:@selector(tableView:didSelectRowAtIndexPath:)]) {
        [self.delegate tableView:tableView didSelectRowAtIndexPath:indexPath];
    }
}

Cell的協(xié)議里有啥

之前文章講過(guò)膝迎,我們可以通過(guò)一個(gè)CellProtocol去指定configItem的方法讓cell自己去解析Model粥帚,同時(shí)我們也可以讓這個(gè)協(xié)議豐富一點(diǎn),比如加上CellDelegate的回調(diào)限次,可以讓Cell把信息傳到外部芒涡,這在一些Cell里面的點(diǎn)擊事件,或者開關(guān)(UISwitch)卖漫,表單編輯時(shí)都很有用费尽,所以我們可以加上delegate

@protocol CDZTableViewCellDelegate <NSObject>
@optional
- (void)didTriggleCell:(UITableViewCell*)cell message:(id)message;
@end

@protocol CDZTableViewCell<NSObject>
@required
- (void)configWithItem:(id<NSObject>)item;
+ (CGFloat)tableView:(UITableView *)tableView rowHeightForItem:(id)item;
@optional
- (void)setCDZCellDelegate:(id<CDZTableViewCellDelegate>)objectDelegate;
@end

而這個(gè)Message的執(zhí)行者應(yīng)該是外部調(diào)用者而不是Manager本身,所以對(duì)于Cell和外部來(lái)說(shuō)羊始,它雖然是個(gè)id但實(shí)際類型是外部和Cell內(nèi)部統(tǒng)一知道的旱幼,比如是個(gè)Bool值表示開關(guān)狀態(tài),一個(gè)NSDictionary里面包含信息突委,或者是一個(gè)NSString柏卤。而Manager只要做一個(gè)中轉(zhuǎn)就好了叹誉。這里我在Manager里把Cell轉(zhuǎn)換成了對(duì)應(yīng)的Item及IndexPath傳給外部,因?yàn)镃ell是可復(fù)用的闷旧,實(shí)際關(guān)心的應(yīng)該是Model长豁。

- (void)didTriggleCell:(UITableViewCell *)cell message:(id)message{
    if ([self.delegate respondsToSelector:@selector(receiveCellMessage:atIndexPath:item:)]) {
        NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
        id item = [self tableView:self.tableView itemForRowAtIndexPath:indexPath];
        [self.delegate receiveCellMessage:message atIndexPath:indexPath item:item];
    }
}

初始化方法

- (instancetype)initWithTableView:(UITableView *)tableView delegate:(id<CDZTableViewManagerDelegate>)objectDelegate{
    self = [super init];
    if (self){
        tableView.delegate = self;
        tableView.dataSource = self;
        tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
        _sections = [NSMutableArray array];
        _delegate = objectDelegate;
        self.tableView = tableView;
    }
    return self;
}

最后外界只需要初始化一個(gè)manager,即可不用寫Datasource和Delegate了忙灼,因?yàn)槎急籑anager接管了匠襟。

item的一些小技巧

對(duì)應(yīng)一些初展示類或者開關(guān)操作的item,我們可以用簡(jiǎn)單的Bool该园,NSString酸舍,NSDictionry作為Model,而有點(diǎn)擊事件的Model里初,我們可以在model類里增加一個(gè)tapBlock啃勉,這樣我們可以在配置item時(shí)順便配置其點(diǎn)擊事件。因?yàn)殪o態(tài)cell中的點(diǎn)擊事件往往各不相同双妨,所以配置在tapBlock可以讓代碼更加統(tǒng)一淮阐。

TestItem *itemD = [[TestItem alloc]init];
itemD.title = @"itemD";
itemD.tapBlock = ^(TestItem *item) {
   NSLog(@"%@ tap",item.title);
};
[firstSection addItem:itemD cellClass:[TestBCell class]];

這樣不僅好找,一次性把相關(guān)的操作配置完成刁品,而且在執(zhí)行點(diǎn)擊事件時(shí)泣特,不用再關(guān)心具體的點(diǎn)擊事件,進(jìn)行item的識(shí)別分類判斷挑随。

- (void)tableView:(UITableView *)tableView didSelectItem:(id)item atIndexPath:(NSIndexPath *)indexPath{
    if ([item isMemberOfClass:[TestItem class]]) {
        TestItem *cellItem = (TestItem *)item;
        if (cellItem.tapBlock) {
            cellItem.tapBlock(cellItem);
        }
    }
}

最后

通過(guò)這些的Manager状您,就可以隨心所欲搭配model和cell,并把配置寫在一起兜挨,方便修改膏孟,查找,同時(shí)也不用重復(fù)寫Delegate和Datasource拌汇,而對(duì)老的Cell和Model也有很好的兼容柒桑。底下是一個(gè)混搭的例子。

- (NSMutableArray <CDZTableViewSection *>*)sections{
    NSMutableArray <CDZTableViewSection *> *sections = [NSMutableArray array];
    CDZTableViewSection *firstSection = [[CDZTableViewSection alloc]init];
  
    NSString *itemA = @"itemA";
    [firstSection addItem:itemA cellClass:[TestACell class]];
    
    NSDictionary *itemB = @{@"title" : @"itemB"};
    [firstSection addItem:itemB cellClass:[TestACell class]];
   
    TestItem *itemC = [[TestItem alloc]init];
    itemC.title = @"itemC";
    itemC.tapBlock = ^(TestItem *item) {
        NSLog(@"%@ tap",item.title);
    };
    [firstSection addItem:itemC cellClass:[TestBCell class]];
  
    TestItem *itemD = [[TestItem alloc]init];
    itemD.title = @"itemD";
    itemD.tapBlock = ^(TestItem *item) {
        NSLog(@"%@ tap",item.title);
    };
    [firstSection addItem:itemD cellClass:[TestBCell class]];
  
    [sections addObject:firstSection];
    return sections;
}

所有源碼和Demo
可以直接拿文件去使用或修改担猛,也支持Cocoapods.

如果您覺(jué)得有幫助,不妨給個(gè)star鼓勵(lì)一下,歡迎關(guān)注&交流
有任何問(wèn)題歡迎評(píng)論私信或者提issue
QQ:757765420
Email:nemocdz@gmail.com
Github:Nemocdz
微博:@Nemocdz

謝謝觀看

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末幕垦,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子傅联,更是在濱河造成了極大的恐慌先改,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,590評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蒸走,死亡現(xiàn)場(chǎng)離奇詭異仇奶,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)比驻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門该溯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)岛抄,“玉大人,你說(shuō)我怎么就攤上這事狈茉》蛲郑” “怎么了?”我有些...
    開封第一講書人閱讀 169,301評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵氯庆,是天一觀的道長(zhǎng)蹭秋。 經(jīng)常有香客問(wèn)我,道長(zhǎng)堤撵,這世上最難降的妖魔是什么仁讨? 我笑而不...
    開封第一講書人閱讀 60,078評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮实昨,結(jié)果婚禮上洞豁,老公的妹妹穿的比我還像新娘。我一直安慰自己荒给,他們只是感情好丈挟,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著锐墙,像睡著了一般礁哄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上溪北,一...
    開封第一講書人閱讀 52,682評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音夺脾,去河邊找鬼之拨。 笑死,一個(gè)胖子當(dāng)著我的面吹牛咧叭,可吹牛的內(nèi)容都是我干的蚀乔。 我是一名探鬼主播,決...
    沈念sama閱讀 41,155評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼菲茬,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼吉挣!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起婉弹,我...
    開封第一講書人閱讀 40,098評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤睬魂,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后镀赌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體氯哮,經(jīng)...
    沈念sama閱讀 46,638評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評(píng)論 3 342
  • 正文 我和宋清朗相戀三年商佛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了喉钢。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片姆打。...
    茶點(diǎn)故事閱讀 40,852評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖肠虽,靈堂內(nèi)的尸體忽然破棺而出幔戏,到底是詐尸還是另有隱情,我是刑警寧澤税课,帶...
    沈念sama閱讀 36,520評(píng)論 5 351
  • 正文 年R本政府宣布闲延,位于F島的核電站,受9級(jí)特大地震影響伯复,放射性物質(zhì)發(fā)生泄漏慨代。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評(píng)論 3 335
  • 文/蒙蒙 一啸如、第九天 我趴在偏房一處隱蔽的房頂上張望侍匙。 院中可真熱鬧,春花似錦叮雳、人聲如沸想暗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)说莫。三九已至,卻和暖如春寞焙,著一層夾襖步出監(jiān)牢的瞬間储狭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工捣郊, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留辽狈,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,279評(píng)論 3 379
  • 正文 我出身青樓呛牲,卻偏偏與公主長(zhǎng)得像刮萌,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子娘扩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容