第二篇 编辑器控件

功能描述

        这个控件能够以Markdown文件格式约定,把文件呈现给用户,并提供交互操作。

        这个最核心的是接受用户输入,在用户点击时能够调起输入法,并和输入法有互动。实际上,这篇文档主要描述UITextInput协议的相关细节。


UITextInput协议概述

        这个协议就是和系统输入法的交互协议,系统控件UITextField和UITextView等都实现了这个协议。

        实现该协议的控件,应该在内部维护一个或一组String,且一般来说,应该是UIView的一个子类,我们需要实现绘制,按我们目的渲染String。


UITextPosition和UITextRange

        这两个类分别标记了位置和区间,默认的区间就是两个位置组成的(看UITextRange)的定义就能发现(过度的抽象会导致扩展的难度,这里的抽象,不是指对现实增加一个抽象层,而是指对现实增加一个条件假设的抽象)。因为在UITextInput协议的方法直接,需要传输位置和区间概念。例如,输入法(或者说是系统)需要获取指定区间的String,这时就会给我们一个UITextRange的子类实例。

        这个协议里所有这两个实例的传入参数,都是这个协议里,其他方法返回给系统的。借此说明几个方法(函数或者怎么翻译?)的意义:

        1、func textRange(fromfromPosition:UITextPosition, totoPosition:UITextPosition) ->UITextRange?
            这个方法就是有两个位置生成一个区间的方法。fromfromPosition和totoPosition分别是区间的起止位置。

        2、func compare(_ position:UITextPosition, toother:UITextPosition) ->ComparisonResult

            这个方法用来比较两个位置的相对关系。这里计算的是一维维度下的位置关系

            需要注意的是两个UITextPostion实例的位置关系有两个维度,分别为一维维度和二维维度。

            1)、一维维度

                在一维维度没有行的概念,认为一行的行尾在下一行的行首前,并且紧邻(这里我没有研究过阿拉伯语等从右向左书写的语言的实际情况)

            2)、二维维度

                二维维度,就是平时的文本文档的位置关系,及两个位置之间有水平关系和垂直关系两种。一般情况下,垂直关系上使用间隔的行数(通常为响应键盘的方向键上或下一次进行调整的单位量),水平关系上使用间隔的字符数(通常为响应键盘的方向键左或右,这里需要注意ejimo表情等字符串,我们要当成一个字符间隔,否则很难想象光标移动到表情中间的样子……)

        3、func position(fromposition:UITextPosition, offset:Int) ->UITextPosition?

            计算一个位置在特定偏移后的一维维度的新位置。需要注意的一个细节是offset的正负表示了移动的方向。

        4、func position(fromposition:UITextPosition, indirection:UITextLayoutDirection, offset:Int) ->UITextPosition?

            计算一个位置在特定偏移后的二维维度的新位置。需要注意offset为负时,对indirection的影响。

            这个方法的实现代码上,我在水平方向上从用了上一个方法的实现。当然,也可以反过来,实现这个方法,然后上一个方法调用这个方法的实现。我没有选择这个方法的原因就是这样导致这个方法太重。

            从实现层面上,选择那种方案主要取决于一个问题?当光标在行首时,再次按左键,光标去上一行行首?本行行首?还是哪里?我选择去上一行行首,所以逻辑层面,水平方向上就是一维维度关系。

        和位置、区间相关的方法其他就不详细解释啦。

        PS:我在实现上强制了书写方向为从左到右,这可能导致我对部分的理解不全面。


selectedTextRange属性

        前面说过,该协议的实现类需要维护一个或一组String,我们要在用户面前呈现出来,并且响应用户交互。这个响应,一个重要的功能就是用户选中,而selectedTextRange表示的就是用户选中的区域。

        PS:系统通过func text(inrange:UITextRange) ->String?活动特定区域的文本,并不是只有选中。

        对于这个属性,需要特别注意一点,这是一个读写属性,系统是能修改的(这个协议所有返回UITextRange实例的方法的返回值,都有可能是这个属性被修改为的目标值)

        在现实上,selectedTextRange标识的String要和其他字符串有视觉上的差别,否则用户找不到选中呀。


markedTextRange属性

        系统输入法(中文)输入使用,这个一定要实现呀。

        这个属性的含义是这个区间的文本是输入法在输入过程中的添加或修改的。注意:这是一个只读属性,系统不会之间修改这个属性,但系统确实需要修改这个属性,故通过下面两个方法完成:

        1、func setMarkedText(_ markedText:String?, selectedRange:NSRange)

            这个方法表示修改markedTextRange属性的文本为markedText。

            我的实现逻辑为(只根据条件执行一条)

            1)、如果markedTextRange属性有效、则把markedTextRange区间的内容修改为markedText,并把markedTextRange的值同步给selectedTextRange属性

            2)、如果selectedTextRange属性有效,则把selectedTextRange区间的内容修改为markedText,并把selectedTextRange的值同步给markedTextRange属性

            3)、在光标所在位置添加markedText的内容,并更新markedTextRange的值,然后把markedTextRange的值同步给selectedTextRange属性

            这里我把markedTextRange属性和selectedTextRange属性强绑定啦,这只是为了方便,并不是规范。另外,我这么做也是参考了Apple的例子(https://developer.apple.com/library/archive/samplecode/SimpleTextInput/Introduction/Intro.html)。

            selectedRange参数,我实际调试发现:location的值一直为markedText的长度、length的值一直为0。故我忽略了这个参数。

        2、func unmarkText()

            这个方法表示置空markedTextRange属性。这样实现markedTextRange属性标识的文本变成真实内容,表示一个输入动作的完成。

            因为我把markedTextRange属性和selectedTextRange属性强绑定了,所以也一起置空了selectedTextRange属性。

            在我的实现上只有用户有输入焦点,selectedTextRange属性就不会为空,selectedTextRange属性表示的区间收尾一致,表示没有选中,且代表了光标的位置。所以,我刚刚表述的置空了selectedTextRange属性的实际行为是把selectedTextRange属性的起止位置都标记为markedTextRange属性置空前的结束位。


UIKeyInput协议

        UITextInput协议是UIKeyInput协议的子协议,这个协议的两个方法也很有用:

        1、func deleteBackward()

            需要在这个方法里实现输入法中删除的响应。

            当接入蓝牙键盘等设备时,只有键盘的backspace键会触发这个方法。键盘的delete键,没有回调。这个问题我暂时没有处理。

        2、func insertText(_ text:String)

            这个方法表示在光标位置插入text的内容。目前实际测试发现搜狗输入法在使用(而不是markedTextRange属性)


定制deleteBackward

        在点击输入法的删除按钮时,deleteBackward会被调用。

        但框架会帮我们做一些事情,简单来说,如果当前选中如果为空(selectedTextRange属性有效、且isEmpty为true),就会改写selectedTextRange为当前位置前一个位置和当前位置的Range,这时deleteBackward的代码就是简单的删除选中,多数时候比较方便的。

        但如果我们需要扩展删除功能(例如,我需要光标在特定位置时,删除行为不是只删除前一个字符),就需要进行一些改写。

        在deleteBackward被调用前,会依次调用一下接口:

        1、func position(from position:UITextPosition, offset:Int) ->UITextPosition? 根据当前位置计算前一个位置

        2、func textRange(from fromPosition:UITextPosition, to toPosition:UITextPosition) ->UITextRange? 根据两个位置生成一个区域UITextRange

        3、selectedTextRange属性的set,修改选中

        这里,我的扩展方案是1步骤的方法不实现,以达到终端流程的目的。

        注意:func position(fromposition:UITextPosition, indirection:UITextLayoutDirection, offset:Int) ->UITextPosition? 这个方法最好实现,它主要是处理键盘方向键控制光标移动的。


效率问题

        该协议的所有属性和方法都有可以被系统频繁调用,尽量不要做耗时操作。


关联文档

        第一篇 开发构想

        第三篇 导入导出功能

        第四篇 App的内购功能

        第五篇 本地化

你可能感兴趣的:(第二篇 编辑器控件)