
對于導(dǎo)入項目:
- cocoaPods 集成:請在Podfile文件中寫入下面代碼
pod “ScalableTableView”
- 可以點擊這里,獲取源碼直接把代碼的直接拖入項目,不過,因為框架一直在更新,所以推薦pod導(dǎo)入
前言:
經(jīng)常遇到多層cell折疊展開的需求,于是寫了一個工具組件。
其中有幾個特點:
- cell的高度自適應(yīng),或者統(tǒng)一設(shè)置cell高度。
- 使用簡單,注冊cell,和cell數(shù)據(jù)傳遞不用手動管理。
- 不需要告訴組件內(nèi)部有model中多少層數(shù)據(jù)。
- 降低耦合,高聚合,并且性能較優(yōu)。
注意:
1. 適用Model的數(shù)據(jù)結(jié)構(gòu)
如下圖:model中有個屬性,是一個model數(shù)組,而model數(shù)組中的model又有包含了一個model數(shù)組屬性,以此類推。。。

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的注冊
- 在model中,獲取cell 的Class,并注冊。在第次傳入根數(shù)據(jù)源的時候,先進行注冊對應(yīng)的cell。而并沒有把所有的子model綁定的cell都注冊完成。
- 在展開子cell的時候,注冊展開cell中未注冊的cell類型。
- 不用擔心會重復(fù)注冊,因為有一個全局變量對已經(jīng)注冊的cell的類型進行了記錄。
4、model的擴展
- 記錄cell的展開狀態(tài),
- 還需要記錄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];
}
如果不太明白,運行一波代碼就都懂嘍!
如果感覺提供了一個不一樣的思路,請來一波紅心,是對我最大的鼓勵。