基于 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: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

 

转载于:https://www.cnblogs.com/fengmin/p/5669002.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/372671.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Web Magic 总体架构

1.2 总体架构 WebMagic的结构分为Downloader、PageProcessor、Scheduler、Pipeline四大组件&#xff0c;并由Spider将它们彼此组织起来。这四大组件对应爬虫生命周期中的下载、处理、管理和持久化等功能。WebMagic的设计参考了Scapy&#xff0c;但是实现方式更Java化一些。 而S…

SpringMVC搭建+实例

想做一点自己喜欢的东西&#xff0c;研究了一下springMVC,所以就自己搭建一个小demo,可供大家吐槽。 先建一个WEB工程&#xff0c;这个相信大家都会&#xff0c;这里不在多说。去网上下载spring jar包&#xff0c;然后在WEB-INF下新建一个lib文件&#xff0c;将下载的jar包放进…

php8更新,PHP 8 中新特性以及重大调整

PHP 8&#xff0c;PHP 的一个新的大版本&#xff0c;预计将于2020年12月3日发布&#xff0c;这意味着将不会有 PHP 7.5 版本。PHP8目前正处于非常活跃的开发阶段&#xff0c;所以在接下来的几个月里&#xff0c;情况可能会发生很大的变化。在这篇文章中&#xff0c;我会维持一个…

Javascript学习之函数(function)

http://www.cnblogs.com/royalroads/p/4418587.html 在JS中,Function(函数)类型实际上是对象;每个函数都是Function类型的实例&#xff0c;而且都与其他引用类型一样具有属性和方法。由于函数是对象,因此函数名实际上也是一个指向函数对象的指针。 一 函数的声明方式 //1.函数声…

用于Spring JPA2后端的REST CXF

在本演示中&#xff0c;我们将使用spring / jpa2后端生成一个REST / CXF应用程序。 该演示演示了分钟项目的轨迹REST-CXF 。 演示2中的模型保持不变。 浓缩保持不变。 但是轨道改变了 添加的是2层&#xff1a; 在JPA2之上具有弹簧集成的DAO层 具有JAX-RS批注的REST-CXF层…

完整的WebApplication JSF EJB JPA JAAS –第1部分

这篇文章将是迄今为止我博客中最大的一篇文章&#xff01; 我们将看到完整的Web应用程序。 最新的技术将完成此工作&#xff08;直到今天&#xff09;&#xff0c;但是我将给出一些提示以显示如何使本文适用于较旧的技术。 在本文的结尾&#xff0c;您将找到要下载的源代码。 您…

使用Hibernate加载或保存图像-MySQL

本教程将引导您逐步了解如何使用Hibernate从数据库&#xff08; MySQL &#xff09;保存和加载图像。 要求 对于此示例项目&#xff0c;我们将使用&#xff1a; Eclipse IDE &#xff08;您可以使用自己喜欢的IDE&#xff09;&#xff1b; MySQL &#xff08;您可以使用任何…

javaweb回顾第四篇Servlet异常处理

前言&#xff1a;很多网站为了给用户很好的用户体验性&#xff0c;都会提供比较友好的异常界面&#xff0c;现在我们在来回顾一下Servlet中如何进行异常处理的。 1&#xff1a;声明式异常处理 什么是声明式&#xff1a;就是在web.xml中声明对各种异常的处理方法。 是通过<er…

java开发cs教程,日常运维(一)

w命令&#xff1a;用于查看系统负载、显示已经登陆系统的用户列表&#xff0c;并显示用户正在执行的指令等信息第一行从左面开始显示的信息依次为&#xff1a;时间&#xff0c;系统运行时间&#xff0c;登录用户数&#xff0c;平均负载。第二行开始以及下面所有的行&#xff0c…

coursera 《现代操作系统》 -- 第五周 同步机制(1)

临界区块&#xff08;Critical section&#xff09;指的是一个访问共用资源&#xff08;例如&#xff1a;共用设备或是共用存储器&#xff09;的程序片段&#xff0c;而这些共用资源有无法同时被多个线程访问的特性。&#xff08;不是字面意思的一个区域&#xff0c;是程序片段…

php进度条插件,分享8款优秀的 jQuery 加载动画和进度条插件_jquery

加载动画和进度条在网站和 Web 应用中的使用非常流行。虽然网速越来越快&#xff0c;但是我们的网站越来越复杂&#xff0c;同时用户对网站的使用体验的要求也越来越高。在内容加载缓慢的时候&#xff0c;使用时尚的加载动画和进度条告诉用户还有内容正在加载是一种非常好的方式…

卷积神经网络(CNN)与特殊的卷积

各种卷积操作的可视化的显示形式&#xff1a;GitHub - vdumoulin/conv_arithmetic: A technical report on convolution arithmetic in the context of deep learning1. fractionally-strided 卷积 如上图示&#xff0c;输入为 33 &#xff0c;想要卷积上采样成 55 的输出。需要…

MySQL安装步骤及相关问题解决

1. 下载MySQL Server&#xff0c;网址&#xff1a;http://dev.mysql.com/downloads/mysql/ 2. 点击MySQL5.5.21的安装文件&#xff0c;出现安装向导界面&#xff0c;单击“next”继续安装&#xff1a; 3. 选择接受协议&#xff0c;单击“next”继续安装&#xff1a; 4. 在出现选…

matlab的数学函数,matlab中常见数学函数的使用

matlab中常见数学函数的使用 MATLAB 基本知识 Matlab 的内部常数 pi 圆周率 exp(1) 自然对数的底数 e i 或 j 虚数单位 Inf 或 inf 无穷大 Matlab 的常用内部数学函数 指数函数 exp(x) 以 e 为底数 log(x) 自然对数&#xff0c;即以 e 为底数的对数 log10(x) 常用对数&#xff…

C++中 list与vector的区别

C中 list与vector的区别 引用http://www.cnblogs.com/shijingjing07/p/5587719.html C vector和list的区别 1.vector数据结构vector和数组类似&#xff0c;拥有一段连续的内存空间&#xff0c;并且起始地址不变。因此能高效的进行随机存取&#xff0c;时间复杂度为o(1);但因为内…

ActiveMQ网络连接器

这篇文章对我和任何对网络连接器如何为ActiveMQ工作感兴趣的ActiveMQ贡献者而言都是更多的内容。 我最近花了一些时间查看代码&#xff0c;并认为最好画一些快速的图表来帮助我记住我学到的东西&#xff0c;并在将来发现问题时帮助将来确定在哪里进行调试。 如果我输入有误&…

《程序设计与数据结构》第3周学习总结

学号 20162317 《程序设计与数据结构》第3周学习总结 教材学习内容总结 第三章的内容相比之前两章更为具体&#xff0c;介绍的内容更为集中&#xff0c;主要说到了类和对象的问题&#xff0c;其中也仔细介绍了String类、Random类、Math类、NumberFormat类等类。此外也说到了与类…

Java中带有JWebSocket的WebServerSocket

首先&#xff0c;转到http://jwebsocket.org/下载2个软件包Server and Client。 如果要查看源代码&#xff0c;请下载源代码包。 服务器 解压缩服务器程序包。 转到“ conf”文件夹 选择“ jWebSocket.xml”文件打开 编辑“ jWebSocket.xml”文件&#xff0c;在标签<dom…

OpenCV入门指南----人脸检测

本篇介绍图像处理与模式识别中最热门的一个领域——人脸检测&#xff08;人脸识别&#xff09;。人脸检测可以说是学术界的宠儿&#xff0c;在不少EI&#xff0c;SCI高级别论文都能看到它的身影。甚至很多高校学生的毕业设计都会涉及到人脸检测。当然人脸检测的巨大实用价值也让…

matlab提取艾里斑,艾里斑:我不是雀斑

正是艾里斑&#xff0c;限制了光学仪器的精度我们知道凸透镜能把入射光会聚到它的焦点上&#xff0c;由于透镜的口径有一定大小&#xff0c;限制了光线的传播&#xff0c;所以凸透镜也会发生衍射。这导致透镜无法把光线会聚成无限小的点&#xff0c;而只会在焦点上形成具有一定…