第⼆篇编辑器控件
功能描述
这个控件能够以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属性
selectedRange参数,我实际调试发现:location的值⼀直为markedText的长度、length的值⼀直为0。故我忽略了这个参数。
2、func unmarkText()
这个⽅法表⽰置空markedTextRange属性。这样实现markedTextRange属性标识的⽂本变成真实内容,表⽰⼀个输⼊动作的完成。
因为我把markedTextRange属性和selectedTextRange属性强绑定了,所以也⼀起置空了selectedTextRange属性。
在我的实现上只有⽤户有输⼊焦点,selectedTextRange属性就不会为空,selectedTextRange属性表⽰的区间收尾⼀致,表⽰没有选中,且代表了光标的位置。所以,我刚刚表述的置空了selectedTextRange属性的实际⾏为是把selectedTextRange属性的起⽌位置都标记为markedTextRang
e属性置空前的结束位。
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? 根据当前位置计算前⼀个位置
func textRange(from fromPosition:UITextPosition, to toPosition:UITextPosition) ->UITextRange? 根据两个位置⽣成⼀个        2、func
区域UITextRange
3、selectedTextRange属性的set,修改选中
这⾥,我的扩展⽅案是1步骤的⽅法不实现,以达到终端流程的⽬的。
注意:func position(fromposition:UITextPosition, indirection:UITextLayoutDirection, offset:Int) ->UITextPosition? 这个⽅法最好实现,它主要是处理键盘⽅向键控制光标移动的。
效率问题
该协议的所有属性和⽅法都有可以被系统频繁调⽤,尽量不要做耗时操作。
关联⽂档
第⼀篇 开发构想
图片编辑器app第三篇 导⼊导出功能
第四篇 App的内购功能
第五篇 本地化