「OC」知乎日报——第三周
本周总结
本周完成了知乎日报的评论区功能,实现了用UITextView和富文本字符串结合实现了评论区的展开和回收,学会使用Masnory实现评论区的自定义行高,修改了原先详情页的点赞收藏状态的逻辑问题和滚动视图初始位置不符的问题。
评论区展开
关于评论区展开大致有三个要点,首先是判断这个评论是否是否有回复,第二个就是判断textView之中文字的行数,最后一个就是当前textView的文字需要展开的时候进行的相关操作
长评和短评的Model
#import <Foundation/Foundation.h>
#import "YYModel.h"
@class ReplyTo;
NS_ASSUME_NONNULL_BEGIN
@interface ShortComment : NSObject<YYModel>
@property (nonatomic, copy) NSString *author;
@property (nonatomic, copy) NSString *content;
@property (nonatomic, copy) NSString *avatar;
@property (nonatomic, assign) NSInteger commentId;
@property (nonatomic, assign) NSInteger likes;
@property (nonatomic, assign) NSTimeInterval time;
@property (nonatomic, strong) ReplyTo *replyTo;
@property (nonatomic, assign) BOOL isLike;
@property (nonatomic, assign) BOOL isExpanded;
@end
#import <Foundation/Foundation.h>
#import "YYModel.h"
@class ReplyTo;
NS_ASSUME_NONNULL_BEGIN
@interface LongComment : NSObject<YYModel>
@property (nonatomic, copy) NSString *author;
@property (nonatomic, copy) NSString *content;
@property (nonatomic, copy) NSString *avatar;
@property (nonatomic, assign) NSInteger commentId;
@property (nonatomic, assign) NSInteger likes;
@property (nonatomic, assign) NSTimeInterval time;
@property (nonatomic, strong) ReplyTo *replyTo;
@property (nonatomic, assign) BOOL isExpanded;
@property (nonatomic, assign) BOOL isLike;
@end
NS_ASSUME_NONNULL_END
这些就是对应的短评和长评的Model,接下来还需要给我们写一个相关的网络申请在我们的NetworkManager之中,以下是方法实现
- (void)fetchLongCommentsForNewsID:(NSString *)ID completion:(void (^)(NSMutableArray<LongComment *> *comments, NSError *error))completion {
NSString *urlString = [NSString stringWithFormat:@"https://news-at.zhihu.com/api/4/story/%@/long-comments", ID];
[self GET:urlString parameters:nil headers:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
if (completion) {
completion(comments, nil);
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
if (completion) {
completion(nil, error);
}
}];
}
- (void)fetchShortCommentsForNewsID:(NSString *)ID completion:(void (^)(NSMutableArray<LongComment *> *comments, NSError *error))completion {
NSString *urlString = [NSString stringWithFormat:@"https://news-at.zhihu.com/api/4/story/%@/short-comments", ID];
[self GET:urlString parameters:nil headers:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject)
if (completion) {
completion(comments, nil);
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
if (completion) {
completion(nil, error);
}
}];
}
我们申请的数据返回的是关于长评和短评的数组,所以tableView的相关信息源就是根据这两个数组来的,大致也没什么难点,就以这个逻辑判断为主就行
if (indexPath.section == 0 && self.longComments.count > 0) {
} else if ((indexPath.section == 1 || (indexPath.section == 0 && self.longComments.count == 0)) && self.shortComments.count > 0) {
}
cell的内容
关于cell的布局在这里就不给出了,就直接给出如何实现评论区展开的代码,首先是如何判断textView之中的内容是否超过三行,我们使用sizeThatFits
给出对应限制的宽度,就能得到文本框的高度,因为font之中也有关于每一行行高的属性,我们访问font.lineHeight
就可以得到,将行数向下取整,就能算出对应的文本行数
CGSize size = [self.replyTextView sizeThatFits:CGSizeMake(317, CGFLOAT_MAX)];
CGFloat lineCount = floor(size.height / self.replyTextView.font.lineHeight);
如果这个文本框内容超过三行,那么就使用富文本将内容包装,以下是相关逻辑
if (lineCount > 3 && !self.isExpanded) {
NSString *Text = [text substringToIndex:[self truncatedTextIndexForText:text]];
NSMutableAttributedString *expandableText = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@...展开", Text]];
[expandableText addAttribute:NSForegroundColorAttributeName value:[UIColor grayColor] range:NSMakeRange(0, Text.length)];
[expandableText addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:14] range:NSMakeRange(0, Text.length)];
[expandableText addAttribute:NSForegroundColorAttributeName value:[UIColor blueColor] range:NSMakeRange(Text.length, 5)];
[expandableText addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:14] range:NSMakeRange(Text.length, 5)];
self.replyTextView.attributedText = expandableText;
} else if (lineCount > 3 && self.isExpanded) {
NSMutableAttributedString *fullText = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@ 收起", text]];
[fullText addAttribute:NSForegroundColorAttributeName value:[UIColor grayColor] range:NSMakeRange(0, fullText.length)];
[fullText addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:14] range:NSMakeRange(0, fullText.length)];
[fullText addAttribute:NSForegroundColorAttributeName value:[UIColor blueColor] range:NSMakeRange(fullText.length - 2, 2)];
[fullText addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:14] range:NSMakeRange(fullText.length - 2, 2)];
self.replyTextView.attributedText = fullText;
}
这个富文本字符串的创建,还是比较简单的,这里就不过多赘述,下面介绍的才是这个展开的主体,truncatedTextIndexForText
也就是这个函数,这个函数是用于获取当文本框处于未展开的状态时的被截断的字符串
- (NSUInteger)truncatedTextIndexForText:(NSString *)text {
UIFont *font = self.replyTextView.font ?: [UIFont systemFontOfSize:14];
CGFloat maxHeight = font.lineHeight * 3;
// 容纳 "...展开" 的宽度预估(根据字体和文字大小)
NSString *expandSuffix = @"...展开";
CGSize expandSize = [expandSuffix boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName: font}
context:nil].size;
CGFloat reservedWidth = expandSize.width;
NSString *substring = text;
for (NSInteger i = text.length - 1; i >= 0; i--) {
substring = [text substringToIndex:i];
// 计算剩余文字的显示大小
CGSize substringSize = [substring boundingRectWithSize:CGSizeMake(317 - reservedWidth, CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName: font}
context:nil].size;
// 如果高度小于等于最大高度,返回当前索引
if (substringSize.height <= maxHeight) {
return i;
}
}
return text.length;
}
我们来解析这个函数,首先是被截断字符串的后缀,...展开
我们用boundingRectWithSize
的方法计算这个后缀的长度,然后就是for循环之中的内容,我们每次都将字符串的长度减去一个单位,检验宽度是否会大于三行,若检验到当前的字符串满足不高于三行的条件,就将下标i进行返回。
自适应行高
我们还需要实现评论区自适应行高,首先在tableView之中设置属性,使得tableView
self.tableView.rowHeight = UITableViewAutomaticDimension;
然后在cell的布局之中使用Masonry这个库进行自定义行高的计算,就是我们在布局textView的时候,上下两边都直接和cell的contentView进行约束
[self.commentText mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.usernameLabel.mas_bottom);
make.left.equalTo(self.usernameLabel);
make.right.equalTo(self.contentView).offset(-20);
}];
[self.replyTextView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.commentText.mas_bottom);
make.left.equalTo(self.commentText.mas_left);
make.right.equalTo(self.contentView).offset(-20);
make.bottom.equalTo(self.contentView).offset(-15);
}];
这样就可以实现相关的cell自适应行高。
下周计划
其实本周看来没有写很多东西,但是确实在实现评论区展开的功能时,耗费了很多时间去学习一些的东西,但从结果体现不出耗费的时间的意义,很多时候扣细节也没将细节扣好,比如无法控制展开按钮一定跟在第三行的最后面,遇到这些问题可能也算得上是写项目的必由之路了。
下周计划就是对tableViewCell进行相关的优化,实现用字典存储相关的行高信息,掌握FMDB的用法,用本地化的方式进行收藏以及点赞信息的存储。