#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface FoldingModel : NSObject
@property (copy, nonatomic) NSString *content;
@property (assign, nonatomic) BOOL expanded; // 是否已经展开
@property (assign, nonatomic) CGFloat cellHeight;// Cache
@end
NS_ASSUME_NONNULL_END
//==============================================================
#import "FoldingModel.h"
@implementation FoldingModel
@end
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@class FoldingModel;
@class FoldingCell;
@protocol FoldingCellDelegate <NSObject>
- (void)foldingCell:(FoldingCell *)cell switchExpandedStateWithIndexPath:(NSIndexPath *)index;
@end
@interface FoldingCell : UITableViewCell
@property (weak, nonatomic) id <FoldingCellDelegate> delegate;
- (void)setFoldingModel:(FoldingModel *)entity indexPath:(NSIndexPath *)indexPath;
@end
NS_ASSUME_NONNULL_END
//==============================================================
#import "FoldingCell.h"
#import "FoldingModel.h"
#import <Masonry/Masonry.h>
@interface FoldingCell ()
@property (strong, nonatomic) UILabel *titleLabel;
@property (strong, nonatomic) UILabel *contentLabel;
@property (strong, nonatomic) UIButton *moreButton;
@property (strong, nonatomic) MASConstraint *contentHeightConstraint;
@property (strong, nonatomic) FoldingModel *foldingModel;
@property (strong, nonatomic) NSIndexPath *indexPath;
@end
@implementation FoldingCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
self.selectionStyle = UITableViewCellSelectionStyleNone;
[self initView];
}
return self;
}
- (void)setFoldingModel:(FoldingModel *)foldingModel indexPath:(NSIndexPath *)indexPath {
_foldingModel = foldingModel;
_indexPath = indexPath;
_titleLabel.text = [NSString stringWithFormat:@"index: %ld, contentView: %p", (long) indexPath.row, (__bridge void *) self.contentView];
_contentLabel.text = _foldingModel.content;
if (_foldingModel.expanded) {
[_contentHeightConstraint uninstall];
} else {
[_contentHeightConstraint install];
}
}
- (void)switchExpandedState:(UIButton *)button {
[_delegate foldingCell:self switchExpandedStateWithIndexPath:_indexPath];
}
- (void)initView {
// Title
_titleLabel = [[UILabel alloc] init];
[self.contentView addSubview:_titleLabel];
[_titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(21);
make.left.right.top.mas_equalTo(self.contentView).insets(UIEdgeInsetsMake(4, 8, 4, 8));
}];
// More button
_moreButton = [UIButton buttonWithType:UIButtonTypeSystem];
[_moreButton setTitle:@"More" forState:UIControlStateNormal];
[_moreButton addTarget:self action:@selector(switchExpandedState:) forControlEvents:UIControlEventTouchUpInside];
[self.contentView addSubview:_moreButton];
[_moreButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(32);
make.left.right.bottom.mas_equalTo(self.contentView);
}];
// Content
// 计算UILabel的preferredMaxLayoutWidth值,多行时必须设置这个值,否则系统无法决定Label的宽度
CGFloat preferredMaxWidth = [UIScreen mainScreen].bounds.size.width - 16;
_contentLabel = [[UILabel alloc] init];
_contentLabel.numberOfLines = 0;
_contentLabel.lineBreakMode = NSLineBreakByCharWrapping;
_contentLabel.clipsToBounds = YES;
_contentLabel.preferredMaxLayoutWidth = preferredMaxWidth; // 多行时必须设置
[self.contentView addSubview:_contentLabel];
[_contentLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.equalTo(self.contentView).insets(UIEdgeInsetsMake(4, 8, 4, 8));
make.top.mas_equalTo(_titleLabel.mas_bottom).mas_offset(4);
make.bottom.mas_equalTo(_moreButton.mas_top).mas_offset(-4);
// 先加上高度的限制
_contentHeightConstraint = make.height.mas_equalTo(@64).priorityHigh(); // 优先级只设置成High,比正常的高度约束低一些,防止冲突
}];
}
@end
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@end
//==============================================================
#import "ViewController.h"
#import "FoldingCell.h"
#import "FoldingModel.h"
@interface ViewController ()<UITableViewDelegate, UITableViewDataSource, FoldingCellDelegate>
@property (strong, nonatomic) UITableView *tableView;
@property (strong, nonatomic) FoldingCell *templateCell;
@property (strong, nonatomic) NSArray *data;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.tableView];
[self generateData];
// [_tableView reloadData];
}
- (UITableView *)tableView {
if (!_tableView) {
_tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
_tableView.delegate = self;
_tableView.dataSource = self;
_tableView.estimatedRowHeight = 0;
_tableView.sectionHeaderHeight = 0.0;
_tableView.sectionFooterHeight = 0.0;
_tableView.estimatedSectionHeaderHeight = 0.0;
_tableView.estimatedSectionFooterHeight = 0.0;
[_tableView registerClass:[FoldingCell class] forCellReuseIdentifier:NSStringFromClass([FoldingCell class])];
}
return _tableView;
}
- (void)foldingCell:(FoldingCell *)cell switchExpandedStateWithIndexPath:(NSIndexPath *)index {
// 改变数据
FoldingModel *foldingModel = self.data[index.row];
foldingModel.expanded = !foldingModel.expanded; // 切换展开还是收回
foldingModel.cellHeight = 0; // 重置高度缓存
// **********************************
// 下面两种方法均可实现高度更新,都尝试下吧
// **********************************
// 刷新方法1:只会重新计算高度,不会reload cell,所以只是把原来的cell撑大了而已,还是同一个cell实例
[_tableView beginUpdates];
[_tableView endUpdates];
[_tableView scrollToRowAtIndexPath:index atScrollPosition:UITableViewScrollPositionMiddle animated:YES];// 让展开/收回的Cell居中,酌情加,看效果决定
// 刷新方法2:先重新计算高度,然后reload,不是原来的cell实例
// [_tableView reloadRowsAtIndexPaths:@[index] withRowAnimation:UITableViewRowAnimationFade];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.data.count;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (!_templateCell) {
_templateCell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([FoldingCell class])];
}
// 获取对应的数据
FoldingModel *foldingModel = _data[indexPath.row];
// 判断高度是否已经计算过
if (foldingModel.cellHeight <= 0) {
// 填充数据
[_templateCell setFoldingModel:foldingModel indexPath:[NSIndexPath indexPathForRow:-1 inSection:-1]]; // 设置-1只是为了方便调试,在log里面可以分辨出哪个cell被调用
// 根据当前数据,计算Cell的高度,注意+1
foldingModel.cellHeight = [_templateCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height + 0.5f;
NSLog(@"Calculate height: %ld", (long) indexPath.row);
} else {
NSLog(@"Get cache %ld", (long) indexPath.row);
}
return foldingModel.cellHeight;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
FoldingCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([FoldingCell class]) forIndexPath:indexPath];
[cell setFoldingModel:self.data[indexPath.row] indexPath:indexPath];
cell.delegate = self;
return cell;
}
- (void)generateData {
NSMutableArray *tmpData = [NSMutableArray new];
for (NSInteger i = 0; i < 20; i++) {
FoldingModel *foldingModel = [[FoldingModel alloc] init];
foldingModel.content = [NSString stringWithFormat:@"第%ld条数据 == %@",i,[self getRandomLengthStr]];
[tmpData addObject:foldingModel];
}
self.data = tmpData;
}
- (NSString *)getRandomLengthStr {
NSMutableString *str = [NSMutableString string];
for (NSInteger i = 0; i < (arc4random() % 50) + 10; i++) {
[str appendString:@"case 8 content."];
}
return [str copy];
}
@end
Simulator Screen Shot - iPhone 11 Pro Max - 2020-11-28 at 19.35.01.png
网友评论