抽屉效果的tableview功能组件:PYScalableTableView

ScalableTablView.gif

对于导入项目:

  1. cocoaPods 集成:请在Podfile文件中写入下面代码
pod “ScalableTableView”
  1. 可以点击这里,获取源码直接把代码的直接拖入项目,不过,因为框架一直在更新,所以推荐pod导入

前言:

经常遇到多层cell折叠展开的需求,于是写了一个工具组件。
其中有几个特点:

  1. cell的高度自适应,或者统一设置cell高度。
  2. 使用简单,注册cell,和cell数据传递不用手动管理。
  3. 不需要告诉组件内部有model中多少层数据。
  4. 降低耦合,高聚合,并且性能较优。

注意:

1. 适用Model的数据结构

如下图:model中有个属性,是一个model数组,而model数组中的model又有包含了一个model数组属性,以此类推。。。

适用的数据结构

2. Model中需要实现的方法
model在定义时,需要实现两个方法:

///1. 返回 model对应 的 cell的class 的方法,通过这个方法返回了model绑定的cell 的类型,在内部进行了cell的注册
- (NSString *) cellClass{
    return @"PYTestCell1";
}
///2. 存储的model array的属性名, 组件内部通过这个方法,进行数据的查找。
- (NSString *) modelArrayPropertyName {
    return @"modelArray";
}

核心思路

1、根数据源

ScalableTableView工具中,要求传入一个数据源(以下统称根数据源@property (nonatomic,strong) NSArray *modelArray;

2、显示数据源

根据根数据源,生成另外一个数据源(以下统称显示数据源@property (nonatomic,strong) NSMutableArray *dataSourceArray;所有的UI展示,获取数据都是从这个数据源中做调整。

3、cell的注册

  1. 在model中,获取cell 的Class,并注册。在第次传入根数据源的时候,先进行注册对应的cell。而并没有把所有的子model绑定的cell都注册完成。
  2. 在展开子cell的时候,注册展开cell中未注册的cell类型。
  3. 不用担心会重复注册,因为有一个全局变量对已经注册的cell的类型进行了记录。

4、model的扩展

  1. 记录cell的展开状态,
  2. 还需要记录model在整个显示数据源的位置,以便数据的插入。

是否已经添加过属性了

- (void) setModelISAddProperty: (id)model andISAddProperty: (BOOL)isAnddProperty{
    if (!model) {
        return NSLog(@"是否已经添加过属性了。model没有值");
    }
      objc_setAssociatedObject(model, &isAddPropertyKey, @(isAnddProperty), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL) getModelISAddProperty: (id)model {
    NSNumber *isAddPropretyNum = objc_getAssociatedObject(model, &isAddPropertyKey);
    return isAddPropretyNum.boolValue;
}

当前是否已经展开了

//当前是否已经展开了
- (void) setModelIsScalable: (id)model andIsScalable: (BOOL) isScalable {
    if (!model) {
        return NSLog(@"设置model 当前是否已经展开了 属性的时候。model没有值,--- 注意,查看是否在 model对应的cell中 实现了“ - (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(@"设置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(@"%@ -> %@,内部没有子数组集合",self,model);
        return;
    }
    
    //如果有值 那么就对数据进行操作
    if (!isCurrentScalable) {
        //当前需要展开
        NSIndexSet *indexSet = [[NSIndexSet alloc]initWithIndexesInRange: NSMakeRange(modelX + 1, modelLength)];
        [self.dataSourceArray insertObjects:subModelArray atIndexes:indexSet];
    }

2. 收起的核心代码

///删除 显示数据源dataSourceArray 中取消展示的model
///(点击model1,则删除dataSourceArray中包含的model1的子model)
- (void) deleteDataSourceArrayContainsWithModel: (id) model {
    /// 获取model 的子model
    NSArray *subModelArray = [self getModelSubModelArray:model];
    
    ///表示model中已经没有其他子数组了,返回传入的modelArray
    if (subModelArray.count <= 0) return;
   
    /// 表示model中还有子数组,那么遍历
    [subModelArray enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        
        ///判断DataSourceArray中是否包含obj
        if ([self.dataSourceArray containsObject: obj]) {
            
            ///设置成收起状态
            [self setModelIsScalable:obj andIsScalable:false];
            
            ///删除
            [self.dataSourceArray removeObject:obj];
        
            /// 递归
            [self deleteDataSourceArrayContainsWithModel:obj];
        }
    }];
}

3.获取model的子model数据数组

- (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(@"🐯||->,%@,没有子数据集合",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传递数据的扩展


@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);
}

///储存数据的model
- (void) setModel:(id)model {
    objc_setAssociatedObject(self, &setModel, model, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

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



///向外界发出点击事件
- (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 调用
- (void) registerClickEventFunc:(Type_cellClickEventBlock)cellClickEventBlock {
    ///储存block
    [self setCellClickEventBlock:cellClickEventBlock];
}

@end


内容扩展更新 ---

cell 与tableview之间的数据传递通道

1、cell内部点击事件的传递调用

[self cellClickEventBlockWithSelectorKey:@"clickButton1"];

2、tableView中注册cell的点击事件调用

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

实现一行代码实现收缩效果

给tableView设置一个代理,并实现下列代码;

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

如果不太明白,运行一波代码就都懂喽!
如果感觉提供了一个不一样的思路,请来一波红心,是对我最大的鼓励。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,049评论 5 473
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,478评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,109评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,097评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,115评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,280评论 1 279
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,748评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,398评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,553评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,440评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,487评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,176评论 3 317
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,750评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,821评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,049评论 1 257
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,559评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,150评论 2 341

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,985评论 4 60
  • 我们在上一篇《通过代码自定义不等高cell》中学习了tableView的相关知识,本文将在上文的基础上,利用sto...
    啊世ka阅读 1,489评论 2 7
  • 2017.02.22 可以练习,每当这个时候,脑袋就犯困,我这脑袋真是神奇呀,一说让你做事情,你就犯困,你可不要太...
    Carden阅读 1,316评论 0 1
  • 西比尔姑娘,2013年4月20作。 还是前几年的作品,近些时日很少创作。 怀旧的分割线。 “人们说,我们活着是为了...
    逆归阅读 254评论 0 1
  • 在facebook上看到一篇貼文,關於五月天的新專輯《自傳》,寫得很好,特別想分享出來。ps.為了和貼文保持和諧,...
    Echo丽阅读 656评论 6 4