iOS如何自定义一个类似UITextView的本文编辑View

对于IOS涉及文本输入常用的两个View是UITextView和UITextField,一个用于复杂文本输入,一个用于简单文本输入,在大多数开发中涉及文本输入的场景使用这两个View能够满足需求。但是对于富文本编辑相关的开发,这两个View就无法满足自己业务的需要了。

在多数情况下,设计富文本开发的业务,大多需要根据键盘输入的内容根据业务场景对文本进行布局排版,这方面有个开源的富文本编辑View那就是YYText,网上也有很多对其源码分析的文章,这里就不过多介绍了,这个自定义的YYTextView堪称大神级,性能和效果都很不错,代码质量相当高,可以细细研读。但是这个代码相对比较复杂,对于研究如何自定义文本编辑View不太友好。然而苹果官方提供了一个简单版自定义文本编辑View的开源项目SimpleTextInput,这个简单易理解,下面就基于此说下如何自定义一个文本编辑的view

对于文本编辑而言,设计两个关键点:1、和键盘交互,当点击键盘上的按键时屏幕上如何显示;2、内容排版,屏幕上的内容如何布局显示。

对于第一点,iOS提供了两个相应协议:1、UIKeyInput简单的键盘交互

2、UITextInput复杂的键盘交互,可以看出UITextInput继承UIKeyInput,因此包含了UIKeyInput所有接口

UIKeyInput只能输入字符,因此提供的接口也简单,只有是否有文本内容,插入、删除三个接口

UITextInput用于输入点击键盘时屏幕不是显示最终内容时,比如输入汉字时,在输入拼音时,屏幕显示的是拼音字符有个蓝色底色,只有最终选中汉字时会将蓝色底色包围的拼音替换为汉字

对比只继承UIKeyInput的view继承UITextInput的view键盘多了红框选字的部分。

那么如何使用这两个协议呢,很简单,基于UIView创建一个自定义View子类,继承UIKeyInput或者UITextInput

@interface APLEditableCoreTextView : UIView <UITextInput> @end

为了使自定义的view能够接受输入需要重载方法canBecomeFirstResponder返回YES这样自定义view才能相应输入

如果是继承UIKeyInput就只需实现

@implementation APLEditableCoreTextView- (BOOL)canBecomeFirstResponder
{return YES;
}- (BOOL)hasText
{return (self.text.length != 0);
}- (void)insertText:(NSString *)text
{}- (void)deleteBackward
{}@end

这三个接口就可以了,当点自定义的view处于第一响应时,系统会触发hasText先判断是否有内容,当点击键盘字母时会调insertText:(NSString*)text方法,当点击键盘delete键会触发deleteBackward方法

如果是继承UITextInput需要实现的就比较多了,这个协议的@required下所有方法都必须实现再加上UIKeyInput的三个方法

/* Methods for manipulating text. */
// 根据给定的范围返回一个字符串,一般是屏幕上已有的内容取range范围内的子字符串
- (nullable NSString *)textInRange:(UITextRange *)range;
// 用给定的字符串替换给定范围部分,一般是屏幕上已有内容range范围内替换为text
- (void)replaceRange:(UITextRange *)range withText:(NSString *)text;/* Text may have a selection, either zero-length (a caret) or ranged.  Editing operations are* always performed on the text from this selection.  nil corresponds to no selection. */// 已选的范围
@property (nullable, readwrite, copy) UITextRange *selectedTextRange;/* If text can be selected, it can be marked. Marked text represents provisionally* inserted text that has yet to be confirmed by the user.  It requires unique visual* treatment in its display.  If there is any marked text, the selection, whether a* caret or an extended range, always resides within.** Setting marked text either replaces the existing marked text or, if none is present,* inserts it from the current selection. */ // 标记的文本范围,如果没有标记文本返回nil,标记文本可以理解为输入内容时,屏幕上蓝色包裹的字符串
@property (nullable, nonatomic, readonly) UITextRange *markedTextRange; // Nil if no marked text.
// 标记文本如何显示
@property (nullable, nonatomic, copy) NSDictionary<NSAttributedStringKey, id> *markedTextStyle; // Describes how the marked text should be drawn.
// 用给定范围设置标记文本内容
- (void)setMarkedText:(nullable NSString *)markedText selectedRange:(NSRange)selectedRange; // selectedRange is a range within the markedText
// 解除标记文本,可以理解为用键盘内容替换蓝色包裹部分,比如选中键盘上汉字替换为屏幕上拼音时,拼音就是要解除的标记文本
- (void)unmarkText;/* The end and beginning of the the text document. */
// 文本开始位置,一般是0
@property (nonatomic, readonly) UITextPosition *beginningOfDocument;
// 文本结束位置,一般是屏幕文本长度
@property (nonatomic, readonly) UITextPosition *endOfDocument;/* Methods for creating ranges and positions. */
// 用给定的开始和结束位置,返回一个range
- (nullable UITextRange *)textRangeFromPosition:(UITextPosition *)fromPosition toPosition:(UITextPosition *)toPosition;
// 用给定位置和偏移量返回一个新的位置
- (nullable UITextPosition *)positionFromPosition:(UITextPosition *)position offset:(NSInteger)offset;
// 用给定的位置、方向和偏移量返回一个新的位置
- (nullable UITextPosition *)positionFromPosition:(UITextPosition *)position inDirection:(UITextLayoutDirection)direction offset:(NSInteger)offset;/* Simple evaluation of positions */
// 比较两个位置关系
- (NSComparisonResult)comparePosition:(UITextPosition *)position toPosition:(UITextPosition *)other;
// 返回给定起始位置和结束位置的偏移量
- (NSInteger)offsetFromPosition:(UITextPosition *)from toPosition:(UITextPosition *)toPosition;/* A system-provided input delegate is assigned when the system is interested in input changes. */
// 输入代理,这个代理不需要自己继承UITextInputDelegate协议定义一个类,系统会给其分配一个实例的
@property (nullable, nonatomic, weak) id <UITextInputDelegate> inputDelegate;/* A tokenizer must be provided to inform the text input system about text units of varying granularity. */
// 分词器,需要给其创建,如果没有特殊需求直接使用UITextInputStringTokenizer就可以了
@property (nonatomic, readonly) id <UITextInputTokenizer> tokenizer;/* Layout questions. */
// 根据给定的布局范围和方向返回一个最远的新位置
- (nullable UITextPosition *)positionWithinRange:(UITextRange *)range farthestInDirection:(UITextLayoutDirection)direction;
// 根据给定的位置和方向返回一个字符的范围
- (nullable UITextRange *)characterRangeByExtendingPosition:(UITextPosition *)position inDirection:(UITextLayoutDirection)direction;/* Writing direction */
// 根据给的位置和记录方向返回书写方向
- (NSWritingDirection)baseWritingDirectionForPosition:(UITextPosition *)position inDirection:(UITextStorageDirection)direction;
// 设置指定范围内的指定的书写方向
- (void)setBaseWritingDirection:(NSWritingDirection)writingDirection forRange:(UITextRange *)range;/* Geometry used to provide, for example, a correction rect. */
// 返回给定范围内的rect
- (CGRect)firstRectForRange:(UITextRange *)range;
// 返回给定位置的光标rect,一般输入框都会有个闪烁的竖线,这个竖线其实是个矩形,为了能让这个竖线在文本间显示且不与文字重叠,需要给字与字之间留够空隙,这个空隙是文字布局时做到,但是空隙大小是通过这个方法得到的
- (CGRect)caretRectForPosition:(UITextPosition *)position;
// 返回给定范围内的选区,这是个数组,因为选区内可能有个多个rect
- (NSArray<UITextSelectionRect *> *)selectionRectsForRange:(UITextRange *)range API_AVAILABLE(ios(6.0));       // Returns an array of UITextSelectionRects/* Hit testing. */
// 返回给定点的最近位置
- (nullable UITextPosition *)closestPositionToPoint:(CGPoint)point;
// 返回给定范围内指定点的最近位置
- (nullable UITextPosition *)closestPositionToPoint:(CGPoint)point withinRange:(UITextRange *)range;
// 返回给定点出的文本范围,一般是用于分词,比如在“今天天气不错”这个内容的“气”字位置处可能想要的的是“天气”这个词的范围
- (nullable UITextRange *)characterRangeAtPoint:(CGPoint)point;

这些方法的触发都是在与键盘交互时相应的。其中UITextInput有个属性inputDelegate,这个不需要设置,由系统设置分配的UIKeyboardImpl实例

通过调用堆栈可以看出,当设置view为第一响应者时,会给inputDelegate设置一UIKeyboardImpl的实例

其中tokenizer属性需要给其分配实例,self.tokenizer = [[UITextInputStringTokenizer alloc] initWithTextInput:self];用户给键盘使用,实际view中并不怎么用这个属性

以上就是关于和键盘交互的所有说明,记住UIKeyInput/UITextInput的接口都是有键盘触发的,可以在这些接口实现断点调试看下堆栈,比如hasText,当点击键盘时会立即触发

再比如markedTextRange

等等,通过堆栈发现,执行UITextInput的接口时,都是通过UIKeyboardImpl这个实例最终执行到UITextInput的接口实现,这个UIKeyboardImpl就是inputDelegate的实例,这个UITextInputDelegate协议有四个接口

我们在selectedTextRange方法处断点调试,通过堆栈可以看出,这个方法就是inputDelegate执行selectionDidChange最终执行了selectedTextRange

最后我们看下UITextInput中涉及到的两个类UITextPosition和UITextRange

@interface UITextPosition : NSObject@end@interface UITextRange : NSObject@property (nonatomic, readonly, getter=isEmpty) BOOL empty;     //  Whether the range is zero-length.
@property (nonatomic, readonly) UITextPosition *start;
@property (nonatomic, readonly) UITextPosition *end;@end

通过定义可以看出,UITextPosition是个空类,没有任何变量和属性,通过类名可以知道这是个定义位置的类,但是并没有变量和属性如何知道位置的值呢。同时UITextRange是一个选区类,有个属性empty显然是为了说明选区是否为空,但是并没有实现,这两个类都只给了定义并没有实现。因此需要开发者继承这两个类自定义UITextPosition和UITextRange。

@interface APLIndexedPosition : UITextPosition@property (nonatomic) NSUInteger index;
@property (nonatomic) id <UITextInputDelegate> inputDelegate;+ (instancetype)positionWithIndex:(NSUInteger)index;@end@implementation APLIndexedPosition#pragma mark IndexedPosition implementation// Class method to create an instance with a given integer index.
+ (instancetype)positionWithIndex:(NSUInteger)index
{APLIndexedPosition *indexedPosition = [[self alloc] init];indexedPosition.index = index;return indexedPosition;
}@end
@interface APLIndexedRange : UITextRange@property (nonatomic) NSRange range;
+ (instancetype)indexedRangeWithRange:(NSRange)range;@end@implementation APLIndexedRange// Class method to create an instance with a given range
+ (instancetype)indexedRangeWithRange:(NSRange)range
{if (range.location == NSNotFound) {return nil;}APLIndexedRange *indexedRange = [[self alloc] init];indexedRange.range = range;return indexedRange;
}// UITextRange read-only property - returns start index of range.
- (UITextPosition *)start
{return [APLIndexedPosition positionWithIndex:self.range.location];
}// UITextRange read-only property - returns end index of range.
- (UITextPosition *)end
{return [APLIndexedPosition positionWithIndex:(self.range.location + self.range.length)];
}// UITextRange read-only property - returns YES if range is zero length.
-(BOOL)isEmpty
{return (self.range.length == 0);
}

这就是在selectedTextRange方法中返回的是APLIndexedRange的原因

以上是如何键盘交互,对于已经在屏幕上的内容,如何布局排版显示,涉及文字绘制,可以参考官方文档Core Text这是一个纯C语言的较为底层的文本排版渲染的框架,其中UITextInput中- (CGRect)caretRectForPosition:(UITextPosition *)position接口就涉及core text相关方法来计算字间距,这里就不对此进行详细阐述了,网上对core text详解有很多,而对UITextInput的使用较少,这里对此做详细说明

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

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

相关文章

Android 使用 Canvas 和 Paint 实现圆角图片

学习笔记 效果展示: 全部代码: public class YuanActivity extends AppCompatActivity {private ActivityYuanBinding binding;Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 通过 DataBinding 获取布局文件binding …

Java设计模式 —— 【创建型模式】建造者模式详解

文章目录 一、建造者模式二、案例实现三、优缺点四、模式拓展五、对比1、工厂方法模式VS建造者模式2、抽象工厂模式VS建造者模式 一、建造者模式 建造者模式&#xff08;Builder Pattern&#xff09; 又叫生成器模式&#xff0c;是一种对象构建模式。它可以将复杂对象的建造过…

泷羽sec学习打卡-brupsuite4

声明 学习视频来自B站UP主 泷羽sec,如涉及侵权马上删除文章 笔记的只是方便各位师傅学习知识,以下网站只涉及学习内容,其他的都 与本人无关,切莫逾越法律红线,否则后果自负 关于brupsuite的那些事儿-proxy proxyInterceptHTTP history/WebSocket history&#xff08;历史记录&a…

redis数据结构和内部编码及单线程架构

博主主页: 码农派大星. 数据结构专栏:Java数据结构 数据库专栏:数据库 JavaEE专栏:JavaEE 软件测试专栏:软件测试 关注博主带你了解更多知识 1. 数据结构和内部编码 Redis会在合适的场景选择合适的内部编码 我们可以通过objectencoding命令查询内部编码 : 2. 单线程架构 …

旅游管理系统的设计与实现

文末获取源码和万字论文&#xff0c;制作不易&#xff0c;感谢点赞支持。 毕 业 设 计&#xff08;论 文&#xff09; 题目&#xff1a;旅游管理系统的设计与实现 摘 要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#…

AIGC 与艺术创作:机遇

目录 一.AIGC 的崛起与艺术领域的变革 二.AIGC 在不同艺术形式中的应用 1.绘画与视觉艺术 2.音乐创作 三.AIGC 为艺术创作带来的机遇 1.激发创意灵感 2.提高创作效率 总结 在当今数字化时代&#xff0c;人工智能生成内容&#xff08;AIGC&#xff09;正以惊人的速度重塑…

Linux下的编程

实验7 Linux下的编程 一、实验目的 熟练掌握Linux下Python编程的方法、函数调用方法以及shell编程中的控制结构。 二、实验环境 硬件&#xff1a;PC电脑一台&#xff0c;网络正常。 配置&#xff1a;win10系统&#xff0c;内存大于8G &#xff0c;硬盘500G及以上。 软件&a…

「Mac畅玩鸿蒙与硬件43」UI互动应用篇20 - 闪烁按钮效果

本篇将带你实现一个带有闪烁动画的按钮交互效果。通过动态改变按钮颜色&#xff0c;用户可以在视觉上感受到按钮的闪烁效果&#xff0c;提升界面互动体验。 关键词 UI互动应用闪烁动画动态按钮状态管理用户交互 一、功能说明 闪烁按钮效果应用实现了一个动态交互功能&#xf…

智能安全新时代:大语言模型与智能体在网络安全中的革命性应用

一、引言 随着信息技术的飞速发展&#xff0c;网络安全问题日益严重&#xff0c;成为各行各业面临的重大挑战。传统的安全防护措施已难以应对日益复杂的网络威胁&#xff0c;人工智能&#xff08;AI&#xff09;技术的引入为网络安全带来了新的希望。特别是大语言模型&#xff…

深度学习(2)前向传播与反向传播

这一次我们重点讲解前向传播与反向传播&#xff0c;对这里还是有点糊涂 前向传播&#xff08;Forward Propagation&#xff09;和反向传播&#xff08;Backward Propagation&#xff09;是深度学习中神经网络训练的核心过程。它们分别负责计算神经网络的输出以及更新神经网络的…

数据结构——图(遍历,最小生成树,最短路径)

目录 一.图的基本概念 二.图的存储结构 1.邻接矩阵 2.邻接表 三.图的遍历 1.图的广度优先遍历 2.图的深度优先遍历 四.最小生成树 1.Kruskal算法 2.Prim算法 五.最短路径 1.单源最短路径--Dijkstra算法 2.单源最短路径--Bellman-Ford算法 3.多源最短路径--Floyd-…

SSM报错:表现层方法应该返回字符串,但是返回页面

在进行SSM项目时&#xff0c;后端表现层应该返回给前端字符串&#xff0c;但是却跳转页面 1.首先检查是否使用ResponseBody注解 ResponseBody注解 作用&#xff1a;将java对象转为json格式的数据。将controller的方法返回的对象通过适当的转换器转换为指定的格式之后&#xff0…

【openGL入门(一)】

openGL入门&#xff08;一&#xff09; OpenGL(Open Graphics Library)GLAD & GLFW通过代码片段滤清流程1. 画面绘制基础2.VAO&#xff0c;VBO&#xff0c;EBO(1) VAO : Vertex Array Object(2) VBO: Vertex Buffer ObjectEBO&#xff1a;Element Buffer Object 总结 OpenG…

DAY2 C++基础补充

作业1&#xff1a; 已知一个数组table,用宏定义求出数组元素的个数。 #define NUM sizeof(table)/sizeof(table[0]) 作业2&#xff1a; 嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a&#xff0c;写两段代码&#xff0c;第一个设置a的bit3&#xff0c;第…

图像滤波和卷积的不同及MATLAB应用实例

滤波与卷积在图像处理中都是非常重要的运算&#xff0c;但它们有着明显的区别。以下是滤波与卷积的主要不同点&#xff0c;并附带一个MATLAB实例来展示两者在图像处理中的效果差异。 一、滤波与卷积的不同 定义与目的&#xff1a; 1&#xff09;滤波&#xff1a;滤波是一种信…

SpringBoot整合knife4j,以及会遇到的一些bug

这篇文章主要讲解了“Spring Boot集成接口管理工具Knife4j怎么用”&#xff0c;文中的讲解内容简单清晰&#xff0c;易于学习与理解&#xff0c;下面请大家跟着小编的思路慢慢深入&#xff0c;一起来研究和学习“Spring Boot集成接口管理工具Knife4j怎么用”吧&#xff01; 一…

回归任务与分类任务应用及评价指标

能源系统中的回归任务与分类任务应用及评价指标 一、回归任务应用1.1 能源系统中的回归任务应用1.1.1 能源消耗预测1.1.2 负荷预测1.1.3 电池健康状态估计&#xff08;SOH预测&#xff09;1.1.4 太阳能发电量预测1.1.5 风能发电量预测 1.2 回归任务中的评价指标1.2.1 RMSE&…

在Ubuntu上使用IntelliJ IDEA:开启你的Java开发之旅!

你好&#xff0c;年轻的学徒&#xff01;&#x1f9d1;‍&#x1f4bb; 是时候踏上进入Java开发世界的史诗之旅了&#xff0c;我们的得力助手将是强大的IntelliJ IDEA。准备好了吗&#xff1f;出发吧&#xff01; 在我们开始之前&#xff0c;我们需要下载这个工具。但是&#…

Burp入门(7)-APP抓包

声明&#xff1a;学习视频来自b站up主 泷羽sec&#xff0c;如涉及侵权马上删除文章 感谢泷羽sec 团队的教学 视频地址&#xff1a;burpsuite(5)web网页端抓包与app渗透测试_哔哩哔哩_bilibili 本文详细介绍如何使用burp suite去抓取APP的数据包。 一、安装 Burp 的 CA 证书 1…

【AI+网络/仿真数据集】1分钟搭建云原生端到端5G网络

导语&#xff1a; 近期智慧网络开放创新平台上线了端到端网络仿真能力&#xff0c;区别于传统的网络仿真工具需要复杂的领域知识可界面操作&#xff0c;该平台的网络仿真能力主打一个小白友好和功能专业。 https://jiutian.10086.cn/open/​jiutian.10086.cn/open/ 端到端仿…