此次分享一個關於性能優化的源碼。git
咱們知道
UITabelView
在iOS開發中扮演者舉足輕重的角色,由於它是iOS開發中使用頻率很是高的控件之一:幾乎每一個app都離不開它,所以,UITabelView
的性能將直接影響這個app的性能。程序員
若是
UITabelView
裏的cell設計的比較簡單,那麼即便不作相應的優化,對性能的影響也不會很大。github
可是,當cell裏面涉及到圖文混排,cell高度不都相等的設計時,若是不進行一些操做的話,會影響性能,甚至會出現卡頓,形成很是很差的用戶體驗。正則表達式
最近在看一些iOS性能優化的文章,我找到了VVeboTableView這個框架。嚴格來講這個不屬於框架,而是做者用本身的方式優化UITableView
的一個實踐。編程
VVeboTableView展現了各類類型的cell(轉發貼,原貼,有圖,無圖)。雖然樣式比較複雜,可是滑動起來性能卻很好:我在個人iphone 4s上進行了Core Animation測試,在滑動的時候幀率沒有低於56,並且也沒有以爲有半點卡頓,那麼他是怎麼作到的呢?數組
看了源碼以後,我把做者的思路整理了出來:緩存
從圖中咱們能夠看出,做者從減小CPU/GPU計算量,按需加載cell,異步處理cell三大塊來實現對UITableView
的優化。下面我就從左到右,從上到下,結合代碼來展現一下做者是如何實現每一點的。性能優化
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
//cell重用
VVeboTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
if (cell==nil) {
cell = [[VVeboTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
}
//繪製
[self drawCell:cell withIndexPath:indexPath];
return cell;
}
複製代碼
這部分就不贅述了,相信你們都已經掌握。網絡
這一步咱們須要在字典轉模型裏統一計算(不須要看代碼細節,只須要知道這裏在模型裏保存了須要保存的控件的frame和整個cell的高度便可):app
- (void)loadData{
...
for (NSDictionary *dict in temp) {
NSDictionary *user = dict[@"user"];
...
NSDictionary *retweet = [dict valueForKey:@"retweeted_status"];
if (retweet) {
NSMutableDictionary *subData = [NSMutableDictionary dictionary];
...
{
float width = [UIScreen screenWidth]-SIZE_GAP_LEFT*2;
CGSize size = [subData[@"text"] sizeWithConstrainedToWidth:width fromFont:FontWithSize(SIZE_FONT_SUBCONTENT) lineSpace:5];
NSInteger sizeHeight = (size.height+.5);
subData[@"textRect"] = [NSValue valueWithCGRect:CGRectMake(SIZE_GAP_LEFT, SIZE_GAP_BIG, width, sizeHeight)];
sizeHeight += SIZE_GAP_BIG;
if (subData[@"pic_urls"] && [subData[@"pic_urls"] count]>0) {
sizeHeight += (SIZE_GAP_IMG+SIZE_IMAGE+SIZE_GAP_IMG);
}
sizeHeight += SIZE_GAP_BIG;
subData[@"frame"] = [NSValue valueWithCGRect:CGRectMake(0, 0, [UIScreen screenWidth], sizeHeight)];
}
data[@"subData"] = subData;
float width = [UIScreen screenWidth]-SIZE_GAP_LEFT*2;
CGSize size = [data[@"text"] sizeWithConstrainedToWidth:width fromFont:FontWithSize(SIZE_FONT_CONTENT) lineSpace:5];
NSInteger sizeHeight = (size.height+.5);
...
sizeHeight += SIZE_GAP_TOP+SIZE_AVATAR+SIZE_GAP_BIG;
if (data[@"pic_urls"] && [data[@"pic_urls"] count]>0) {
sizeHeight += (SIZE_GAP_IMG+SIZE_IMAGE+SIZE_GAP_IMG);
NSMutableDictionary *subData = [data valueForKey:@"subData"];
if (subData) {
sizeHeight += SIZE_GAP_BIG;
CGRect frame = [subData[@"frame"] CGRectValue];
...
sizeHeight += frame.size.height;
data[@"subData"] = subData;
}
sizeHeight += 30;
data[@"frame"] = [NSValue valueWithCGRect:CGRectMake(0, 0, [UIScreen screenWidth], sizeHeight)];
}
[datas addObject:data];
}
}
//獲取高度緩存
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
NSDictionary *dict = datas[indexPath.row];
float height = [dict[@"frame"] CGRectValue].size.height;
return height;
}
複製代碼
這裏咱們能夠看到,做者根據帖子類型的不一樣:原貼(subData)的存在與否),來逐漸疊加cell的高度。
緩存的高度在heightForRowAtIndexPath:
方法裏使用。而緩存的控件的frame的使用,咱們在下面講解繪製cell的代碼裏詳細介紹。
咱們先來看一下一個帶有原貼的轉發貼的佈局:
可能有小夥伴會將上中下這三個部分各自封裝成一個view,再經過每一個view來管理各自的子view。可是這個框架的做者卻將它們都排列到一層上。
減小了子view的層級,有助於減小cpu對各類約束的計算。這在子view的數量,層級都不少的狀況下對cpu的壓力會減輕不少。
//頭像,frame固定
avatarView = [UIButton buttonWithType:UIButtonTypeCustom];//[[VVeboAvatarView alloc] initWithFrame:avatarRect];
avatarView.frame = CGRectMake(SIZE_GAP_LEFT, SIZE_GAP_TOP, SIZE_AVATAR, SIZE_AVATAR);
avatarView.backgroundColor = [UIColor colorWithRed:250/255.0 green:250/255.0 blue:250/255.0 alpha:1];
avatarView.hidden = NO;
avatarView.tag = NSIntegerMax;
avatarView.clipsToBounds = YES;
[self.contentView addSubview:avatarView];
//覆蓋在頭像上面的圖片,製造圓角效果:frame
cornerImage = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, SIZE_AVATAR+5, SIZE_AVATAR+5)];
cornerImage.center = avatarView.center;
cornerImage.image = [UIImage imageNamed:@"corner_circle@2x.png"];
cornerImage.tag = NSIntegerMax;
[self.contentView addSubview:cornerImage];
複製代碼
在這裏,做者沒有使用任何複雜的技術來實現圖片的圓角(使用layer或者裁剪圖片),只是將一張圓角顏色和cell背景色一致的圖片覆蓋在了原來的頭像上,實現了圓角的效果(可是這個方法不太適用於有多個配色方案的app)。
上文提到過,UITableView
持有一個needLoadArr
數組,它保存着須要刷新的cell的NSIndexPath
。
咱們先來看一下needLoadArr
是如何使用的:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
...
[self drawCell:cell withIndexPath:indexPath];
...
}
- (void)drawCell:(VVeboTableViewCell *)cell withIndexPath:(NSIndexPath *)indexPath{
NSDictionary *data = [datas objectAtIndex:indexPath.row];
...
cell.data = data;
//當前的cell的indexPath不在needLoadArr裏面,不用繪製
if (needLoadArr.count>0&&[needLoadArr indexOfObject:indexPath]==NSNotFound) {
[cell clear];
return;
}
//將要滾動到頂部,不繪製
if (scrollToToping) {
return;
}
//真正繪製cell的代碼
[cell draw];
}
複製代碼
知道了如何使用needLoadArr
,咱們看一下needLoadArr
裏面的元素是如何被添加和刪除的。
//按需加載 - 若是目標行與當前行相差超過指定行數,只在目標滾動範圍的先後指定3行加載。
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset{
//targetContentOffset : 中止後的contentOffset
NSIndexPath *ip = [self indexPathForRowAtPoint:CGPointMake(0, targetContentOffset->y)];
//當前可見第一行row的index
NSIndexPath *cip = [[self indexPathsForVisibleRows] firstObject];
//設置最小跨度,當滑動的速度很快,超過這個跨度時候執行按需加載
NSInteger skipCount = 8;
//快速滑動(跨度超過了8個cell)
if (labs(cip.row-ip.row)>skipCount) {
//某個區域裏的單元格的indexPath
NSArray *temp = [self indexPathsForRowsInRect:CGRectMake(0, targetContentOffset->y, self.width, self.height)];
NSMutableArray *arr = [NSMutableArray arrayWithArray:temp];
if (velocity.y<0) {
//向上滾動
NSIndexPath *indexPath = [temp lastObject];
//超過倒數第3個
if (indexPath.row+3<datas.count) {
[arr addObject:[NSIndexPath indexPathForRow:indexPath.row+1 inSection:0]];
[arr addObject:[NSIndexPath indexPathForRow:indexPath.row+2 inSection:0]];
[arr addObject:[NSIndexPath indexPathForRow:indexPath.row+3 inSection:0]];
}
} else {
//向下滾動
NSIndexPath *indexPath = [temp firstObject];
//超過正數第3個
if (indexPath.row>3) {
[arr addObject:[NSIndexPath indexPathForRow:indexPath.row-3 inSection:0]];
[arr addObject:[NSIndexPath indexPathForRow:indexPath.row-2 inSection:0]];
[arr addObject:[NSIndexPath indexPathForRow:indexPath.row-1 inSection:0]];
}
}
//添加arr裏的內容到needLoadArr的末尾
[needLoadArr addObjectsFromArray:arr];
}
}
複製代碼
知道了如何向needLoadArr
裏添加元素,如今看一下什麼時候(重置)清理這個array:
//用戶觸摸時第一時間加載內容
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
if (!scrollToToping) {
[needLoadArr removeAllObjects];
[self loadContent];
}
return [super hitTest:point withEvent:event];
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
[needLoadArr removeAllObjects];
}
//將要滾動到頂部
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView{
scrollToToping = YES;
return YES;
}
//中止滾動
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView{
scrollToToping = NO;
[self loadContent];
}
//滾動到了頂部
- (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView{
scrollToToping = NO;
[self loadContent];
}
複製代碼
咱們能夠看到,當手指觸碰到tableview時 和 開始拖動tableview的時候就要清理這個數組。
並且在手指觸碰到tableview時和 tableview中止滾動後就會執行loadContent
方法,用來加載可見區域的cell。
loadContent
方法的具體實現:
- (void)loadContent{
//正在滾動到頂部
if (scrollToToping) {
return;
}
//可見cell數
if (self.indexPathsForVisibleRows.count<=0) {
return;
}
//觸摸的時候刷新可見cell
if (self.visibleCells&&self.visibleCells.count>0) {
for (id temp in [self.visibleCells copy]) {
VVeboTableViewCell *cell = (VVeboTableViewCell *)temp;
[cell draw];
}
}
}
複製代碼
在這裏注意一下,tableview的visibleCells
屬性是可見的cell的數組。
在講解如何異步處理cell以前,咱們大體看一下這個cell都有哪些控件:
瞭解到控件的名稱,位置以後,咱們看一下做者是如何佈局這些控件的:
在上面能夠大體看出來,除了須要異步網絡加載的頭像(avatarView)和帖子圖片(multiPhotoScrollView),做者都將這些控件畫在了一張圖上面(postBgView)。並且咱們能夠看到,在postBgView上面須要異步顯示的內容分爲四種:
下面結合代碼來說解這四種繪製:
首先看一下cell內部的核心繪製方法:
如今咱們來看一下cell繪製的核心方法,draw方法:
//將cell的主要內容繪製到圖片上
- (void)draw{
//drawed = YES說明正在繪製,則當即返回。由於繪製是異步的,因此在開始繪製以後須要當即設爲yes,防止重複繪製
if (drawed) {
return;
}
//標記當前的繪製
NSInteger flag = drawColorFlag;
drawed = YES;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//獲取整個cell的frame,已經換存在模型裏了
CGRect rect = [_data[@"frame"] CGRectValue];
//開啓圖形上下文
UIGraphicsBeginImageContextWithOptions(rect.size, YES, 0);
//獲取圖形上下文
CGContextRef context = UIGraphicsGetCurrentContext();
//背景顏色
[[UIColor colorWithRed:250/255.0 green:250/255.0 blue:250/255.0 alpha:1] set];
//經過rect填充背景顏色
CGContextFillRect(context, rect);
//若是有原帖(說明當前貼是轉發貼)
if ([_data valueForKey:@"subData"]) {
[[UIColor colorWithRed:243/255.0 green:243/255.0 blue:243/255.0 alpha:1] set];
CGRect subFrame = [_data[@"subData"][@"frame"] CGRectValue];
CGContextFillRect(context, subFrame);
//原帖上面的分割線
[[UIColor colorWithRed:200/255.0 green:200/255.0 blue:200/255.0 alpha:1] set];
CGContextFillRect(context, CGRectMake(0, subFrame.origin.y, rect.size.width, .5));
}
{
float leftX = SIZE_GAP_LEFT+SIZE_AVATAR+SIZE_GAP_BIG;
float x = leftX;
float y = (SIZE_AVATAR-(SIZE_FONT_NAME+SIZE_FONT_SUBTITLE+6))/2-2+SIZE_GAP_TOP+SIZE_GAP_SMALL-5;
//繪製名字
[_data[@"name"] drawInContext:context withPosition:CGPointMake(x, y) andFont:FontWithSize(SIZE_FONT_NAME)
andTextColor:[UIColor colorWithRed:106/255.0 green:140/255.0 blue:181/255.0 alpha:1]
andHeight:rect.size.height];
//繪製名字下面的info
y += SIZE_FONT_NAME+5;
float fromX = leftX;
float size = [UIScreen screenWidth]-leftX;
NSString *from = [NSString stringWithFormat:@"%@ %@", _data[@"time"], _data[@"from"]];
[from drawInContext:context withPosition:CGPointMake(fromX, y) andFont:FontWithSize(SIZE_FONT_SUBTITLE)
andTextColor:[UIColor colorWithRed:178/255.0 green:178/255.0 blue:178/255.0 alpha:1]
andHeight:rect.size.height andWidth:size];
}
{
//評論角
CGRect countRect = CGRectMake(0, rect.size.height-30, [UIScreen screenWidth], 30);
[[UIColor colorWithRed:250/255.0 green:250/255.0 blue:250/255.0 alpha:1] set];
CGContextFillRect(context, countRect);
float alpha = 1;
float x = [UIScreen screenWidth]-SIZE_GAP_LEFT-10;
NSString *comments = _data[@"comments"];
if (comments) {
CGSize size = [comments sizeWithConstrainedToSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) fromFont:FontWithSize(SIZE_FONT_SUBTITLE) lineSpace:5];
x -= size.width;
//圖片文字
[comments drawInContext:context withPosition:CGPointMake(x, 8+countRect.origin.y)
andFont:FontWithSize(12)
andTextColor:[UIColor colorWithRed:178/255.0 green:178/255.0 blue:178/255.0 alpha:1]
andHeight:rect.size.height];
//評論圖片(bundle裏的圖片)
[[UIImage imageNamed:@"t_comments.png"] drawInRect:CGRectMake(x-5, 10.5+countRect.origin.y, 10, 9) blendMode:kCGBlendModeNormal alpha:alpha];
commentsRect = CGRectMake(x-5, self.height-50, [UIScreen screenWidth]-x+5, 50);
x -= 20;
}
//轉發角
NSString *reposts = _data[@"reposts"];
if (reposts) {
CGSize size = [reposts sizeWithConstrainedToSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) fromFont:FontWithSize(SIZE_FONT_SUBTITLE) lineSpace:5];
x -= MAX(size.width, 5)+SIZE_GAP_BIG;
//轉發文字
[reposts drawInContext:context withPosition:CGPointMake(x, 8+countRect.origin.y)
andFont:FontWithSize(12)
andTextColor:[UIColor colorWithRed:178/255.0 green:178/255.0 blue:178/255.0 alpha:1]
andHeight:rect.size.height];
//轉發圖片(bundle裏的圖片)
[[UIImage imageNamed:@"t_repost.png"] drawInRect:CGRectMake(x-5, 11+countRect.origin.y, 10, 9) blendMode:kCGBlendModeNormal alpha:alpha];
repostsRect = CGRectMake(x-5, self.height-50, commentsRect.origin.x-x, 50);
x -= 20;
}
//更多角
[@"•••" drawInContext:context
withPosition:CGPointMake(SIZE_GAP_LEFT, 8+countRect.origin.y)
andFont:FontWithSize(11)
andTextColor:[UIColor colorWithRed:178/255.0 green:178/255.0 blue:178/255.0 alpha:.5]
andHeight:rect.size.height];
//繪製原帖底部的分割線
if ([_data valueForKey:@"subData"]) {
[[UIColor colorWithRed:200/255.0 green:200/255.0 blue:200/255.0 alpha:1] set];
CGContextFillRect(context, CGRectMake(0, rect.size.height-30.5, rect.size.width, .5));
}
}
//將整個contex轉化爲圖片,賦給背景imageview
UIImage *temp = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(), ^{
if (flag==drawColorFlag) {
postBGView.frame = rect;
postBGView.image = nil;
postBGView.image = temp;
}
});
});
//繪製兩個label的text
[self drawText];
//加載帖子裏的網路圖片,使用SDWebImage
[self loadThumb];
}
複製代碼
下面抽出每一種繪製內容的代碼,分別講解:
關於網絡圖片的異步加載和緩存,做者使用了第三方框架:SDWebImage
。
- (void)setData:(NSDictionary *)data{
_data = data;
[avatarView setBackgroundImage:nil forState:UIControlStateNormal];
if ([data valueForKey:@"avatarUrl"]) {
NSURL *url = [NSURL URLWithString:[data valueForKey:@"avatarUrl"]];
[avatarView sd_setBackgroundImageWithURL:url forState:UIControlStateNormal placeholderImage:nil options:SDWebImageLowPriority];
}
}
複製代碼
對於SDWebImage
,我相信你們都不會陌生,我前一陣寫了一篇源碼解析,有興趣的話能夠看一下:SDWebImage源碼解析。
本地圖片的繪製,只須要提供圖片在bundle內部的名字和frame就能夠繪製:
[[UIImage imageNamed:@"t_comments.png"] drawInRect:CGRectMake(x-5, 10.5+countRect.origin.y, 10, 9) blendMode:kCGBlendModeNormal alpha:alpha];
複製代碼
###3.3 異步繪製UIView
對於UIView
的繪製,咱們只須要知道要繪製的UIView
的frame和顏色便可:
//背景顏色
[[UIColor colorWithRed:250/255.0 green:250/255.0 blue:250/255.0 alpha:1] set];
//經過rect填充背景顏色
CGContextFillRect(context, rect);
複製代碼
講到如今,就剩下了關於文字的繪製,包括脫離了UILabel的純文本的繪製和UILabel裏文本的繪製,咱們先說一下關於簡單的純NSString的繪製:
做者經過傳入字符串的字體,顏色和行高,以及位置就實現了純文本的繪製:
//繪製名字
[_data[@"name"] drawInContext:context withPosition:CGPointMake(x, y) andFont:FontWithSize(SIZE_FONT_NAME)
andTextColor:[UIColor colorWithRed:106/255.0 green:140/255.0 blue:181/255.0 alpha:1]
andHeight:rect.size.height];
複製代碼
這個方法是做者在NSString
的一個分類裏自定義的,咱們看一下它的實現:
- (void)drawInContext:(CGContextRef)context withPosition:(CGPoint)p andFont:(UIFont *)font andTextColor:(UIColor *)color andHeight:(float)height andWidth:(float)width{
CGSize size = CGSizeMake(width, font.pointSize+10);
CGContextSetTextMatrix(context,CGAffineTransformIdentity);
//移動座標系統,全部點的y增長了height
CGContextTranslateCTM(context,0,height);
//縮放座標系統,全部點的x乘以1.0,全部的點的y乘以-1.0
CGContextScaleCTM(context,1.0,-1.0);
//文字顏色
UIColor* textColor = color;
//生成CTFont
CTFontRef font1 = CTFontCreateWithName((__bridge CFStringRef)font.fontName, font.pointSize,NULL);
//用於建立CTParagraphStyleRef的一些基本數據
CGFloat minimumLineHeight = font.pointSize,maximumLineHeight = minimumLineHeight+10, linespace = 5;
CTLineBreakMode lineBreakMode = kCTLineBreakByTruncatingTail;
//左對齊
CTTextAlignment alignment = kCTLeftTextAlignment;
//建立CTParagraphStyleRef
CTParagraphStyleRef style = CTParagraphStyleCreate((CTParagraphStyleSetting[6]){
{kCTParagraphStyleSpecifierAlignment, sizeof(alignment), &alignment},
{kCTParagraphStyleSpecifierMinimumLineHeight,sizeof(minimumLineHeight),&minimumLineHeight},
{kCTParagraphStyleSpecifierMaximumLineHeight,sizeof(maximumLineHeight),&maximumLineHeight},
{kCTParagraphStyleSpecifierMaximumLineSpacing, sizeof(linespace), &linespace},
{kCTParagraphStyleSpecifierMinimumLineSpacing, sizeof(linespace), &linespace},
{kCTParagraphStyleSpecifierLineBreakMode,sizeof(CTLineBreakMode),&lineBreakMode}
},6);
//設置屬性字典;對象,key
NSDictionary* attributes = [NSDictionary dictionaryWithObjectsAndKeys:
(__bridge id)font1,(NSString*)kCTFontAttributeName,
textColor.CGColor,kCTForegroundColorAttributeName,
style,kCTParagraphStyleAttributeName,
nil];
//生成path,添加到cgcontex上
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path,NULL,CGRectMake(p.x, height-p.y-size.height,(size.width),(size.height)));
//生成CF屬性字符串
NSMutableAttributedString *attributedStr = [[NSMutableAttributedString alloc] initWithString:self attributes:attributes];
CFAttributedStringRef attributedString = (__bridge CFAttributedStringRef)attributedStr;
//從attributedString拿到ctframesetter
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributedString);
//從framesetter拿到 core text 的 ctframe
CTFrameRef ctframe = CTFramesetterCreateFrame(framesetter, CFRangeMake(0,CFAttributedStringGetLength(attributedString)),path,NULL);
//將ctframe繪製到context裏面
CTFrameDraw(ctframe,context);
//由於不是對象類型,須要釋放
CGPathRelease(path);
CFRelease(font1);
CFRelease(framesetter);
CFRelease(ctframe);
[[attributedStr mutableString] setString:@""];
//恢復context座標系統
CGContextSetTextMatrix(context,CGAffineTransformIdentity);
CGContextTranslateCTM(context,0, height);
CGContextScaleCTM(context,1.0,-1.0);
}
複製代碼
在這裏,做者根據文字的起點,顏色,字體大小和行高,使用Core Text,將文字繪製在了傳入的context上面。
而對於UILabel
裏面的繪製,做者也採起了相似的方法:
首先看一下在cell實現文件裏,關於繪製label文字方法的調用:
//將文本內容繪製到圖片上,也是異步繪製
- (void)drawText{
//若是發現label或detailLabel不存在,則從新add一次
if (label==nil||detailLabel==nil) {
[self addLabel];
}
//傳入frame
label.frame = [_data[@"textRect"] CGRectValue];
//異步繪製text
[label setText:_data[@"text"]];
//若是存在原帖
if ([_data valueForKey:@"subData"]) {
detailLabel.frame = [[_data valueForKey:@"subData"][@"textRect"] CGRectValue];
//異步繪製text
[detailLabel setText:[_data valueForKey:@"subData"][@"text"]];
detailLabel.hidden = NO;
}
}
複製代碼
能夠看出,對於帖子而言,是否存在原貼(當前貼是不是轉發貼)是不固定的,因此須要在判斷以後,用hidden
屬性來控制相應控件的隱藏和顯示,而不是用addSubView
的方法。
這裏的label是做者本身封裝的VVeboLabel
。它具備高亮顯示點擊,利用正則表達式區分不一樣類型的特殊文字(話題名,用戶名,網址,emoji)的功能。
簡單介紹一下這個封裝好的label:
UIView
,能夠響應用戶點擊,在初始化以後,_textAlignment
,_textColor
,_font
,_lienSpace
屬性都會被初始化。這裏講一下這個label的setText:
方法:
//使用coretext將文本繪製到圖片。
- (void)setText:(NSString *)text{
//labelImageView 普通狀態時的imageview
//highlightImageView 高亮狀態時的iamgeview
...
//繪製標記,初始化時賦一個隨機值;clear以後更新一個隨機值
NSInteger flag = drawFlag;
//是否正在高亮(在點擊label的時候設置爲yes,鬆開的時候設置爲NO)
BOOL isHighlight = highlighting;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *temp = text;
_text = text;
CGSize size = self.frame.size;
size.height += 10;
UIGraphicsBeginImageContextWithOptions(size, ![self.backgroundColor isEqual:[UIColor clearColor]], 0);
CGContextRef context = UIGraphicsGetCurrentContext();
if (context==NULL) {
return;
}
if (![self.backgroundColor isEqual:[UIColor clearColor]]) {
[self.backgroundColor set];
CGContextFillRect(context, CGRectMake(0, 0, size.width, size.height));
}
CGContextSetTextMatrix(context,CGAffineTransformIdentity);
CGContextTranslateCTM(context,0,size.height);
CGContextScaleCTM(context,1.0,-1.0);
//Determine default text color
UIColor* textColor = self.textColor;
//Set line height, font, color and break mode
CGFloat minimumLineHeight = self.font.pointSize,maximumLineHeight = minimumLineHeight, linespace = self.lineSpace;
CTFontRef font = CTFontCreateWithName((__bridge CFStringRef)self.font.fontName, self.font.pointSize,NULL);
CTLineBreakMode lineBreakMode = kCTLineBreakByWordWrapping;
CTTextAlignment alignment = CTTextAlignmentFromUITextAlignment(self.textAlignment);
//Apply paragraph settings
CTParagraphStyleRef style = CTParagraphStyleCreate((CTParagraphStyleSetting[6]){
{kCTParagraphStyleSpecifierAlignment, sizeof(alignment), &alignment},
{kCTParagraphStyleSpecifierMinimumLineHeight,sizeof(minimumLineHeight),&minimumLineHeight},
{kCTParagraphStyleSpecifierMaximumLineHeight,sizeof(maximumLineHeight),&maximumLineHeight},
{kCTParagraphStyleSpecifierMaximumLineSpacing, sizeof(linespace), &linespace},
{kCTParagraphStyleSpecifierMinimumLineSpacing, sizeof(linespace), &linespace},
{kCTParagraphStyleSpecifierLineBreakMode,sizeof(CTLineBreakMode),&lineBreakMode}
},6);
//屬性字典
NSDictionary* attributes = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)font,(NSString*)kCTFontAttributeName,
textColor.CGColor,kCTForegroundColorAttributeName,
style,kCTParagraphStyleAttributeName,
nil];
//拿到CFAttributedStringRef
NSMutableAttributedString *attributedStr = [[NSMutableAttributedString alloc] initWithString:text attributes:attributes];
CFAttributedStringRef attributedString = (__bridge CFAttributedStringRef)[self highlightText:attributedStr];
//根據attributedStringRef 獲取CTFramesetterRef
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributedString);
CGRect rect = CGRectMake(0, 5,(size.width),(size.height-5));
if ([temp isEqualToString:text]) {
//根據 framesetter 和 attributedString 繪製text
[self drawFramesetter:framesetter attributedString:attributedStr textRange:CFRangeMake(0, text.length) inRect:rect context:context];
//恢復context
CGContextSetTextMatrix(context,CGAffineTransformIdentity);
CGContextTranslateCTM(context,0,size.height);
CGContextScaleCTM(context,1.0,-1.0);
//截取當前圖片
UIImage *screenShotimage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(), ^{
CFRelease(font);
CFRelease(framesetter);
[[attributedStr mutableString] setString:@""];
if (drawFlag==flag) {
if (isHighlight) {
//高亮狀態:把圖片付給highlightImageView
if (highlighting) {
highlightImageView.image = nil;
if (highlightImageView.width!=screenShotimage.size.width) {
highlightImageView.width = screenShotimage.size.width;
}
if (highlightImageView.height!=screenShotimage.size.height) {
highlightImageView.height = screenShotimage.size.height;
}
highlightImageView.image = screenShotimage;
}
} else {
//非高亮狀態,把圖片付給labelImageView
if ([temp isEqualToString:text]) {
if (labelImageView.width!=screenShotimage.size.width) {
labelImageView.width = screenShotimage.size.width;
}
if (labelImageView.height!=screenShotimage.size.height) {
labelImageView.height = screenShotimage.size.height;
}
highlightImageView.image = nil;
labelImageView.image = nil;
labelImageView.image = screenShotimage;
}
}
// [self debugDraw];//繪製可觸摸區域
}
});
}
});
}
複製代碼
這個被做者封裝好的Label裏面還有不少其餘的方法,好比用正則表達式高亮顯示特殊字符串等等。
關於tableView的優化,做者作了不少處理,使得這種顯示內容比較豐富的cell在4s真機上好不卡頓,很是值得學習。
本篇文章已經同步到我我的博客:VVeboTableView源碼解析
---------------------------- 2018年7月17日更新 ----------------------------
注意注意!!!
筆者在近期開通了我的公衆號,主要分享編程,讀書筆記,思考類的文章。
由於公衆號天天發佈的消息數有限制,因此到目前爲止尚未將全部過去的精選文章都發布在公衆號上,後續會逐步發佈的。
並且由於各大博客平臺的各類限制,後面還會在公衆號上發佈一些短小精幹,以小見大的乾貨文章哦~
掃下方的公衆號二維碼並點擊關注,期待與您的共同成長~