Latest 0.1.0
Homepage https://github.com/LiPengYue/ScalableTableView
License MIT
Platforms ios 8.0
Authors

ScalableTablView.gif
对于导入项目:

  1. cocoaPods 集成:请在Podfile文件中写入下面代码
    pod “ScalableTableView”
  2. 可以直接把代码的直接拖入项目,不过,因为框架一直在更新,所以推荐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];
}

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

Installation

ScalableTableView is available through CocoaPods. To install
it, simply add the following line to your Podfile:

pod 'ScalableTableView'

Author

LiPengYue, [email protected]

License

ScalableTableView is available under the MIT license. See the LICENSE file for more info.

Latest podspec

{
    "name": "ScalableTableView",
    "version": "0.1.0",
    "summary": "ScalableTableView.",
    "description": "TODO: u5173u4e8etableview u7684u6269u5c55uff0cu652fu6301u62bdu5c49u6548u679c.",
    "homepage": "https://github.com/LiPengYue/ScalableTableView",
    "license": {
        "type": "MIT",
        "file": "LICENSE"
    },
    "authors": {
        "LiPengYue": "[email protected]"
    },
    "source": {
        "git": "https://github.com/LiPengYue/ScalableTableView.git",
        "tag": "0.1.0"
    },
    "platforms": {
        "ios": "8.0"
    },
    "source_files": "ScalableTableView/Classes/**/*"
}

Pin It on Pinterest

Share This