基于 CoreText 实现高性能 UITableView
引起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:colorlineBreakMode: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, ^(CFRunLoopObserverRefobserver, 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的更多相关文章
- iOS:基于CoreText的排版引擎
一.CoreText的简介 CoreText是用于处理文字和字体的底层技术.它直接和Core Graphics(又被称为Quartz)打交道.Quartz是一个2D图形渲染引擎,能够处理OSX和iOS ...
- C#编程(七十六)----------使用指针实现基于栈的高性能数组
使用指针实现基于栈的高性能数组 以一个案例为主来分析实现方法: using System; using System.Collections.Generic; using System.Linq; u ...
- common-jdbc:一个基于SpringJdbcTemplate的高性能数据库操作工具类库
项目地址:https://gitee.com/cnsugar/common-jdbc 一.简介 基于SpringJdbcTemplate的高性能数据库操作工具类库,支持mysql.oracle数据库, ...
- 基于CoreText的排版引擎
前言 本人今年主要在负责猿题库iOS客户端的开发,本文旨在通过分享猿题库iOS客户端开发过程中的技术细节,达到总结和交流的目的. 这是本技术分享系列文章的第三篇.本文涉及的技术细节是:基于CoreTe ...
- 2018-10-29-微软-Tech-Summit-技术暨生态大会课程-·-基于-Roslyn-打造高性能预编译框架...
title author date CreateTime categories 微软 Tech Summit 技术暨生态大会课程 · 基于 Roslyn 打造高性能预编译框架 lindexi 2018 ...
- SpringBoot 搭建基于 MinIO 的高性能存储服务
1.什么是MinIO MinIO是根据GNU Affero通用公共许可证v3.0发布的高性能对象存储.它与Amazon S3云存储服务兼容.使用MinIO构建用于机器学习,分析和应用程序数据工作负载的 ...
- 【转】基于 CoreText 实现的高性能 UITableView
引起UITableView卡顿比较常见的原因有cell的层级过多.cell中有触发离屏渲染的代码(譬如:cornerRadius.maskToBounds 同时使用).像素是否对齐.是否使用UITab ...
- 基于 CoreText 实现的高性能 UITableView
引起UITableView卡顿比较常见的原因有cell的层级过多.cell中有触发离屏渲染的代码(譬如:cornerRadius.maskToBounds 同时使用).像素是否对齐.是否使用UITab ...
- 基于async/non-blocking高性能redis组件库BeetleX.Redis
BeetleX.Redis是基于async/non-blocking模式实现的高性能redis组件库,组件支持redis基础指令集,并封装更简便的List,Hashset和Subscribe操作.除了 ...
随机推荐
- 矩阵中的旋转(Rotation)
参考的是<游戏和图形学的3D数学入门教程>,算是读书笔记吧. 目录 [隐藏] 1.2D中的旋转 2.3D中的旋转 2.1绕x轴旋转: 2.2绕Y轴旋转 2.3绕Z轴旋转 1.2D中的旋转 ...
- MFC中如何给静态文本框添加消息响应
需要两个步骤: 第一个: 是改变它的ID(默认情况下所有的静态文本框的ID都为IDC_STATIC,你需要改变他的ID为其他的值). 第二个: 是在它的属性对话框中选中Notify选项,VS是将该属性 ...
- 学界 | Yann LeCun新作,中日韩文本分类到底要用哪种编码?
https://www.wxwenku.com/d/102093756 AI科技评论按:前几天,Yann LeCun与其学生 张翔在arXiv上发表了一篇新作「Which Encoding is th ...
- windows vs2017环境下编译webkit 2
WebKit在Windows上 内容 安装开发工具 设置Git存储库 设置支持工具 构建WebKit 安装Cygwin(可选) 得到一个崩溃日志 本指南提供了用于构建WebKit的指令在Windows ...
- Android github XListView 分析(2-3)
本文内容 概述 XListView UML 图 下载 github XListView 概述 我们经常能见到 app 中的 listview 有"下拉更多"和"上拉加载& ...
- spring结合mybatis实现数据库读写分离
随着系统用户访问量的不断增加,数据库的频繁访问将成为我们系统的一大瓶颈之一.由于项目前期用户量不大,我们实现单一的数据库就能完成.但是后期单一的数据库根本无法支撑庞大的项目去访问数据库,那么如何解决这 ...
- 配置windows失败,不能进入系统
曾经,遇到过<配置windows失败,还原更新,请勿关机>,可多次尝试都无效. 包括: 1. 安全模式(进不了系统) 2. 带命令的安全模式 3. 最后一次正确的配置 4 ...
- 使用Selenium+PhantomJS实现网页内容加载(包括网页后期Ajax出来的结果)
一.需求 需要Nuget下面的库: 二.代码 class Program { static void Main(string[] args) { TestPhantomJsDriver(); } pr ...
- 两个自定义对象List列表取交集(intersection)
public static void main(String[] args) { List<Fpxx> list = ListUtils.intersection(getFpList1() ...
- MySQL 源码系列:1:窥探篇
1:下载源码 http://cdn.mysql.com/Downloads/MySQL-5.6/mysql-5.6.25.tar.gz http://dev.mysql.com/downloads/m ...