項目中要做塊,發起事件著和粉絲互動,相互回復評論.在這塊.用到動態布局.接下來就總結下自己寫的這塊.
一.首先網絡請求接口是公司的..不能在這寫了,我就自己本地寫個 plist 文件,模擬網路請求吧。
NSString*filePath = [[NSBundlemainBundle]pathForResource:@"Data"ofType:@"plist"];
NSArray*dataArr = [[NSArrayalloc]initWithContentsOfFile:filePath];
for(NSDictionary*dictindataArr) {
CommentModel*model = [[CommentModelalloc]init];
[modelsetValuesForKeysWithDictionary:dict];
[self.datasourceaddObject:model];
}
[_tableViewreloadData];
二.網絡請求回來數據了,我們應該設計怎么把布局和數據聯系在一起.這里難得就是計算文本的高度.應該在什么時候計算.什么時候調用.在這我的思路是在對數據進行處理的時候.順便把高度計算出來.和數據模型綁定在一起.這樣就能和布局的 View 綁定在一起.
@interface ReplayModel : NSObject
/** 評論方式 **/
@property (nonatomic, copy) NSString *CommentType;
/** 評論內容 **/
@property (nonatomic, copy) NSString *Content;
/** 來自評論者的 id **/
@property (nonatomic, strong) NSNumber *FromMemberId;
@property (nonatomic, strong) NSNumber *Id;
/** 被評論者的 id **/
@property (nonatomic, strong) NSNumber *ToMemberId;
/** 被評論者發表的任務 id **/
@property (nonatomic, strong) NSNumber *ToTaskId;
@end
@interface CommentModel : NSObject
/** 評論方式 **/
@property (nonatomic, copy) NSString *CommentType;
/** 內容 **/
@property (nonatomic, copy) NSString *Content;
/** 創建時間 **/
@property (nonatomic, copy) NSString *CreateDateStr;
/** 來自評論者的名稱 **/
@property (nonatomic, copy) NSString *FromNickname;
/** 來自評論者的頭像 **/
@property (nonatomic, copy) NSString *FromHeadImage;
/** 來自評論者的id **/
@property (nonatomic, strong) NSNumber *FromMemberId;
@property (nonatomic, strong) NSNumber *Id;
/** 回復 **/
@property (nonatomic, strong) NSArray <ReplayModel *> *Replay;
/** 內容高度 **/
@property (nonatomic, assign) CGFloat contentHeight;
@end
這是我的數據模型.在大的數據模型里面嵌套了一個專門關于評論的回復的數據模型.在. m 的才是中點.
- (void)setNilValueForKey:(NSString *)key{
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key{
}
- (void)setValue:(id)value forKey:(NSString *)key{
if ([key isEqualToString:@"Content"]) {
//計算高度
CGFloat replyHeight = [[value stringByAppendingString:@"回復: "] boundingRectWithSize:CGSizeMake(SCREENWIDTH - 80, MAXFLOAT) options:NSStringDrawingUsesFontLeading | NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:16]} context:nil].size.height;
//拼接到數據中
value = [NSString stringWithFormat:@"%f#回復: %@",replyHeight,value];
[super setValue:value forKey:key];
}else{
[super setValue:value forKey:key];
}
}
@end
@implementation CommentModel
- (void)setNilValueForKey:(NSString *)key{
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key{
}
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *,id> *)keyedValues{
[super setValuesForKeysWithDictionary:keyedValues];
//計算文本高度
[keyedValues enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
if ([key isEqualToString:@"Content"]) {
self.contentHeight = [obj boundingRectWithSize:CGSizeMake(SCREENWIDTH - 103, MAXFLOAT) options:NSStringDrawingUsesFontLeading | NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:16]} context:nil].size.height;
return ;
}
if ([key isEqualToString:@"Replay"]) {
NSMutableArray *relayArr = [NSMutableArray array];
[obj enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
ReplayModel *replayModel = [[ReplayModel alloc] init];
[replayModel setValuesForKeysWithDictionary:obj];
[relayArr addObject:replayModel];
}];
self.Replay = relayArr;
}
}];
}
@end
這里處理是在
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues
這個方法里面做操作.將作者文本高度計算出來.賦值給事先定義的contentHeight這個屬性.好處之后說.在判斷在key 是Replay.那就是嵌套的回復評論的 model. 所以這時要對它處理,一一賦值.
在ReplayModel的
- (void)setValue:(nullable id)value forKey:(NSString *)key;
方法里面計算高度.這時候因為是不固定的高度,所以不能為這個 model 設置屬性.所以想到直接將計算好的高度拼接到評論下面.中間件個#標示符.這時.數據模型算是處理完了.
三.UITableViewCell 里面的操作
這里主要有幾個東西.
/** 存放評論lable 高度的數組 **/
@property (nonatomic, strong) NSMutableArray *replayLabelHeights;
/** 存放評論lable文本的數組 **/
@property (nonatomic, strong) NSMutableArray *replayLabelTexts;
用兩個數據去存儲所有緩存的高度和文本的內容
接下來就是重新設置頭條問題的高度.和動態添加回復評論
//如果是沒回復的,返回
if (commentModel.Replay == nil) {
return ;
}
//存放評論lable 高度的數組
_replayLabelHeights = [NSMutableArray array];
_replayLabelTexts = [NSMutableArray array];
//回復
[commentModel.Replay enumerateObjectsUsingBlock:^(ReplayModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
ReplayModel *replayModel = obj;
//分割數據.收集評論lable的高度和文本
[_replayLabelHeights addObject:[replayModel.Content componentsSeparatedByString:@"#"].firstObject];
NSString *textStr = @"";
int i = 0;
for (NSString *text in [replayModel.Content componentsSeparatedByString:@"#"]) {
i ++;
if (i == 1) {
continue;
}
textStr = [textStr stringByAppendingString:text];
}
[_replayLabelTexts addObject:textStr];
}];
//動態添加回復 label
for (int i = 0; i < _replayLabelHeights.count; i ++) {
CGFloat height = [self addHeightWithI:i - 1];
UILabel *replyLabel = [[UILabel alloc] initWithFrame:CGRectMake(76, _contentLabel.bottomY + height, SCREENWIDTH - 80, [_replayLabelHeights[i] floatValue])];
replyLabel.numberOfLines = 0;
replyLabel.layer.borderColor = [UIColor lightGrayColor].CGColor;
replyLabel.layer.borderWidth = 1;
replyLabel.backgroundColor = [UIColor colorWithRed:200 / 255.0 green:205 / 255.0 blue:246 / 255.0 alpha:1];
replyLabel.font = [UIFont systemFontOfSize:16];
replyLabel.text = _replayLabelTexts[i];
[self.contentView addSubview:replyLabel];
}
動態計算設置 Y 時,擁到這樣個方法.
- (CGFloat)addHeightWithI:(int)i{
__block CGFloat height = 0;
//第一行直接返回,不取值
if (i < 0) {
return height;
}
[_replayLabelHeights enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
height += [obj floatValue];
height += 8;
//取到對應行后返回值
if (idx == i) {
*stop = YES;
}
}];
return height;
}
來確定動態添加的回復評論的 lable 的位置
四.最后就是UIViewController的協議方法的東西了.這里說以下兩個方法
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
if (indexPath.row == 0) {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:indentifierNormal forIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.textLabel.text = @"評論";
return cell;
}
CommmentTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:indentifier forIndexPath:indexPath];
cell.assignment(self.datasource[indexPath.row - 1]);
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
if (indexPath.row == 0) {
return 44;
}
CommentModel *commentModel = self.datasource[indexPath.row - 1];
//51為第一個內容劇 cell 上的距離
CGFloat cellHeight = 51 + (commentModel.Replay.count * 8) + commentModel.contentHeight;
//如果沒有回復,直接返回
if (commentModel.Replay == nil) {
//8為據底部的邊距
return cellHeight + 8;
}
//計算回復的評論高度
for (ReplayModel *replayModel in commentModel.Replay) {
NSArray *replayArr = [replayModel.Content componentsSeparatedByString:@"#"];
cellHeight += [replayArr.firstObject floatValue];
}
return cellHeight;
}
這里直接從計算好的數據源中取高度..因此這里不會造成性能的影響.
現在差不多就完成了.這里是效果圖,雖然有點丑.??
總結:
1.之所以把計算耗時的操作放到數據處理的里面去.一是為了與數據模型綁定,因而和 View 來綁定.達到高聚合.二是這是一個費時的操作.應為網絡請求會等待時間.這個是無法改變的.所以,把他放這和網絡請求中增加那么一小點時間還是可以的.三是放在模型轉換的方法中,只會計算一次.達到高效率.起到緩存的效果.有人會問,為啥不放到子線程.這個數據回來就要刷新界面.意味這就得布局界面,這時不能保證已經算好.所以不能放到子線程.
2.也調研了一些,說可以吧計算耗時的東西方法 RUNloop 的空閑時候.這個應該不錯,但是水平有限,以后補上.
3.其實這里犯了個錯誤,就是盡量不要動態在 UITableViewCell 上添加子控件.但是,這地方沒辦法.以后優化補上.
最后.這里說下作者測得的性能:
在這可以看出.在計算方法里面是好了點時間.但是在
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
這個方法里面耗時.這個方法是調用時機是很頻繁的.在 tableView 初始化的時候,要調用多少個cell個這個方法.來確定 contenetSize ,在 每次調用cellForRow方法又得調用一次,所以說.這樣寫的話,能稍微好點.