引起UITableView卡顿比较常见的原因有cell的层级过多、cell中有触发离屏渲染的代码(譬如:cornerRadius、maskToBounds 同时使用)、像素是否对齐、是否使用UITableView自动计算cell高度的方法等。本文将从cell层级出发,以一个仿朋友圈的demo来讲述如何让列表保持顺滑,项目的源码可在文末获得。不可否认的是,过早的优化是魔鬼,请在项目出现性能瓶颈再考虑优化。

首先看看reveal上页面层级的效果图

1、绘制文本

使用core text可以将文本绘制在一个CGContextRef上,最后再通过UIGraphicsGetImageFromCurrentImageContext()生成图片,再将图片赋值给cell.contentView.layer,从而达到减少cell层级的目的。

绘制普通文本(譬如用户昵称)在context上,相关注释在代码里:

- (void)drawInContext:(CGContextRef)context withPosition:(CGPoint)p andFont:(UIFont *)font andTextColor:(UIColor *)color andHeight:(float)height andWidth:(float)width lineBreakMode:(CTLineBreakMode)lineBreakMode {

CGSize size = CGSizeMake(width, height);

// 翻转坐标系

CGContextSetTextMatrix(context,CGAffineTransformIdentity);

CGContextTranslateCTM(context,0,height);

CGContextScaleCTM(context,1.0,-1.0);

NSMutableDictionary * attributes = [StringAttributes attributeFont:font andTextColor:color lineBreakMode:lineBreakMode];

// 创建绘制区域(路径)

CGMutablePathRef path = CGPathCreateMutable();

CGPathAddRect(path,NULL,CGRectMake(p.x, height-p.y-size.height,(size.width),(size.height)));

// 创建AttributedString

NSMutableAttributedString *attributedStr = [[NSMutableAttributedString alloc] initWithString:self attributes:attributes];

CFAttributedStringRef attributedString = (__bridge CFAttributedStringRef)attributedStr;

// 绘制frame

CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributedString);

CTFrameRef ctframe = CTFramesetterCreateFrame(framesetter, CFRangeMake(0,0),path,NULL);

CTFrameDraw(ctframe,context);

CGPathRelease(path);

CFRelease(framesetter);

CFRelease(ctframe);

[[attributedStr mutableString] setString:@""];

CGContextSetTextMatrix(context,CGAffineTransformIdentity);

CGContextTranslateCTM(context,0, height);

CGContextScaleCTM(context,1.0,-1.0);

}

绘制朋友圈内容文本(带链接)在context上,这里我还没有去实现文本多了会折叠的效果,与上面普通文本不同的是这里需要创建带链接的AttributeString和CTLineRef的逐行绘制:

- (NSMutableAttributedString *)highlightText:(NSMutableAttributedString *)coloredString{

// 创建带高亮的AttributedString

NSString* string = coloredString.string;

NSRange range = NSMakeRange(0,[string length]);

NSDataDetector *linkDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:nil];

NSArray *matches = [linkDetector matchesInString:string options:0 range:range];

for(NSTextCheckingResult* match in matches) {

[self.ranges addObject:NSStringFromRange(match.range)];

UIColor *highlightColor = UIColorFromRGB(0x297bc1);

[coloredString addAttribute:(NSString*)kCTForegroundColorAttributeName

value:(id)highlightColor.CGColor range:match.range];

}

return coloredString;

}

- (void)drawFramesetter:(CTFramesetterRef)framesetter

attributedString:(NSAttributedString *)attributedString

textRange:(CFRange)textRange

inRect:(CGRect)rect

context:(CGContextRef)c {

CGMutablePathRef path = CGPathCreateMutable();

CGPathAddRect(path, NULL, rect);

CTFrameRef frame = CTFramesetterCreateFrame(framesetter, textRange, path, NULL);

CGFloat ContentHeight = CGRectGetHeight(rect);

CFArrayRef lines = CTFrameGetLines(frame);

NSInteger numberOfLines = CFArrayGetCount(lines);

CGPoint lineOrigins[numberOfLines];

CTFrameGetLineOrigins(frame, CFRangeMake(0, numberOfLines), lineOrigins);

// 遍历每一行

for (CFIndex lineIndex = 0; lineIndex < numberOfLines; lineIndex++) {

CGPoint lineOrigin = lineOrigins[lineIndex];

CTLineRef line = CFArrayGetValueAtIndex(lines, lineIndex);

CGFloat descent = 0.0f, ascent = 0.0f, lineLeading = 0.0f;

CTLineGetTypographicBounds((CTLineRef)line, &ascent, &descent, &lineLeading);

CGFloat penOffset = (CGFloat)CTLineGetPenOffsetForFlush(line, NSTextAlignmentLeft, rect.size.width);

CGFloat y = lineOrigin.y - descent - self.font.descender;

// 设置每一行位置

CGContextSetTextPosition(c, penOffset + self.xOffset, y - self.yOffset);

CTLineDraw(line, c);

// CTRunRef同一行中文本的不同样式,包括颜色、字体等,此处用途为处理链接高亮

CFArrayRef runs = CTLineGetGlyphRuns(line);

for (int j = 0; j < CFArrayGetCount(runs); j++) {

CGFloat runAscent, runDescent, lineLeading1;

CTRunRef run = CFArrayGetValueAtIndex(runs, j);

NSDictionary *attributes = (__bridge NSDictionary*)CTRunGetAttributes(run);

// 判断是不是链接

if (!CGColorEqualToColor((__bridge CGColorRef)([attributes valueForKey:@"CTForegroundColor"]), self.textColor.CGColor)) {

CFRange range = CTRunGetStringRange(run);

float offset = CTLineGetOffsetForStringIndex(line, range.location, NULL);

// 得到链接的CGRect

CGRect runRect;

runRect.size.width = CTRunGetTypographicBounds(run, CFRangeMake(0,0), &runAscent, &runDescent, &lineLeading1);

runRect.size.height = self.font.lineHeight;

runRect.origin.x = lineOrigin.x + offset+ self.xOffset;

runRect.origin.y = lineOrigin.y;

runRect.origin.y -= descent + self.yOffset;

// 因为坐标系被翻转,链接正常的坐标需要通过CGAffineTransform计算得到

CGAffineTransform transform = CGAffineTransformMakeTranslation(0, ContentHeight);

transform = CGAffineTransformScale(transform, 1.f, -1.f);

CGRect flipRect = CGRectApplyAffineTransform(runRect, transform);

// 保存是链接的CGRect

NSRange nRange = NSMakeRange(range.location, range.length);

self.framesDict[NSStringFromRange(nRange)] = [NSValue valueWithCGRect:flipRect];

// 保存同一条链接的不同CGRect,用于点击时背景色处理

for (NSString *rangeString in self.ranges) {

NSRange range = NSRangeFromString(rangeString);

if (NSLocationInRange(nRange.location, range)) {

NSMutableArray *array = self.relationDict[rangeString];

if (array) {

[array addObject:NSStringFromCGRect(flipRect)];

self.relationDict[rangeString] = array;

} else {

self.relationDict[rangeString] = [NSMutableArray arrayWithObject:NSStringFromCGRect(flipRect)];

}

}

}

}

}

}

CFRelease(frame);

CFRelease(path);

}

上述方法运用起来就是:

这样就完成了文本的显示。

2、显示图片

图片包括用户头像和朋友圈的内容,这里只是将CALayer添加到contentView.layer上,具体做法是继承了CALayer,实现部分功能。

通过链接显示图片:

- (void)setContentsWithURLString:(NSString *)urlString {

self.contents = (__bridge id _Nullable)([UIImage imageNamed:@"placeholder"].CGImage);

@weakify(self)

SDWebImageManager *manager = [SDWebImageManager sharedManager];

[manager downloadImageWithURL:[NSURL URLWithString:urlString]

options:SDWebImageCacheMemoryOnly

progress:nil

completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {

if (image) {

@strongify(self)

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

if (!_observer) {

_observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopBeforeWaiting | kCFRunLoopExit, false, POPAnimationApplyRunLoopOrder, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {

self.contents = (__bridge id _Nullable)(image.CGImage);

});

if (_observer) {

CFRunLoopAddObserver(CFRunLoopGetMain(), _observer,  kCFRunLoopCommonModes);

}

}

});

self.originImage = image;

}

}];

}

其他比较简单就不展开。

3、显示小视频

之前的一篇文章简单讲了怎么自己做一个播放器,这里就派上用场了。而显示小视频封面图片的CALayer同样在显示小视频的时候可以复用。

这里使用了NSOperationQueue来保障播放视频的流畅性,具体继承NSOperation的VideoDecodeOperation相关代码如下:

解码图片是因为UIImage在界面需要显示的时候才开始解码,这样可能会造成主线程的卡顿,所以在子线程对其进行解压缩处理。

具体的使用:

4、其他

1、触摸交互是覆盖了以下方法实现:

- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event

- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event

2、页面上FPS的测量是使用了YYKit项目中的YYFPSLabel。

3、测试数据是微博找的,其中小视频是Gif快手。

本文的代码在https://github.com/hawk0620/PYQFeedDemo

基于 CoreText 实现的高性能 UITableView的更多相关文章

  1. 【转】基于 CoreText 实现的高性能 UITableView

    引起UITableView卡顿比较常见的原因有cell的层级过多.cell中有触发离屏渲染的代码(譬如:cornerRadius.maskToBounds 同时使用).像素是否对齐.是否使用UITab ...

  2. iOS:基于CoreText的排版引擎

    一.CoreText的简介 CoreText是用于处理文字和字体的底层技术.它直接和Core Graphics(又被称为Quartz)打交道.Quartz是一个2D图形渲染引擎,能够处理OSX和iOS ...

  3. 基于.NET MVC的高性能IOC插件化架构

    基于.NET MVC的高性能IOC插件化架构 最近闲下来,整理了下最近写的代码,先写写架构,后面再分享几个我自己写的插件 最近经过反复对比,IOC框架选择了Autofac,原因很简单,性能出众,这篇博 ...

  4. 基于nginx+lua+redis高性能api应用实践

    基于nginx+lua+redis高性能api应用实践 前言 比较传统的服务端程序(PHP.FAST CGI等),大多都是通过每产生一个请求,都会有一个进程与之相对应,请求处理完毕后相关进程自动释放. ...

  5. 基于CoreText的排版引擎

    前言 本人今年主要在负责猿题库iOS客户端的开发,本文旨在通过分享猿题库iOS客户端开发过程中的技术细节,达到总结和交流的目的. 这是本技术分享系列文章的第三篇.本文涉及的技术细节是:基于CoreTe ...

  6. 基于Protobuf的分布式高性能RPC框架——Navi-Pbrpc

    基于Protobuf的分布式高性能RPC框架——Navi-Pbrpc 二月 8, 2016 1 简介 Navi-pbrpc框架是一个高性能的远程调用RPC框架,使用netty4技术提供非阻塞.异步.全 ...

  7. 基于 CoreText 实现高性能 UITableView

    引起UITableView卡顿比较常见的原因有cell的层级过多.cell中有触发离屏渲染的代码(譬如:cornerRadius.maskToBounds 同时使用).像素是否对齐.是否使用UITab ...

  8. 基于MINA构建简单高性能的NIO应用

    mina是非常好的C/S架构的java服务器,这里转了一篇关于它的使用感受. 前言MINA是Trustin Lee最新制作的Java通讯框架.通讯框架的主要作用是封装底层IO操作,提供高级的操作API ...

  9. 基于开源软件构建高性能集群NAS系统,包括负载均衡(刘爱贵)

    大数据时代的到来已经不可阻挡,面对数据的爆炸式增长,尤其是半结构化数据和非结构化数据,NoSQL存储系统和分布式文件系统成为了技术浪潮,得到了长足的发展.非结构化数据目前呈现更加快速的增长趋势,IDC ...

随机推荐

  1. maven安装和环境变量配置

    maven安装和环境变量配置 myeclipse自带maven(Maven4MyEclipse)创建项目:新建Web Projects项目,在新建的页面上打上maven的勾.新建的项目里会多出个pom ...

  2. NODE.JS玩玩

    按一个网页的来,最好最后能到EXPRESS.JS. http://www.nodebeginner.org/index-zh-cn.html 这样就能对比DJANGO,看看两者的WEB框架,加深认识. ...

  3. [wikioi]能量项链

    http://wikioi.com/problem/1154/ 这是石子归并的加强版,基本就是分治法的DP.但是有了个环,因为任何一个位置都可开始,所以就建立2*N的数组,然后对可能的区间遍历一次,就 ...

  4. void (*fun)(void);什么意思?

    2440test程序中的Main.c中在结构体中有这么一句: void (*fun)(void); 后查阅资料得知这句代码的意思是: 定义一个函数指针. 比如:定义一个指向函数的指针,该函数有一个整形 ...

  5. Gdi+实用入门

    大部分是参照其它资料,然后加以自己的理解,那是什么,总结.算不得什么教程.......汗,自己看着就行了..如果别人能看那就更好了. 首先下载GDI+文件包,一个动态链接库,使用GDI+就是调用那个动 ...

  6. 【Xamarin开发 Android 系列 13】 应用打包部署

    原文:[Xamarin开发 Android 系列 13] 应用打包部署 开始倒叙咯................ 先更新大宝部署吧,这个章节比较的Easy,童鞋们不用费脑筋.点解?从界面上填写几个参 ...

  7. Android开源项目发现---Menu 篇(持续更新)

    1. MenuDrawer 滑出式菜单,通过拖动屏幕边缘滑出菜单,支持屏幕上下左右划出,支持当前View处于上下层,支持Windows边缘.ListView边缘.ViewPager变化划出菜单等. 项 ...

  8. 12.URL重写

    为什么要URL重写?1.有利于SEO(搜索引擎优化),带参数的RUL权重较低.2.地址看起来更正规,推广uid. 如我们一般在访问网页是会带参数,http://aaa.com/view.htm?id= ...

  9. Learning WCF Chapter2 Data Contracts

    A data contract describes how CLR types map to XSD schema definitions. Data contracts are the prefer ...

  10. SIFT算法:DoG尺度空间生产

    SIFT算法:DoG尺度空间生产  SIFT算法:KeyPoint找寻.定位与优化 SIFT算法:确定特征点方向  SIFT算法:特征描述子 目录: 1.高斯尺度空间(GSS - Gauss Scal ...