项目回复评论-动态布局

项目中要做块,发起事件着和粉丝互动,相互回复评论.在这块.用到动态布局.接下来就总结下自己写的这块.

一.首先网络请求接口是公司的..不能在这写了,我就自己本地写个 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  *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 *)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 *)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;
}

这里直接从计算好的数据源中取高度..因此这里不会造成性能的影响.
现在差不多就完成了.这里是效果图,虽然有点丑.

F43F761F-5301-4C73-8A81-E11E08F978EC.png

总结:

1.之所以把计算耗时的操作放到数据处理的里面去.一是为了与数据模型绑定,因而和 View 来绑定.达到高聚合.二是这是一个费时的操作.应为网络请求会等待时间.这个是无法改变的.所以,把他放这和网络请求中增加那么一小点时间还是可以的.三是放在模型转换的方法中,只会计算一次.达到高效率.起到缓存的效果.有人会问,为啥不放到子线程.这个数据回来就要刷新界面.意味这就得布局界面,这时不能保证已经算好.所以不能放到子线程.

2.也调研了一些,说可以吧计算耗时的东西方法 RUNloop 的空闲时候.这个应该不错,但是水平有限,以后补上.

3.其实这里犯了个错误,就是尽量不要动态在 UITableViewCell 上添加子控件.但是,这地方没办法.以后优化补上.

最后.这里说下作者测得的性能:

FA9741B6-B4B1-42D6-9374-6FDEEF48A87E.png
E7CF80F5-2ED5-4BFB-9535-BEB112FAF955.png

3DC68657-D3FE-4DB1-945B-1ADF3E85191B.png

在这可以看出.在计算方法里面是好了点时间.但是在

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;

这个方法里面耗时.这个方法是调用时机是很频繁的.在 tableView 初始化的时候,要调用多少个cell个这个方法.来确定 contenetSize ,在 每次调用cellForRow方法又得调用一次,所以说.这样写的话,能稍微好点.

这里留个 demo 的地址:.

你可能感兴趣的:(项目回复评论-动态布局)