// // UITableView+SDAutoTableViewCellHeight.m // SDAutoLayout 测试 Demo // // Created by aier on 15/11/1. // Copyright © 2015年 gsd. All rights reserved. // /* ********************************************************************************* * * * 在您使用此自动布局库的过程中如果出现bug请及时以以下任意一种方式联系我们,我们会及时修复bug并 * * 帮您解决问题。 * * QQ : 2689718696(gsdios) * * Email : gsdios@126.com * * GitHub: https://github.com/gsdios * * 新浪微博:GSD_iOS * * * ********************************************************************************* */ #import "UITableView+SDAutoTableViewCellHeight.h" #import @interface SDCellAutoHeightManager () @property (nonatomic, weak) UITableView *modelTableview; @end @implementation SDCellAutoHeightManager { NSMutableDictionary *_cacheDictionary; NSMutableDictionary *_modelCellsDict; } - (instancetype)init { if (self = [super init]) { [self setup]; } return self; } - (instancetype)initWithCellClass:(Class)cellClass tableView:(UITableView *)tableView { if (self = [super init]) { [self setup]; self.modelTableview = tableView; [self registerCellWithCellClass:cellClass]; } return self; } - (instancetype)initWithCellClasses:(NSArray *)cellClassArray tableView:(UITableView *)tableView { if (self = [super init]) { [self setup]; self.modelTableview = tableView; [cellClassArray enumerateObjectsUsingBlock:^(Class obj, NSUInteger idx, BOOL *stop) { [self registerCellWithCellClass:obj]; }]; } return self; } - (void)setup { _cacheDictionary = [NSMutableDictionary new]; _modelCellsDict = [NSMutableDictionary new]; } - (void)registerCellWithCellClass:(Class)cellClass { [_modelTableview registerClass:cellClass forCellReuseIdentifier:NSStringFromClass(cellClass)]; self.modelCell = [_modelTableview dequeueReusableCellWithIdentifier:NSStringFromClass(cellClass)]; if (!self.modelCell.contentView.subviews.count) { NSString *path = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"%@.nib", NSStringFromClass(cellClass)] ofType:nil]; if (path) { self.modelCell = nil; [_modelTableview registerNib:[UINib nibWithNibName:NSStringFromClass(cellClass) bundle:nil] forCellReuseIdentifier:NSStringFromClass(cellClass)]; self.modelCell = [_modelTableview dequeueReusableCellWithIdentifier:NSStringFromClass(cellClass)]; } } if (self.modelCell) { [_modelCellsDict setObject:self.modelCell forKey:NSStringFromClass(cellClass)]; } } + (instancetype)managerWithCellClass:(Class)cellClass tableView:(UITableView *)tableView { SDCellAutoHeightManager *manager = [[self alloc] initWithCellClass:cellClass tableView:tableView]; return manager; } - (UITableViewCell *)modelCell { if (_modelCell.contentView.tag != kSDModelCellTag) { _modelCell.contentView.tag = kSDModelCellTag; } return _modelCell; } - (NSDictionary *)heightCacheDict { return _cacheDictionary; } - (void)clearHeightCache { [_cacheDictionary removeAllObjects]; [_subviewFrameCacheDict removeAllObjects]; } - (NSString *)cacheKeyForIndexPath:(NSIndexPath *)indexPath { return [NSString stringWithFormat:@"%ld-%ld", (long)indexPath.section, (long)indexPath.row]; } - (void)clearHeightCacheOfIndexPaths:(NSArray *)indexPaths { [indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) { NSString *cacheKey = [self cacheKeyForIndexPath:indexPath]; [_cacheDictionary removeObjectForKey:cacheKey]; [_subviewFrameCacheDict removeObjectForKey:cacheKey]; }]; } - (void)deleteThenResetHeightCache:(NSIndexPath *)indexPathToDelete { NSString *cacheKey = [self cacheKeyForIndexPath:indexPathToDelete]; [_cacheDictionary removeObjectForKey:cacheKey]; [_subviewFrameCacheDict removeObjectForKey:cacheKey]; long sectionOfToDeleteItem = indexPathToDelete.section; long rowOfToDeleteItem = indexPathToDelete.row; NSMutableDictionary *tempHeightCacheDict = [NSMutableDictionary new]; NSMutableDictionary *tempFrameCacheDict = [NSMutableDictionary new]; for (NSString *key in _cacheDictionary.allKeys) { NSArray *res = [key componentsSeparatedByString:@"-"]; long section = [res.firstObject integerValue]; long row = [res.lastObject integerValue]; if (section == sectionOfToDeleteItem && row > rowOfToDeleteItem) { NSNumber *heightCache = _cacheDictionary[key]; NSArray *frameCache = _subviewFrameCacheDict[key]; NSString *newKey = [NSString stringWithFormat:@"%ld-%ld", section, (row - 1)]; [tempHeightCacheDict setValue:heightCache forKey:newKey]; [tempFrameCacheDict setValue:frameCache forKey:newKey]; [_cacheDictionary removeObjectForKey:key]; [_subviewFrameCacheDict removeObjectForKey:key]; } } [_cacheDictionary addEntriesFromDictionary:tempHeightCacheDict]; [_subviewFrameCacheDict addEntriesFromDictionary:tempFrameCacheDict]; } - (void)insertNewDataAtTheBeginingOfSection:(NSInteger)section newDataCount:(NSInteger)count { NSMutableDictionary *tempHeightCacheDict = [NSMutableDictionary new]; NSMutableDictionary *tempFrameCacheDict = [NSMutableDictionary new]; for (NSString *key in _cacheDictionary.allKeys) { NSArray *res = [key componentsSeparatedByString:@"-"]; long originalSection = [res.firstObject integerValue]; long row = [res.lastObject integerValue]; if (originalSection == section) { NSNumber *heightCache = _cacheDictionary[key]; NSArray *frameCache = _subviewFrameCacheDict[key]; NSString *newKey = [NSString stringWithFormat:@"%ld-%ld", originalSection, (row + count)]; [tempHeightCacheDict setValue:heightCache forKey:newKey]; [tempFrameCacheDict setValue:frameCache forKey:newKey]; [_cacheDictionary removeObjectForKey:key]; [_subviewFrameCacheDict removeObjectForKey:key]; } } [_cacheDictionary addEntriesFromDictionary:tempHeightCacheDict]; [_subviewFrameCacheDict addEntriesFromDictionary:tempFrameCacheDict]; } - (void)insertNewDataAtIndexPaths:(NSArray *)indexPaths { NSMutableDictionary *sectionsdict = [NSMutableDictionary new]; for (NSIndexPath *indexPath in indexPaths) { NSString *sectionkey = [@(indexPath.section) stringValue]; if (![sectionsdict objectForKey:sectionkey]) { [sectionsdict setValue:[NSMutableArray new] forKey:sectionkey]; } NSMutableArray *arr = sectionsdict[sectionkey]; [arr addObject:indexPath]; } for (NSString *sectionkey in sectionsdict.allKeys) { NSMutableArray *tempHeightCaches = [NSMutableArray new]; NSMutableArray *tempFrameCaches = [NSMutableArray new]; NSInteger section = [sectionkey integerValue]; NSInteger rowCount = [self.modelTableview numberOfRowsInSection:section]; if (rowCount <= 0) { continue; } else { for (int i = 0; i < rowCount; i++) { [tempHeightCaches addObject:[NSNull null]]; [tempFrameCaches addObject:[NSNull null]]; } } for (NSString *key in _cacheDictionary.allKeys) { NSArray *res = [key componentsSeparatedByString:@"-"]; long originalSection = [res.firstObject integerValue]; long row = [res.lastObject integerValue]; if (originalSection == section) { NSNumber *heightCache = _cacheDictionary[key]; NSArray *frameCache = _subviewFrameCacheDict[key]; [tempHeightCaches setObject:heightCache atIndexedSubscript:row]; [tempFrameCaches setObject:frameCache atIndexedSubscript:row]; [_cacheDictionary removeObjectForKey:key]; [_subviewFrameCacheDict removeObjectForKey:key]; } } NSMutableArray *objsToInsert = [NSMutableArray new]; NSMutableIndexSet *indexSet = [NSMutableIndexSet new]; NSArray *indexPaths = sectionsdict[sectionkey]; [indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *obj, NSUInteger idx, BOOL *stop) { [objsToInsert addObject:[NSNull null]]; [indexSet addIndex:obj.row]; }]; [tempHeightCaches insertObjects:objsToInsert atIndexes:indexSet]; [tempFrameCaches insertObjects:objsToInsert atIndexes:indexSet]; [tempHeightCaches enumerateObjectsUsingBlock:^(NSNumber *heightCache, NSUInteger idx, BOOL *stop) { if (![heightCache isKindOfClass:[NSNull class]]) { NSString *key = [NSString stringWithFormat:@"%zd-%zd", section, idx]; [_cacheDictionary setValue:heightCache forKey:key]; [_subviewFrameCacheDict setValue:[tempFrameCaches objectAtIndex:idx] forKey:key]; } }]; } } - (NSNumber *)heightCacheForIndexPath:(NSIndexPath *)indexPath { /* 如果程序卡在了这里很可能是由于你用了“dequeueReusableCellWithIdentifier:forIndexPath:”方法来重用cell,换成““dequeueReusableCellWithIdentifier:”(不带IndexPath)方法即可解决 */ NSString *cacheKey = [self cacheKeyForIndexPath:indexPath]; return (NSNumber *)[_cacheDictionary objectForKey:cacheKey]; } - (CGFloat)cellHeightForIndexPath:(NSIndexPath *)indexPath model:(id)model keyPath:(NSString *)keyPath { NSNumber *cacheHeight = [self heightCacheForIndexPath:indexPath]; if (cacheHeight) { return [cacheHeight floatValue]; } else { if (!self.modelCell) { return 0; } if (self.modelTableview && self.modelTableview != self.modelCell.sd_tableView) { self.modelCell.sd_tableView = self.modelTableview; } self.modelCell.sd_indexPath = indexPath; if (model && keyPath) { [self.modelCell setValue:model forKey:keyPath]; } else if (self.cellDataSetting) { self.cellDataSetting(self.modelCell); } #ifdef SDDebugWithAssert /* 如果程序卡在了这里说明你的cell还没有调用“setupAutoHeightWithBottomView:(UIView *)bottomView bottomMargin:(CGFloat)bottomMargin”方法或者你传递的bottomView为nil,请检查并修改。例: //注意:bottomView不能为nil [cell setupAutoHeightWithBottomView:bottomView bottomMargin:bottomMargin]; */ NSAssert(self.modelCell.sd_bottomViewsArray.count, @">>>>>> 你的cell还没有调用“setupAutoHeightWithBottomView:(UIView *)bottomView bottomMargin:(CGFloat)bottomMargin”方法或者你传递的bottomView为nil,请检查并修改"); #endif [self.modelCell.contentView layoutSubviews]; NSString *cacheKey = [self cacheKeyForIndexPath:indexPath]; [_cacheDictionary setObject:@(self.modelCell.autoHeight) forKey:cacheKey]; if (self.modelCell.sd_indexPath && self.modelCell.sd_tableView) { if (self.modelCell.contentView.shouldReadjustFrameBeforeStoreCache) { self.modelCell.contentView.height_sd = self.modelCell.autoHeight; [self.modelCell.contentView layoutSubviews]; } [self.modelCell.contentView.autoLayoutModelsArray enumerateObjectsUsingBlock:^(SDAutoLayoutModel *model, NSUInteger idx, BOOL *stop) { [self.modelTableview.cellAutoHeightManager setSubviewFrameCache:model.needsAutoResizeView.frame WithIndexPath:self.modelCell.sd_indexPath]; }]; } return self.modelCell.autoHeight; } } - (CGFloat)cellHeightForIndexPath:(NSIndexPath *)indexPath model:(id)model keyPath:(NSString *)keyPath cellClass:(Class)cellClass { if (![self.modelCell isKindOfClass:cellClass]) { self.modelCell = nil; self.modelCell = [_modelCellsDict objectForKey:NSStringFromClass(cellClass)]; if (!self.modelCell) { [self registerCellWithCellClass:cellClass]; } _modelCell.contentView.tag = kSDModelCellTag; } if (self.modelCell.contentView.width_sd != self.contentViewWidth) { _modelCell.contentView.width_sd = self.contentViewWidth; } return [self cellHeightForIndexPath:indexPath model:model keyPath:keyPath]; } - (void)setContentViewWidth:(CGFloat)contentViewWidth { if (_contentViewWidth == contentViewWidth) return; CGFloat lastContentViewWidth = _contentViewWidth; _contentViewWidth = contentViewWidth; self.modelCell.contentView.width_sd = self.contentViewWidth; if (lastContentViewWidth > 0) { [_subviewFrameCacheDict removeAllObjects]; dispatch_async(dispatch_get_main_queue(), ^{ [self clearHeightCache]; [self.modelTableview reloadData]; }); } } - (void)setSubviewFrameCache:(CGRect)rect WithIndexPath:(NSIndexPath *)indexPath { if (!self.subviewFrameCacheDict) { self.subviewFrameCacheDict = [NSMutableDictionary new]; } NSString *cacheKey = [self cacheKeyForIndexPath:indexPath]; NSMutableArray *caches = [self.subviewFrameCacheDict objectForKey:cacheKey]; if (!caches) { caches = [NSMutableArray new]; [self.subviewFrameCacheDict setValue:caches forKey:cacheKey]; } [caches addObject:[NSValue valueWithCGRect:rect]]; } - (NSMutableArray *)subviewFrameCachesWithIndexPath:(NSIndexPath *)indexPath { NSString *cacheKey = [self cacheKeyForIndexPath:indexPath]; return [self.subviewFrameCacheDict valueForKey:cacheKey]; } @end @implementation UITableView (SDAutoTableViewCellHeight) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSArray *selStringsArray = @[@"reloadData", @"reloadRowsAtIndexPaths:withRowAnimation:", @"deleteRowsAtIndexPaths:withRowAnimation:", @"insertRowsAtIndexPaths:withRowAnimation:"]; [selStringsArray enumerateObjectsUsingBlock:^(NSString *selString, NSUInteger idx, BOOL *stop) { NSString *mySelString = [@"sd_" stringByAppendingString:selString]; Method originalMethod = class_getInstanceMethod(self, NSSelectorFromString(selString)); Method myMethod = class_getInstanceMethod(self, NSSelectorFromString(mySelString)); method_exchangeImplementations(originalMethod, myMethod); }]; }); } - (void)sd_reloadData { if (!self.cellAutoHeightManager.shouldKeepHeightCacheWhenReloadingData) { [self.cellAutoHeightManager clearHeightCache]; } [self sd_reloadData]; self.cellAutoHeightManager.shouldKeepHeightCacheWhenReloadingData = NO; } - (void)sd_reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation { [self.cellAutoHeightManager clearHeightCacheOfIndexPaths:indexPaths]; [self sd_reloadRowsAtIndexPaths:indexPaths withRowAnimation:animation]; } - (void)sd_deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation { for (NSIndexPath *indexPath in indexPaths) { [self.cellAutoHeightManager deleteThenResetHeightCache:indexPath]; } [self sd_deleteRowsAtIndexPaths:indexPaths withRowAnimation:animation]; } - (void)sd_insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation { [self.cellAutoHeightManager insertNewDataAtIndexPaths:indexPaths]; [self sd_insertRowsAtIndexPaths:indexPaths withRowAnimation:animation]; } /* * 下一步即将实现的功能 - (void)sd_moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath { [self sd_moveRowAtIndexPath:indexPath toIndexPath:newIndexPath]; } */ - (CGFloat)cellHeightForIndexPath:(NSIndexPath *)indexPath model:(id)model keyPath:(NSString *)keyPath cellClass:(Class)cellClass contentViewWidth:(CGFloat)contentViewWidth { self.cellAutoHeightManager.modelTableview = self; self.cellAutoHeightManager.contentViewWidth = contentViewWidth; return [self.cellAutoHeightManager cellHeightForIndexPath:indexPath model:model keyPath:keyPath cellClass:cellClass]; } - (CGFloat)cellHeightForIndexPath:(NSIndexPath *)indexPath cellClass:(__unsafe_unretained Class)cellClass cellContentViewWidth:(CGFloat)width cellDataSetting:(AutoCellHeightDataSettingBlock)cellDataSetting { self.cellDataSetting = cellDataSetting; return [self cellHeightForIndexPath:indexPath model:nil keyPath:nil cellClass:cellClass contentViewWidth:width]; } - (void)reloadDataWithExistedHeightCache { self.cellAutoHeightManager.shouldKeepHeightCacheWhenReloadingData = YES; [self reloadData]; } - (void)reloadDataWithInsertingDataAtTheBeginingOfSection:(NSInteger)section newDataCount:(NSInteger)count { self.cellAutoHeightManager.shouldKeepHeightCacheWhenReloadingData = YES; [self.cellAutoHeightManager insertNewDataAtTheBeginingOfSection:section newDataCount:count]; [self reloadData]; } - (void)reloadDataWithInsertingDataAtTheBeginingOfSections:(NSArray *)sectionNumsArray newDataCounts:(NSArray *)dataCountsArray { self.cellAutoHeightManager.shouldKeepHeightCacheWhenReloadingData = YES; [sectionNumsArray enumerateObjectsUsingBlock:^(NSNumber *num, NSUInteger idx, BOOL *stop) { int section = [num intValue]; int dataCountForSection = [dataCountsArray[idx] intValue]; [self.cellAutoHeightManager insertNewDataAtTheBeginingOfSection:section newDataCount:dataCountForSection]; }]; [self reloadData]; } - (CGFloat)cellsTotalHeight { CGFloat h = 0; if (!self.cellAutoHeightManager.heightCacheDict.count) { [self reloadData]; } NSArray *values = [self.cellAutoHeightManager.heightCacheDict allValues]; for (NSNumber *number in values) { h += [number floatValue]; } return h; } - (SDCellAutoHeightManager *)cellAutoHeightManager { SDCellAutoHeightManager *cellAutoHeightManager = objc_getAssociatedObject(self, _cmd); if (!cellAutoHeightManager) { cellAutoHeightManager = [[SDCellAutoHeightManager alloc] init]; [self setCellAutoHeightManager:cellAutoHeightManager]; } return cellAutoHeightManager; } - (void)setCellAutoHeightManager:(SDCellAutoHeightManager *)cellAutoHeightManager { objc_setAssociatedObject(self, @selector(cellAutoHeightManager), cellAutoHeightManager, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (void)setCellDataSetting:(AutoCellHeightDataSettingBlock)cellDataSetting { self.cellAutoHeightManager.cellDataSetting = cellDataSetting; } - (AutoCellHeightDataSettingBlock)cellDataSetting { return self.cellAutoHeightManager.cellDataSetting; } @end @implementation UITableViewController (SDTableViewControllerAutoCellHeight) - (CGFloat)cellHeightForIndexPath:(NSIndexPath *)indexPath cellContentViewWidth:(CGFloat)width { return [self cellHeightForIndexPath:indexPath cellContentViewWidth:width tableView:self.tableView]; } @end @implementation NSObject (SDAnyObjectAutoCellHeight) - (CGFloat)cellHeightForIndexPath:(NSIndexPath *)indexPath cellContentViewWidth:(CGFloat)width tableView:(UITableView *)tableView { tableView.cellAutoHeightManager.modelTableview = tableView; if (tableView.cellAutoHeightManager.contentViewWidth != width) { tableView.cellAutoHeightManager.contentViewWidth = width; } if ([tableView.cellAutoHeightManager heightCacheForIndexPath:indexPath]) { return [[tableView.cellAutoHeightManager heightCacheForIndexPath:indexPath] floatValue]; } UITableViewCell *cell = [tableView.dataSource tableView:tableView cellForRowAtIndexPath:indexPath]; tableView.cellAutoHeightManager.modelCell = cell; if (cell.contentView.width_sd != width) { cell.contentView.width_sd = width; } return [[tableView cellAutoHeightManager] cellHeightForIndexPath:indexPath model:nil keyPath:nil]; } @end