iOS使用CoreText完成txt阅读器

 CoreText是一个高效处理字符和字形转换和进行文字排版的框架,API基于C语言。

常见的CoreText类介绍

(1)、CFAttributedStringRef
属性字符串,用于存储需要绘制的文字字符和字符属性

(2)、CTFramesetterRef
framesetter对应的类型是 CTFramesetter,通过CFAttributedStringRef进行初始化,它作为CTFrame对象的生产工厂,负责根据path生产对应的CTFrame;

(3)、CTFrame
CTFrame是可以通过CTFrameDraw函数直接绘制到context上的,当然你可以在绘制之前,操作CTFrame中的CTLine,进行一些参数的微调;

(3)、CTLine
在CTFrame内部是由多个CTLine来组成的,每个CTLine代表一行;可以看做Core Text绘制中的一行的对象 通过它可以获得当前行的line ascent,line descent ,line leading,还可以获得Line下的所有Glyph Runs;

(4)、CTRun
或者叫做 Glyph Run,每个CTLine又是由多个CTRun组成的,每个CTRun代表一组显示风格一致的文本,是一组共享想相同attributes(属性)的字形的集合体;

渲染流程

  1. 当我们需要排版时,可以对字符串设置各种格式,生成NSAttributeString;
  2. 然后用NSAttributeString去创建CTFramesetter类,
  3. CTFramesetter会处理排版信息,然后生成排版后的结果CTFrame;
  4. CTFrame是一段或者多段文本,每段文本又由多行文字组成,每行的表示为CTLine;
  5. CTLine是一行文本,每行文本由多个CTRun组成,CTRun是一小段连续的字形;
  6. CTTypeSetter负责上下文相关排版处理,比如说换行,每个CTFrame中都会有一个CTTypeSetter; 他们之间的关系图如下:

总的来说,CTFramesetter是生成CTFrame的工厂类,初始化参数是attributed string,会在内部创建CTTypesetter并进行实际的排版;

CTLine类似每一行的文字,CTRun是一行中具有相同属性的连续字形,比如说“我正在分享阅读器”,就会由三个CTRun组成,分别是“我正在”、“分享”、“阅读器”(因为“分享”两个字加粗了,否则就会是一个CTRun)。

 CoreText的使用流程:

  1. 使用core text就是有一个要显示的string,
  2. 然后定义这个string每个部分的样式生成富文本attributedString
  3. 由富文本生成 CTFramesetter
  4. CTFramesetter得到CTFrame
  5. 使用绘制(CTFrameDraw)CTFrame 

关键函数介绍

由富文本字符串得到CTFramesetter

  • CTFramesetterCreateWithAttributedString(att as CFAttributedString)
  • CFAttributedString是NSAttributedString的CF对象,可以直接强转;

CTFramesetterRef CTFramesetterCreateWithAttributedString( CFAttributedStringRef string );  

CTFramesetter包含了富文本字符串的布局信息和相关属性,供后续的绘制操作使用。最主要的作用就是生成下面的CTFrame。

通过调用 CTFramesetterCreateWithAttributedString 函数,可以将富文本字符串转换为 Core Text 的布局对象,为后续的绘制操作提供所需的文本排版和属性信息。这样,你就可以使用 Core Text 提供的更多功能来自定义文本的布局、字体、颜色等,并实现高度定制化的文本渲染效果。

CTFramesetterRef 对象并不直接进行绘制操作,它只包含了文本布局的信息。要将文本绘制到图形上下文中,还需要使用 CTFrameDraw 函数创建并绘制 CTFrameRef 对象。

生成CTFrame

  • CTFramesetterCreateFrame(framesetter, CFRangeMake(pageStart, 0), path, nil)

使用 CTFramesetterRef 对象、文本范围、路径和其他参数创建一个 CTFrameRef 对象,

CTFrame是排版数据,可直接通过重写View的drawRect方法渲染到页面上

  • framesetter:上面创建的CTFramesetterRef
  • stringRange:要使用的文本范围,即 CFRange 结构体。
    • 可以通过设置 CFRangeMake 参数来确定要使用的富文本字符串的起始位置和长度
    • 如果范围的长度部分设置为0,比如CFRangeMake(location, 0),则会尽可能的填满CTFrame,将继续添加行,直到文本或空间用完。
  • path:绘制文本的路径,即 CGPathRef 类型对象。
    • 路径定义了文本应该在画布上的布局方式和区域。
    • 一般传渲染View的bounds即可
  • frameAttributes:可选的附加属性字典,提供额外的布局控制和属性设置。

计算分页

  • CTFrameGetVisibleStringRange(frame)

CTFrameGetVisibleStringRange 函数的作用是获取给定文本框架(CTFrame)中可见的文本范围。可见的范围是指在当前文本框架大小和路径下实际可见的文本部分。

返回值: CTFrameGetVisibleStringRange 函数返回一个 CFRange 结构体,表示给定文本框架中可见的文本范围。该范围包括起始位置(location)和长度(length)信息。

比如原文有1W字,当前的frame只能显示200字,那么返回的Range就是(0,200),下一页在从200的基础上进行计算,比如第二页算出为(200,430),在下一页就从430开始计算,如此循环就可计算出这1W字需要分多少页,并且每页内容的CTFrame都已生成。

通过上面的介绍,把这几个函数连起来,就是数据准备阶段的核心方法:

  1. 根据txt内容生成String -> 在由String生成富文本-> 由富文本生成framesetter,
  2. 根据页面大小计算生成单页的CTFrame
  3. CTFrame获取当前Frame有效的文字显示范围,下一页的location累加,循环计算分页,保存得到每页的内容范围和每页的CTFrame
func createCTFrame(contentStr: String) {let range = NSMakeRange(0, contentStr.count)let att = NSMutableAttributedString(string: contentStr)att.addAttribute(.foregroundColor, value: UIColor.lightGray, range: range)att.addAttribute(.font, value: UIFont.systemFont(ofSize: 22), range: range)let framesetter = CTFramesetterCreateWithAttributedString(att as CFAttributedString)let path = CGPath(rect: self.readView.bounds, transform: nil)var pageStart = 0var frameArray: [CTFrame] = []var i: Int = 0repeat {let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(pageStart, 0), path, nil)let pageRange = CTFrameGetVisibleStringRange(frame)let beginIndex = contentStr.index(contentStr.startIndex, offsetBy: pageRange.location)let endIndex = contentStr.index(beginIndex, offsetBy: pageRange.length)let onePage = String(contentStr[beginIndex..<endIndex])pageStart = pageRange.location + pageRange.lengthprint("第\(i)页" ,pageRange, onePage)i+=1frameArray.append(frame)} while(pageStart < contentStr.count )self.frameArray = frameArray}

渲染方法

  • CTFrameDraw(frame, ctx)

渲染的核心方法,CTFrameDraw 方法的作用是将指定的文本框架对象绘制到图形上下文中,实现文本的可视化呈现。

具体做法:在继承UIView的子类中,重写drawrect方法,里面最重要的一行就是CTFrameDraw(frame, ctx),即可完成渲染:

/// 绘制
override func draw(_ rect: CGRect) {guard let frame = frameRef, let ctx = UIGraphicsGetCurrentContext() else {return}ctx.textMatrix = CGAffineTransform.identityctx.translateBy(x: 0, y: bounds.size.height)ctx.scaleBy(x: 1.0, y: -1.0)CTFrameDraw(frame, ctx)
}

除了 CTFrameDraw , 还想要对文本内容有更精细的控制,可以使用CTLineDraw,CTRunDraw

  • void CTLineDraw(CTLineRef line,CGContextRef context )

  • void CTRunDraw( CTRunRef run, CGContextRef context,CFRange range ) 

CTLineDraw 函数绘制的是单行文本,需要在CGContext中设置好position,在图文混排时,可以用到。

CTRunDraw 函数绘制的是单个文本运行,YYText使用的渲染方法就是CTRunDraw,对于控制特别精细的可以但是CTRun控制渲染。

以下是在学习的过程中找到的资料:

CoreText的基础知识了解:

CoreText实战讲解,手把手教你实现图文、点击高亮、自定义截断功能 - 简书

文字排版入门—— 排版基础、CoreText和图文混排-腾讯云开发者社区-腾讯云

iOS 基于CoreText的排版引擎 - 简书

比较完整的txt阅读器demo:

iOS: .txt 小说阅读器功能开发的 5 个老套路 - 掘金

套路继续, .txt 小说阅读器功能开发 - 掘金

最简版demo: 使用coretext计算分页并渲染,上面demo的功能多,导致核心逻辑淹没在业务代码中,找起来麻烦,所以做了一个只展示核心原理的最简demo : 

博客园系列文章:

https://www.cnblogs.com/summer-blog/p/6030641.html

https://www.cnblogs.com/summer-blog/p/6030885.html

https://www.cnblogs.com/summer-blog/p/6044118.html

https://www.cnblogs.com/summer-blog/p/6402664.html

比较精细的阅读器思路,页面行高重排,目前我们还用不到

我在七猫做阅读器——排版篇

从基础的各种CoreText渲染,到页面之间切换动画都有独立的demo,最后有一个把CoreText渲染+页面切换集成在一起的demo 

小说阅读器的设计和实现 - 简书

阅读器多种翻页的设计与实现 - 简书 

GitHub - loyinglin/LearnCoreText

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

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

相关文章

【原创】录剪视频的折腾之路

制作视频的起因 本人为IT男&#xff0c;IT发展快&#xff0c;需要学习的东西又多。往往为了一个技术小问题&#xff0c;花好几天时间学习&#xff0c;接下来十来分钟把事情做完。下次遇到这个同样的问题的时候&#xff0c;可能是几个月后&#xff0c;甚至是几年以后了。这些技…

微信小程序页面跳转方法

文章目录 前言方式一&#xff1a;wx.navigateTo方式二&#xff1a;wx.redirectTo方式三&#xff1a;wx.reLaunch方式四&#xff1a;wx.switchTab方式五&#xff1a;wxml中navigator标签跳转页面回退 前言 微信小程序页面跳转的各种方法总结&#xff0c;备查。 方式一&#xff…

汽车连接器

汽车连接器 电子元器件百科 文章目录 汽车连接器前言一、汽车连接器是什么二、汽车连接器的类别三、汽车连接器的应用实例四、汽车连接器的作用原理总结前言 汽车连接器通常需要具备防水、防尘、耐高温等特性,以适应汽车恶劣的工作环境。它们的设计和连接方式也各不相同,以适…

JVM 内存分析工具 Memory Analyzer Tool(MAT)的深度讲解

目录 一. 前言 二. MAT 使用场景及主要解决问题 三. MAT 基础概念 3.1. Heap Dump 3.2. Shallow Heap 3.3. Retained Set 3.4. Retained Heap 3.5. Dominator Tree 3.6. OQL 3.7. references 四. MAT 功能概述 4.1. 内存分布 4.2. 对象间依赖 4.3. 对象状态 4.4…

鸿蒙前端开发-构建第一个ArkTS应用(Stage模型)

创建ArkTS工程 若首次打开DevEco Studio&#xff0c;请点击Create Project创建工程。如果已经打开了一个工程&#xff0c;请在菜单栏选择File > New > Create Project来创建一个新工程。 选择Application应用开发&#xff08;本文以应用开发为例&#xff0c;Atomic Serv…

docker-compose安装教程

1.确认docker-compose是否安装 docker-compose -v如上图所示表示未安装&#xff0c;需要安装。 如上图所示表示已经安装&#xff0c;不需要再安装&#xff0c;如果觉得版本低想升级&#xff0c;也可以继续安装。 2.离线安装 下载docker-compose安装包&#xff0c;上传到服务…

uniapp小程序分享为灰色

引用&#xff1a;https://www.cnblogs.com/panwudi/p/17074172.html uniapp开发的微信小程序&#xff0c;没有转发&#xff0c;分享&#xff1a; 创建一个mixin:common/share.js export default {onShareAppMessage(res) { //发送给朋友return {}},onShareTimeline(res) {//…

人工智能原理复习--机器学习

文章目录 上一篇机器学习概述归纳(示例)学习ID3决策树算法K近邻算法下一篇 上一篇 人工智能原理复习–搜索策略&#xff08;二&#xff09; 机器学习概述 学习系统的基本结构&#xff1a; #mermaid-svg-JMjIZHjVOirLolvu {font-family:"trebuchet ms",verdana,ari…

辨析旅行商问题(TSP)与车辆路径问题(VRP)

目录 前言旅行商问题 (TSP)问题介绍数学模型符号定义问题输入约束条件目标函数问题输出 解的空间解空间大小计算解释 车辆路径问题 (VRP)问题介绍TSP到VRP的过渡数学模型符号定义问题输入约束条件优化目标问题输出 解空间特殊情况一般情况 TSP 与 VRP 对比 前言 计划是通过本文…

基于JavaWeb+SSM+Vue助农扶贫微信小程序系统的设计和实现

基于JavaWebSSMVue助农扶贫微信小程序系统的设计和实现 源码获取入口Lun文目录前言主要技术系统设计功能截图 源码获取入口 Lun文目录 目 录 第一章 绪论 1 1.1 研究背景 1 1.2 研究意义 1 1.3 研究内容 2 第二章 开发环境与技术 3 2.1 JSP技术 3 2.2 MySQL数据库 3 2.3 Java…

基于Solr的全文检索系统的实现与应用

文章目录 一、概念1、什么是Solr2、与Lucene的比较区别1&#xff09;Lucene2&#xff09;Solr 二、Solr的安装与配置1、Solr的下载2、Solr的文件夹结构3、运行环境4、Solr整合tomcat1&#xff09;Solr Home与SolrCore2&#xff09;整合步骤 5、Solr管理后台1&#xff09;Dashbo…

4-Docker命令之docker commit

1.docker commit介绍 docker commit命令是用于根据docker容器的改变创建一个新的docker镜像 2.docker commit用法 docker commit [参数] container [repository[:tag]] [rootcentos79 ~]# docker commit --helpUsage: docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG…

微服务学习:Nacos配置中心

先打开Nacos&#xff08;详见微服务学习&#xff1a;Nacos微服务架构中的服务注册、服务发现和动态配置&Nacos下载&#xff09; 1.环境隔离&#xff1a; 新建命名空间&#xff1a; 记住命名空间ID&#xff1a; c82496fb-237f-47f7-91ed-288a53a63324 再配置 就可达成环…

vue3 创建过程中 运行npm create vue@latest 和 npm install卡住不动的解决方法之一

问题&#xff1a;npm create vuelatest、和npm install 不管是电脑cmd上还是vscode终端上都是卡很久或不动&#xff01; 解决&#xff1a; 1、查看npm代理 npm config get registry2、更换npm镜像 npm config set registryhttps://registry.npmmirror.com这里换成淘宝源好像…

学习 Vue 3 源码

Vue 3 是一款流行的前端框架&#xff0c;它的数据代理和虚拟 DOM 实现是其核心功能之一 Vue 3 的数据代理 在 Vue 3 中&#xff0c;数据代理是指将组件实例的属性代理到其内部状态对象上。这使得开发者可以使用更便捷的方式来访问和修改组件的状态。 Vue 3 的数据代理实现主…

docker-centos中基于keepalived+niginx模拟主从热备完整过程

文章目录 一、环境准备二、主机1、环境搭建1.1 镜像拉取1.2 创建网桥1.3 启动容器1.4 配置镜像源1.5 下载工具包1.6 下载keepalived1.7 下载nginx 2、配置2.1 配置keepalived2.2 配置nginx2.2.1 查看nginx.conf2.2.2 修改index.html 3、启动3.1 启动nginx3.2 启动keepalived 4、…

【HarmonyOS开发】控件开发过程中,知识点记录

1、问题记录及解决方案 1.1 资源&#xff08;Icon&i18n&#xff09;问题 控件&#xff1a;只有一个JS文件&#xff0c;不会将任何资源型文件&#xff08;图片、字体、默认文字等&#xff09;打包到SO中。因此&#xff0c;当我们开发控件时&#xff0c;需要将需要使用到的资…

【机器学习】042_迁移学习

一、概述、定义 目的&#xff1a; 迁移学习的目的是将某个领域或任务上学习到的模式、知识应用到不同但相关的领域里&#xff0c;获取更多数据&#xff0c;而不必投入许多时间人力来进行数据的标注。 举例&#xff1a; 已经会下中国象棋&#xff0c;就可以类比着来学习国际…

Java单元测试:JUnit和Mockito的使用指南

引言&#xff1a; 在软件开发过程中&#xff0c;单元测试是一项非常重要的工作。通过单元测试&#xff0c;我们可以验证代码的正确性、稳定性和可维护性&#xff0c;帮助我们提高代码质量和开发效率。本文将介绍Java中两个常用的单元测试框架&#xff1a;JUnit和Mockito&#x…

Navicat连接Oracle数据库

Navicat连接Oracle数据库 打开服务里面找到Oracle服务 OracleServerXE或者OracleServerTTL 创建数据库连接 连接名默认自己起 主机选择本地 端口默认 服务名在服务中可以找到输入后缀 用户名默认都是system 密码是创建oracle时候填写的口令 点击测试连接即可