抽屜效果的tableview功能組件:PYScalableTableView

ScalableTablView.gif

對于導(dǎo)入項目:

  1. cocoaPods 集成:請在Podfile文件中寫入下面代碼
pod “ScalableTableView”
  1. 可以點擊這里,獲取源碼直接把代碼的直接拖入項目,不過,因為框架一直在更新,所以推薦pod導(dǎo)入

前言:

經(jīng)常遇到多層cell折疊展開的需求,于是寫了一個工具組件。
其中有幾個特點:

  1. cell的高度自適應(yīng),或者統(tǒng)一設(shè)置cell高度。
  2. 使用簡單,注冊cell,和cell數(shù)據(jù)傳遞不用手動管理。
  3. 不需要告訴組件內(nèi)部有model中多少層數(shù)據(jù)。
  4. 降低耦合,高聚合,并且性能較優(yōu)。

注意:

1. 適用Model的數(shù)據(jù)結(jié)構(gòu)

如下圖:model中有個屬性,是一個model數(shù)組,而model數(shù)組中的model又有包含了一個model數(shù)組屬性,以此類推。。。

適用的數(shù)據(jù)結(jié)構(gòu)

2. Model中需要實現(xiàn)的方法
model在定義時,需要實現(xiàn)兩個方法:

///1. 返回 model對應(yīng) 的 cell的class 的方法,通過這個方法返回了model綁定的cell 的類型,在內(nèi)部進行了cell的注冊
- (NSString *) cellClass{
    return @"PYTestCell1";
}
///2. 存儲的model array的屬性名, 組件內(nèi)部通過這個方法,進行數(shù)據(jù)的查找。
- (NSString *) modelArrayPropertyName {
    return @"modelArray";
}

核心思路

1、根數(shù)據(jù)源

ScalableTableView工具中,要求傳入一個數(shù)據(jù)源(以下統(tǒng)稱根數(shù)據(jù)源@property (nonatomic,strong) NSArray *modelArray;

2、顯示數(shù)據(jù)源

根據(jù)根數(shù)據(jù)源,生成另外一個數(shù)據(jù)源(以下統(tǒng)稱顯示數(shù)據(jù)源@property (nonatomic,strong) NSMutableArray *dataSourceArray;所有的UI展示,獲取數(shù)據(jù)都是從這個數(shù)據(jù)源中做調(diào)整。

3、cell的注冊

  1. 在model中,獲取cell 的Class,并注冊。在第次傳入根數(shù)據(jù)源的時候,先進行注冊對應(yīng)的cell。而并沒有把所有的子model綁定的cell都注冊完成。
  2. 在展開子cell的時候,注冊展開cell中未注冊的cell類型。
  3. 不用擔心會重復(fù)注冊,因為有一個全局變量對已經(jīng)注冊的cell的類型進行了記錄。

4、model的擴展

  1. 記錄cell的展開狀態(tài),
  2. 還需要記錄model在整個顯示數(shù)據(jù)源的位置,以便數(shù)據(jù)的插入。

是否已經(jīng)添加過屬性了

- (void) setModelISAddProperty: (id)model andISAddProperty: (BOOL)isAnddProperty{
    if (!model) {
        return NSLog(@"是否已經(jīng)添加過屬性了。model沒有值");
    }
      objc_setAssociatedObject(model, &isAddPropertyKey, @(isAnddProperty), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL) getModelISAddProperty: (id)model {
    NSNumber *isAddPropretyNum = objc_getAssociatedObject(model, &isAddPropertyKey);
    return isAddPropretyNum.boolValue;
}

當前是否已經(jīng)展開了

//當前是否已經(jīng)展開了
- (void) setModelIsScalable: (id)model andIsScalable: (BOOL) isScalable {
    if (!model) {
        return NSLog(@"設(shè)置model 當前是否已經(jīng)展開了 屬性的時候。model沒有值,--- 注意,查看是否在 model對應(yīng)的cell中 實現(xiàn)了“ - (void)cellSetDataFunc:(void (^)(NSObject *model))setDataCallBack“方法,想要有折疊效果必須要用這個方法對cell傳值");
    }
    objc_setAssociatedObject(model, &isScalableKey, @(isScalable), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL) getModelIsScalable: (id)model {
    NSNumber *isScalable = objc_getAssociatedObject(model, &isScalableKey);
    return isScalable.boolValue;
}

range

- (void) setModelRange: (id)model andRange: (NSRange)range{
    if (!model) {
        return NSLog(@"設(shè)置model range 屬性的時候。model沒有值");
    }
    NSValue *rangeValue = [NSValue valueWithRange:range];
    objc_setAssociatedObject(model, &rangeKey, rangeValue, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSRange) getModelRange: (id)model{
    NSValue *range = (objc_getAssociatedObject(model, &rangeKey));
    return range.rangeValue;
}

核心代碼

注釋很清楚了,不再贅述。

1. 展開的核心代碼

 UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
    NSObject *model = cell.model;
    NSInteger modelX = indexPath.row;
    NSInteger modelLength = [self getModelRange:model].length;

    NSArray *subModelArray = [self getModelSubModelArray:model];
    BOOL isCurrentScalable = [self getModelIsScalable:model];
    [self setModelIsScalable:model andIsScalable:!isCurrentScalable];
    
    if (subModelArray.count == 0 || !subModelArray) {
        NSLog(@"%@ -> %@,內(nèi)部沒有子數(shù)組集合",self,model);
        return;
    }
    
    //如果有值 那么就對數(shù)據(jù)進行操作
    if (!isCurrentScalable) {
        //當前需要展開
        NSIndexSet *indexSet = [[NSIndexSet alloc]initWithIndexesInRange: NSMakeRange(modelX + 1, modelLength)];
        [self.dataSourceArray insertObjects:subModelArray atIndexes:indexSet];
    }

2. 收起的核心代碼

///刪除 顯示數(shù)據(jù)源dataSourceArray 中取消展示的model
///(點擊model1,則刪除dataSourceArray中包含的model1的子model)
- (void) deleteDataSourceArrayContainsWithModel: (id) model {
    /// 獲取model 的子model
    NSArray *subModelArray = [self getModelSubModelArray:model];
    
    ///表示model中已經(jīng)沒有其他子數(shù)組了,返回傳入的modelArray
    if (subModelArray.count <= 0) return;
   
    /// 表示model中還有子數(shù)組,那么遍歷
    [subModelArray enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        
        ///判斷DataSourceArray中是否包含obj
        if ([self.dataSourceArray containsObject: obj]) {
            
            ///設(shè)置成收起狀態(tài)
            [self setModelIsScalable:obj andIsScalable:false];
            
            ///刪除
            [self.dataSourceArray removeObject:obj];
        
            /// 遞歸
            [self deleteDataSourceArrayContainsWithModel:obj];
        }
    }];
}

3.獲取model的子model數(shù)據(jù)數(shù)組

- (NSArray *) getModelSubModelArray: (id)model{
    NSObject *modelObj = model;
    SEL modelArraySEL = NSSelectorFromString(@"modelArrayPropertyName");
    IMP imp = [modelObj methodForSelector:modelArraySEL];
    NSObject *(*func)(id,SEL) = (void*)imp;
    
    if ([model respondsToSelector:modelArraySEL]) {
        NSObject *modelSubArrayName = func(model,modelArraySEL);
        if ([modelSubArrayName.class isSubclassOfClass: NSClassFromString(@"NSString")]) {
            NSString *modelSubArrayNameStr = (NSString *)modelSubArrayName;
            NSObject *modelSubArrayObj = [modelObj valueForKey:modelSubArrayNameStr];
            if ([modelSubArrayObj.class isSubclassOfClass:NSClassFromString(@"NSArray")]) {
                return (NSArray *)modelSubArrayObj;
            }
        }
    }
    NSLog(@"??||->,%@,沒有子數(shù)據(jù)集合",model);
    return [NSArray new];
}

4. 獲取model 中 cell 的Classa

// 獲取model 中 cell 的Classa
- (Class) getModelCellClass: (NSObject *) model {
    return NSClassFromString([self getModelCellClassName: (model)]);
}
- (NSString *) getModelCellClassName: (NSObject *)model {
    SEL bindCellClassNameSEL = NSSelectorFromString(@"cellClass");
    IMP imp = [model methodForSelector:bindCellClassNameSEL];
    
    NSObject *(*func)(id,SEL) = (void*)imp;
    NSObject *bindCellClassNameObj = func(model,bindCellClassNameSEL);
    if ([bindCellClassNameObj.class isSubclassOfClass:NSClassFromString(@"NSString")]) {
        return (NSString *)bindCellClassNameObj;
    }
    NSLog(@"??%@||-> 沒有獲取到model綁定的cell",self);
    return @"UITableViewCell";
}

5. cell傳遞數(shù)據(jù)的擴展


@implementation UITableViewCell (ScalableTableViewCell_Extension)
static NSString *const setModel = @"setModel_ScalableTableViewCell_Extension";
static NSString *const setDataCallBackKey = @"setDataCallBackKey_ScalableTableViewCell_Extension";
static NSString *const setDictionryKey = @"setDictionryKey_ScalableTableViewCell_Extension";
static NSString *const setClickCellCallBackKey = @"setClickCellCallBackKey_ScalableTableViewCell_Extension";


- (void) tableviewAssignedTheValueToCell:(id)model {
    if (![self getSetDataBlock]) {
           NSLog(@"??%@,objc_getAssociatedObject(self, &setDataCallBackKey); 獲取不到值",self);
        return;
    }
    void (^setDataCallBack)(id) = [self getSetDataBlock];
    [self setModel:model];
    setDataCallBack(model);
}

- (void)cellSetDataFunc:(void (^)(NSObject *model))setDataCallBack {
    [self setDataBlock:setDataCallBack];
}


- (void(^)(id)) getSetDataBlock {
    return objc_getAssociatedObject(self, &setDataCallBackKey);
}

- (void) setDataBlock: (void(^)(id)) block {
    objc_setAssociatedObject(self, &setDataCallBackKey, block, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

///儲存數(shù)據(jù)的model
- (void) setModel:(id)model {
    objc_setAssociatedObject(self, &setModel, model, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (id) model {
    return objc_getAssociatedObject(self, &setModel);
}



///向外界發(fā)出點擊事件
- (void) cellClickEventBlockWithSelectorKey: (NSString *)selectorKey {
    Type_cellClickEventBlock block = objc_getAssociatedObject(self, &setClickCellCallBackKey);
    if (block) {    
        block(self.model,selectorKey);
    }
}

- (void)setCellClickEventBlock:(Type_cellClickEventBlock)cellClickEventBlock {
    objc_setAssociatedObject(self, &setClickCellCallBackKey, cellClickEventBlock, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

///外部tableview 調(diào)用
- (void) registerClickEventFunc:(Type_cellClickEventBlock)cellClickEventBlock {
    ///儲存block
    [self setCellClickEventBlock:cellClickEventBlock];
}

@end


內(nèi)容擴展更新 ---

cell 與tableview之間的數(shù)據(jù)傳遞通道

1、cell內(nèi)部點擊事件的傳遞調(diào)用

[self cellClickEventBlockWithSelectorKey:@"clickButton1"];

2、tableView中注冊cell的點擊事件調(diào)用

[self.tableview registerClickCellFunc:^(id  _Nullable model, NSString * _Nonnull clickSelectorKey) {
//代碼處理
}];

實現(xiàn)一行代碼實現(xiàn)收縮效果

給tableView設(shè)置一個代理,并實現(xiàn)下列代碼;

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [self.tableview didSelectRowAtIndexPath:indexPath];
}

如果不太明白,運行一波代碼就都懂嘍!
如果感覺提供了一個不一樣的思路,請來一波紅心,是對我最大的鼓勵。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,426評論 4 61
  • 我們在上一篇《通過代碼自定義不等高cell》中學習了tableView的相關(guān)知識,本文將在上文的基礎(chǔ)上,利用sto...
    啊世ka閱讀 1,658評論 2 7
  • 2017.02.22 可以練習,每當這個時候,腦袋就犯困,我這腦袋真是神奇呀,一說讓你做事情,你就犯困,你可不要太...
    Carden閱讀 1,492評論 0 1
  • 西比爾姑娘,2013年4月20作。 還是前幾年的作品,近些時日很少創(chuàng)作。 懷舊的分割線。 “人們說,我們活著是為了...
    逆歸閱讀 307評論 0 1
  • 在facebook上看到一篇貼文,關(guān)於五月天的新專輯《自傳》,寫得很好,特別想分享出來。ps.為了和貼文保持和諧,...
    Echo麗閱讀 749評論 6 4

友情鏈接更多精彩內(nèi)容