iOS中的文本处理

内容来自于 iOS文档中 About Text Handling in iOS 部分

ios平台提供了显示及编辑文本,及显示格式化的文本和web内容的功能,提供的接口类包括上至text view,text field和web view,下至layout manager,供绘制,控制文本的布局,以及管理文本。

文本metrics

UIKit框架中的类足以实现编辑菜单项的自定义,自定义input view,及文本在app内及app之间的复制,剪切及粘贴。

文本处理概览

普通文本,用UITextField,UITextView,UILabel即可,Web文本用UIWebView

编辑文本时需要和keyboard交互,并在keyboard消失时通过delegate记住输入的文本

最强大的功能即是直接绘制和管理文本

text view底层是强大的称为Text Kit的layout引擎,如果想自定义layout过程,或者参与这个过程,可以使用Text Kit(using text kit to draw and manage text),它由多个类和protocol所组成的,支持对文本进行存储,显示及使用精良的kerning,连体字符,justification等特性进行排版 这些功能。

对于大多app来说,UIKit和TextKit就已经够用了,对于少部分特殊需求来说,可以使用更底层的技术,比如Core Text,Core Graphics和Core animation,当然还有UiKit本身。

自定义数据的输入和编辑

自定义input view可以取代系统keyboard以进行特殊数据的输入,使用UIPasteboard及相关类,app可以在app内或者 app之间进行复制,剪切数据,当然还可以自定义复制-剪切-粘贴 这个菜单

iOS中的排版概念

管理text fields和text view

text field和text view的功能在于显示文本及开启了编辑文本的入口,其承担的任务在于配置文本对象,访问当前的文本,验证用户的输入,以及显示在textfield中放置的view,比如书签按钮。完成这些任务是通过delegate,即UITextFieldDelegate及UITextViewDelegate来完成的。

发送给delegate的一系列message

UITextField和UITextView类的实例通常会在处于first-responder状态的特定文本对象发生或即将发生变化的时候向其delegate发送一系列名字类似的消息。当用户点击一个text object的时候,它会成为first responder,同时系统会显示keyboard并为这个text object开始editing session。而若用户点击了另一个text object或者点击了结束编辑的按钮,则当前first-responder的text object会重新分配first-responder状态。若没有选中其他text object则keyboard收起,否则为选中的另一个text object显示其keyboard。

上面所述是一般特征,当然也是有例外的:

1 在iPad中,对于使用"form sheet"style来模态显示的其view的view controller,则keyboard一旦显示,则不会消失,除非用户点击dismiss key 或者modal view controller被dismiss掉。其目的是为了避免用户在多个text field移动时过多的keyboard动画。

2 自定义input view时,input view是text view或者自定义view的用来替换系统keyboard的属性值。有input views的时,text object的keyboard可以在其仍然是first-responder的时候被UIKit交换出去,或者也可以为非text object也可以显示一个类似于keyboard的input view。

发送给text view的delegate的消息序列如下:

1 在text object成为first-responder之前 textfield/textview shouldbeginediting:,可以在此方法内返回YES/NO 以确认是否愿意text object成为first responder

2 在text object成为first-responder之后,textfield/textview DidBeginEditing:

3 在editing session中:用户输入及编辑文本时,text object会调用特定的delegate方法,比如文本变化时的textViewDidChange:,或者text field的delegate会在用户点击了clear button之后收到textFieldShouldClear:获取是否应当清空文件的信息。

4 textobject即将resign first-responder的时候,会向delegate发送textField/textView ShouldEndEditing: ,实现这个方法的初衷其实是为了校验用户输入是否合法,比如如果输入文本需要遵守一定的格式,则此方法可以返回NO

textfield另一个相关的方法是textFieldShouldReturn:,用户点击return键的时候会询问delegate是否resign first-responder。

5 在已经resign了first-responder之后textField/textView DidEndEdting:。

其它希望知道text views变化情况的对象可以通过监听它们的notification。

配置text fields和text views

配置文本特性:文本颜色,对齐,font族,font typeface和font size

配置keyboard:keyboard type,返回键名,安全文本输入,auto-enabled 返回键,这些都是UITextInputTrait所声明的特性(需要注意的是对于text view的情况,auto-enabled 返回键是作为回车换行键的)

配置特定于Text-field:border,背景图,disabled image,清空按钮和占位字符。同时还可以设置UIControl的信息

配置特定于text view:可编辑状态,数据检测(比如电话号码和URL链接),同时还有UIScrollView的属性

多text field及text view情况下区分

对于delegate方法调用的时候,区分是哪个text view发出来的delegate消息,可以使用outlet和tag两种方法:outlet即是连接interface builder中的控件,并在delegate方法中做判断;tag方法即为各控件定义tag,并根据不同的tag做区分

获取输入的文本和设置的文本

使用textview的text属性进行读取和设置

对text field使用Formatters

在输入日期的时候除了使用datepicker之外,也可以使用text view加nsdateformatter的方法,为了确保dateformatter拿到正常的字符串来解析,需要验证输入的文本

验证输入文本

有时候不能直接接受用户输入到text view中的文本,最好的验证时机是textField/textView ShouldEndEditing:

另一个时机是 textFiled向delegate发送textField:shouldChangeCharactersInRange:replacementString:消息时。

在textField中使用覆盖view

覆盖view是放置在textField左边或者右边角落的小块view,它们通常是作为控件存在,并作用于当前textField的内容。搜索和书签是覆盖view的两项特殊任务,当然也肯定有其他情况。比如如下的覆盖view会使用text field中的URL加载一个web browser。

overlay view

创建overlay view需要创建一个能够适配textField高度的view,并设定一张尺寸合适的image。如果view是button或者control,可以使用target,action,及triggering control event来呼应用户操作。一般情况下的需求是text field是编辑焦点时,显示overlay view,这时只需要在delegate的textFieldDidBeginEditing:中将其赋值给leftView或者rightView即可。可以控制overlay view在编辑session中出现的时机,比如用户开始输入之前或者用户输入之后(为leftViewMode或者rightViewMode属性赋值一个UITextFieldViewMode常数)。而要移除overlay view也很简单,只需要在textFieldDidEndEditing:中将leftView和rightView赋nil即可。

跟踪TextView中选中的文本

textViewDidChangeSelection:方法可以使得追踪用户对选中文本的修改成为可能,并获取选中的子串并做相关的操作,比如将所有字符变成大写等。

显示web内容

如果使用了UIWebView对象,那么可以显示本地或者网络上的内容。

显示本地内容

loadData:MIMEType:textEncodingName:baseURL: 或者 loadHTMLString:baseURL:方法,如可以这样显示pdf文件

NSData *pdfData = [NSData dataWithContentsOfFile:thePath];

[(UIWebView *)self.view loadData:pdfData MIMEType:@"application/pdf"

textEncodingName:@"utf-8" baseURL:nil];

虽然textEncodingName对pdf数据没有影响,但仍在上述代码中保留。

显示网络内容

加载远程网页需要使用loadRequest:(NSURLRequest*)req,由于加载网络内容可能费时,可能需要显示一个活动显示器以表示加载正在进行,可能通过添加遵守UIWebViewDelegate的delegate来实现这个目标。

如果在初始化基于网络的请求之后,需要释放UIWebView,则必须在释放WebView之前取消掉在处理中的request,可以使用webview的stopLoading方法取消一个加载请求。比较典型的地方是在viewController的viewWillDisappear方法中。判定request是否仍然pending可以通过web view的loading属性。

webview释放代码示例

管理键盘

用户点击textField,textView或者web view的某个域的时候,需要出现键盘,这时候可以配置键盘的各种特性,需要在编辑开始及结束的时候控制键盘,而且由于键盘出现时会挡住屏幕的一部分,所以还需要对界面进行相应的调整。

键盘和输入法

先略过textfield和textView的keyboard配置,说下webview的键盘配置,虽然UIWebView不直接支持UITextInputTraits协议,但可以指定HTML中input元素的属性,比如可以设定autocorrect 和 autocapitalize以设定键盘的行为

HTML元素控制keyboard行为

也可以指定希望使用的keyboard类型,比如使用tel,email,url为type属性值以使用对应的键盘,或者为pattern指定"[0-9]*" 或 "\d*"值以使用数字键盘,这些HTML5特性在iOS上都是支持的。

管理键盘

虽然UIKit对象通常直接响应用户操作并显示键盘,但仍然可以管理键盘,如下部分描述keyboard的管理。

接收键盘通知

UIKeyboardWillShowNotification

UIKeyboardDidShowNotification

UIKeyboardWillHideNotification

UIKeyboardDidHideNotification

使用userInfo中的信息即可获取键盘尺寸信息,使用UIKeyboardFrameBeginUserInfoKey和 UIKeyboardFrameEndUserInfoKey

在解决键盘遮盖界面内容的时候,比如UIScrollView的情况,则在键盘出现之后,使用scrollRectToVisible:animated:方法将点击的内容显示进视角中。

另一种滚动编辑区域的办法是将scrollview的frame高度向屏幕底部增加键盘的高度,并将offset设置为activefield的y 减去键盘高度

复制剪切和粘贴操作

用户可以在应用内或者应用间进行文本,图片和其他数据的复制,剪切和粘贴,UIKit在UITextView,UITextField和UIWebView中实现了复制-剪切-粘贴,如果想使用这些特性,可以使用这些类,或者自己实现这些特性。这部分接下来的部分描述实现这些操作的接口。

UIKit中的复制粘贴操作

UIPasteboard类提供pasteboard:app内或者app间共享数据的保护区域,这个类提供了从读取和向写入 数据到pasteboard的方法。

UIMenuController提供在复制,剪切和粘贴的选择块之上或之下显示的编辑菜单。菜单中默认的命令有复制,剪切,粘贴,选择和全选,可以添加自定义的菜单项。

UIResponder提供了canPerformAction:withSender:方法来根据当前上下文来显示或者移除菜单中的命令

UIResponderStandardEditActions这个协议声明了处理复制,剪切,粘贴,选择和全选的接口,当用户点击命令时,相应的UIResponderStandardEditActions方法会被调用。

剪贴板

剪贴板是在app内及app之间交换数据的标准机制,剪贴板最常用的是处理复制,剪切和粘贴:

1 用户在app内选择数据并选择copy或者cut菜单命令时,选择的数据会放置到剪贴板上

2 当用户选择paste菜单命令时,数据从剪贴板复制到当前app中

iOS中剪贴板也支持find操作,此外,还可以在app之间通过剪贴板使用自定义的URL范式而非复制剪切粘贴来传输数据,可以查看Updating Your Info.plist Settings 部分以查看这个技巧的信息。

撇开操作,使用剪贴板对象的基本操作是写入和读取数据,虽然这些操作概念上比较简单,但这份简单实际上掩盖了很多细节。主要的难点在于存在很多种表示数据的方式,而这份复杂导致需要对效率进行考量,接下来会讨论这些以及其它细节。

命名剪贴板

剪贴板可以是公有,也可以是私有的,公有剪贴板叫做系统剪贴板,私有剪贴板是app自己创建的剪贴板,剪贴板必须有独一无二的名字,UIPasteboard定义了两个系统剪贴板:

UIPasteboardNameGeneral 是用来对一系列类型的数据进行复制剪切粘贴操作的剪贴板,可以通过generalPasteboard这个类方法来获取其单例

UIPasteboardNameFind是用于搜索的,当前输入searchable(UISearchBar)的字符串会被写入其中,因此可以在app间共享,可以使用参数UIPasteboardNameFind调用类方法pasteboardWithName:create:来获取这个剪贴板

通常情况下,使用系统剪贴板就已经够用了,但必要情况下可以自己创建剪贴板,通过pasteboardWithName:create:方法,也可以通过pasteboardWithUniqueName获取一个独一无二的app内剪贴板。

剪贴板持久化

系统剪贴板是持久化的,app剪贴板也可以是持久化的,非持久化的app剪贴板在app退出后就被移除,而持久化的剪贴板可以跨越app生存期和系统重启而生存下来,可以通过persistent属性设为YES来实现剪贴板的持久化。

剪贴板属主和items

上一次输入数据到剪贴板的对象称为剪贴板的owner,每块写进剪贴板的数据都称为一个item,剪贴板可以包含单条或者多条item,app可以放置任意多条item。比如若一段选择包含文字和图片,则剪贴板会让你将文本和图片分成多条item复制进去,而app从剪贴板中读取多条item时,可以选择只读取自己支持的item,比如只读取文本。

剪贴板UTI

剪贴板操作经常在多个app之间进行,各app不需要知道其他app的存在,也不需要知道其他app能够处理的数据类型。为了最大化共享的潜力,剪贴板可以持有同一个剪贴板item的多种表示形式,比如一个富文本编辑器可能提供HTML,PDF及纯文本形式的所复制的数据。剪贴板上的item包含所有app所能提供的此item数据的所有形式。

剪贴板item的每种形式都是用一个唯一类型标识符(Unique Type Identifier (UTI))所表示的(UTI只是一个唯一标识特定数据类型的字符串),UTI提供了一种识别数据的能用方法。如果想支持自定义类型,可以使用反DNS记号规则,比如com.jeff.pastetype,具体可参考Uniform Type Identifiers Overview。

比如,假定app支持选择富文本和图片,其可能会在剪贴板上放置富文本版及unicode版本的选中的文本和多种形式的选中的图片。每个item的每种形式的数据都与item存储在一起的,如下图:

剪贴板item及其数据存储形式

一般为了尽可能地共享,剪贴板item需要包含尽可能多的数据形式,剪贴板读取数据的时候尽量选所支持的最丰富的类型。

change count

change count是与pasteboard一对一个一个数据,用来记录剪贴板内容每次添加,修改及移除时,数量增1 ,每次changecount增加时,pasteboard都会发通知出来通知监听的观察者。

复制粘贴的第一步

在复制剪切粘贴任何数据之前,需要先选择,选择某item(并显式地通过UI也好,什么也好指示了此选中)之后,需要显示编辑菜单,用来指示此次选择,系统编辑菜单包含的选项有Copy, Cut, Paste, Select, 和 Select All,选中某菜单项时,相应的UIResponderStandardEditActions方法实现(比如cut: 或者 paste:)会被调用。详情参见Managing the Selection and the Edit Menu

复制剪切所选

用户点击菜单项时,会触发responder object的响应,比如cut:,copy:等,通常是first responder,如果其未实现,则会沿着responder chain传递下去,UIResponderStandardEditActions这个非正式协议实现了这些方法,由于它是个非正式的协议,为了更好地利用系统对responder chain的遍历,可以对继承自UIResponder的对象实现此协议并将其安装到responder chain中。

针对cut:和copy:,需要将所选中的内容按尽可能多种形式的数据和对象写进剪贴板中。这个操作会包含如下这些步骤(假定只有一个item):

1 根据选择的内容,获取此对象对应的对象或二进制数据

二进制数据必须封装在NSData中,如果还想写另一个类型的对象进剪贴板,则其必须是一个property list对象,即其必须是NSString, NSArray, NSDictionary, NSDate, NSNumber, 或 NSURL 这些对象中的一个

2 如果可能的话为对象生成一种或多种其他形式的数据

比如,如果为选中的图片创建了UIImage对象,则可以使用UIImageJPEGRepresentation 和 UIImagePNGRepresentation将其转换成不同的形式。

3 获取pasteboard对象

4 为写进剪贴板的item中的每种形式的数据选择一种合适的UTI

5 将每种形式的数据写进第一个剪贴板item中

写数据对象用 setData:forPasteboardType:

写property list对象用setValue:forPasteboardType:

6 如果命令是cut:,则将其从你的数据模型中移除并更新view

粘贴

当用户点击粘贴时,在responder chain中调用paste: ,其从剪贴板中读取你的app支持一种形式的数据,步骤如下(假定只有一个item)

1 获取pasteboard对象

2 使用containsPasteboardTypes:方法或者检查pasteboardTypes:返回数据以验证是否剪贴板第一个item中是否有当前app可处理的数据

3 如果有可处理的数据,则使用dataForPasteboardType:读取封装的NSData对象,或者使用valueForPasteboardType:读取property list对象

4 将数据添加进你的数据模型中

结束操作

当从各命令的处理中返回时,菜单会消失,当然也可以手动将其继续显示,可以参见Dismissing the Edit Menu

自定义数据输入view

主要是inputview及input accessory view的定制,及输入点击声音等的处理,先略过

显示及管理编辑菜单

管理选择和编辑菜单

选择可以是一块文本,一张图片,一个URL,一种颜色或任何其他形式的数据,包括自定义对象,你必须对你view中的对象的选择进行管理。如果你要自定义选择的手势,或者支持多段选择,都需要自己实现这种“选择”。

当你的app断定需要出现菜单时(可能是要做选择),可能通过这些步骤来完成:

1 调用UIMenuController的sharedMenuController获取全局menu-controller实例

2 计算选择的边界,并使用得到的矩形调用setTargetRect:inView:方法,则会根据此矩形与屏幕顶部或者底部的距离判断菜单是出现在矩形上面或者下面

3 调用setMenuVisible:animated:方法以显示menu

更多可参见iOS document

向编辑菜单添加自定义item

可以添加自定义item到菜单中,并通过target-action来完成action操作,当然,为了action能够在responder chain中找到,通常需要将你的view becomeFirstResponder,因为UIMenuItem的形式是[[UIMenuItem alloc] initWithTitle:@"Change Color" action:@selector(changeColor:)],随后将其放置进menu controller的menuItems中。

dismiss编辑菜单

[UIMenuController sharedMenuController].menuVisible = YES;通常菜单是在菜单项命令处理完之后自动隐藏的,但可以用这句来手动将其显示。当然,如果有场景需要根据菜单状态来处理,则可以监听UIMenuControllerWillHideMenuNotification这个通知。

Text Kit绘制及管理文本

UITextField,UITextView,UILabel,UIWebView用来显示文本,UITextView用来显示大块文本,其底层是强大的layout引擎,称为Text Kit。如果想自定义layout过程或者干预这个过程,可以使用Text Kit,对于少量点的文本和需要自定义方案的特殊需求,可以选择更底层的技术core text,本节之后后讲到。

Text Kit是一组UIKit下用来使向app提供高质量的存储,布局和显示文本这类排版服务的类和协议,可以提供诸如kerning,连体字符,断行和justification等这些精细的排版功能。Text Kit是基于Core Text的,所以有一样高效的性能,UITextView是完全与Text Kit整合的,它提供了编辑和显示的功能使用户可以输入文本,指定格式属性并看到结果。其他Text Kit类提供文本存储和布局功能。

Text Kit框架层级图

基本的text kit 对象

基本text kit 对象

NSTextStorage存储用于显示的文本,由NSLayoutManager将其显示在NSTextContainer所指示的区域内。通常NSTextContainter定义文本显示的区域,通常是矩形区域,但可以通过继续它从而指定圆形,五边形等非矩形。text container不仅定义了文本显示区域的轮廓,也维护了一个贝塞尔路径的数组以定义不可布局文本的区域。因此在布局的时候,文本流会围绕不可布局的路径,以此引入graphics及非文本布局的因素。

NSTextStorage是NSMutableAttributedString子类,是存储文本及其属性的基础存储机制。确保文本及属性在编辑过程中处于一致的状态。除了存储文本之外,NSTextStorage对象也管理一组client NSLayoutManager对象,对其字符发生的任何变化,对这些manager进行通知,以对文本进行及时的relay和redisplay。

NSLayoutManager整体调度其它文本处理对象的操作,调解将NSTextStorage中的数据到view显示区域的文本的所有操作过程。它将Unicode字符转换成glyph,并监督glyph在NSTextContainer对象所定义的区域中布局的过程。(需要注意的是,layout manager,text storage,text container可以从子线程访问,只要app guarantees the access from a single thread

文本属性

Text Kit处理三种文本属性:字符属性,paragraph属性和文档属性。字符属性包括font,颜色和下标等特性,即单个字符或者一组字符的相关特性。paragraph是比如缩进,制表符和line spacing等属性。文档属性包括纸张大小,页边距,和视角放大百分比等文档属性。

字符属性

attributed字符串使用NSDictionary存储键值对形式的字符属性,key是字符串常量表示的属性名,比如NSFontAttributeName

attributed string的组成

概念上,attributed string中的每个字符都对应一个属性dictionary,但这组属性通常是针对一连串的字符串的,可能通过方法获取某字符或者某串字符对应的属性

可以对attributed string应用任意自定义的key-value对,可以对NSTextStorage对象中的文本应用NSMutableAttributedString的addAttribute:value:range:方法以添加属性,也可以通过addAttributes:range:添加一组自定义属性。要支持自定义的属性,需要继续NSLayoutManager,重载drawGlyphsForGlyphRange:atPoint:,可以在此方法中先调用super将各字符先绘制出来然后将自己的属性在其上绘制出来,也可以完全自己绘制glyphs。

段落属性

paragraph属性影响段落中各行的排列,使用NSParagraphStyle类对象封装段落属性,NSParagraphStyleAttributeName这个字符串属性指定段落属性对象,attribute fixing这一机制会确保在编辑过程中,每个段落只对应一个NSParagraphStyle对象。

文档属性

虽然文本系统没有内建机制存储文档属性,但可以通过NSAttributedString的类似于initWithRTF:documentAttributes:的方法指定从一段RTF或者HTML数据流中提取的文档属性,相反地可以通过比如RTFFromRange:documentAttributes:的方法将RTF数据写入。

attribute fixing

为了处理编辑过程中产生的不一致,NSMutableAttributedString的UIKit扩展定义了fixAttributesInRange:方法来修复attachment,字符,段落属性之间的不一致。确保attachments在对应的attachment字符串删除之后不再存在,字符属性只应用于对应font可作用的字符上,且段落属性在整个段落中一致。

操作text storage

要编辑text storage,需要3个步骤,先用beginEditing声明更改开始,然后用replaceCharactersInRange:withString: 和 setAttributes:range:类似的方法添加修改,每次调用这样的方法,text storage都会调用edited:range:changeInLength:以跟踪受其影响的字符。完成修改时,调用endEditing,这会导致其delegate调用到textStorage:willProcessEditing:range:changeInLength:,并调用其自己的processEditing方法,完成attribute fixing。

修复完attributes后,会调用delegate的textStorage:didProcessEditing:range:changeInLength:方法以给予delegate验证及修改attribute的机会(虽然delegate可以改变字符属性,但它会引起attribute不一致)。最终text storage对所有相关的layout manager发送processEditingForTextStorage:edited:range:changeInLength:invalidatedRange:,通知这些range属性的变化,便于重新布局。

Font对象

计算机font是一个使用诸如open type或者true type格式存储的数据文件,包含glyph相关的信息,以及绘制会用到的所有增补信息。创建UIFont时不使用alloc/init,相反使用preferredFontForTextStyle:传入text style常数,或者fontWithName:size:,也可以用font描述符fontWithDescriptor:size:。

text style

从iOS7中引入,是由Dynamic Type机制实现的基于语义的font描述,使用它的好处在于可以获取dynamic type为文本可读性带来的好处,因为dynamic type的响应是基于用户偏好,增强辨识度及超大号类型的 权限设置的。

font descriptor

font描述符UIFontDescriptor类对象,由一组属性dictionary创建UIFont对象。可以用它查询系统支持的特定特征对应的所有字体,比如字体名,特性,语言及其他特征等。

激活font特性

font描述符的另一个作用是在一堆font特性中激活及选择。font特性(font features)是字体的排版特性,控制glyph的渲染,只有在字体设计者支持时才会有相应特性。

font特性归类为feature types,使用特定特性选择符选特定特性设定。feature type可以是排他也可以是非排他的,如果是排他的,则一次只能选择一个可能的feature 选择符,比如数字是成比例还是等宽的,如果是非排他的,则一次可以选多个,比如连体字符feature类型。

feature有上下文相关和非相关之分,上下文相关的feature应用于glyph的方式是依赖于其邻接的glyph的,ios文本系统layout能力的强大之处在于可以自动进行复杂的上下文处理。

非上下文相关特性应用方式不依赖于邻接glyph,这些特性包括所选择文本在选择后呈现另一套glyph,以及为了数学排版或者增加排版复杂度而进行的glyph替换。

激活font特性

上述代码激活了number spacing特性(由kNumberSpacingType常数所标识),选择的是比例宽度数字(kProportionalNumbersSelector),同时激活了字符替换特性类型(kCharacterAlternativesType)选择了值2,这个例子中用来表示字体特性类型和selector的是在core text框架中SFNTLayoutTypes.h中定义的枚举。字符alternative类型没有预定义的常量代表特性选择标识,所以要用font-defined数值。由于font特性是由font定义的,所以可以直接查询其支持的特性,使用CTFontCopyFeatures函数。

查询font metric

可使用ascender, capHeight, xHeight等属性查UIFont的metric信息

布局文本

layout 过程

layoutmanager通过两个步骤控制文本的布局:glyph生成和glyph布局,这两个步骤都是lazily进行的,在生成了glyph并计算出其位置信息之后,会存起来以备以后使用,并监听glyph range的invalidated。character range可以有两种方式自动失效:需要glyphs生成时和需要glyphs布局时。当然也可以手动失效glyph或者布局信息,当成layout manager收到获取失效区域glyph或者布局信息的时候,会重新生成glyph或者重新布局。

生成行fragment rectangle

text container中各行是由其形状及不可布局的路径所决定的,一旦行fragment 矩形与不可布局的路径相交,这些部分的行必须被缩短或者fragmented。如果区域中有gap,则重叠于其上的行必须进行偏移。

layout manger提出一个矩形给某行,并请求text container调整这个矩形,这个矩形可以全部或者部分地超出text container的区域,但其可以变宽也可以变窄,请求调整的方法是lineFragmentRectForProposedRect:atIndex:writingDirection:remainingRect:,其返回值是所请求区域中基于文本方向的最大的可用区域,它同时也会返回一个包含所有剩余区域的矩形,比如hole或者gap另一边的空间。

在将文本适配进矩形之后,会做最后一个调整,称为line fragment padding,定义每行尾在行fragment矩形中的空白比例,可以通过lineFragmentPadding属性更改padding,但这并不是一个设置页边距的合适方法,可以设置textview在其父view中的位置,而对于text margin,可以设置textContainerInset属性,当然也可以设置段落的indent。

除了line fragment矩形本身,layout manger还会返回一个称为used rectangle,这是line fragment矩形中实际显示glyph和mark的区域。通常两个矩形都包括line fragment padding,和行间空间(比如font的line height和段落的行spacing参数)。然而段落spacing及任何text周围的空白,及center-spaced文本引起的空白,只在行fragment矩形中出现,并不出现在used rectangle中。

指定不可布局的路径

text container维护着一个贝塞尔路径对象数组代表bounding区域中不可布局的区域。当lineFragmentRectForProposedRect:atIndex:writingDirection:remainingRect:所请求的区域与这些路径包围的区域重叠的时候,会返回调整过的区域。

行fragment适配

指定多页和多列布局

最简单的情况下,text kit对象都是单个的,即一个text storage object,一个text container,一个layout manager

单文本流的对象配置

但只有一个container和storage,这种安排下,文本流在textcontainer定义的空间中连续。但page  breaks,多列布局,和更复杂的布局是无法由这种安排满足的。

通过使用多text container,每个联系一个text view,可以实现更复杂的文本布局,比如可以通过这样实现page break

分页文本配置

多列文档的对象结构可以是这样

多列文本对象配置

与其每页单独对应一个text container,现在对应两个text container,页中每列一个。每个container控制文档的一部分,文本先是在左上的container中,满了之后delegate会收到通知,如果还有文本需要排布会在下一个text container中布局,并在完成的时候通知delegate,依此类推。

不仅可以有多container,还可以有多个layout manager 访问同一个text storage,目的是提供同一段文本的多个view,如果用户更改了上面view中的文本,下面的view会直接反映出来

同一段文本的多个布局

更底层的文本处理技术

core text先等一等

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • 1、窗体 1、常用属性 (1)Name属性:用来获取或设置窗体的名称,在应用程序中可通过Name属性来引用窗体。 ...
    Moment__格调阅读 4,322评论 0 11
  • 前几天一边开车一边听罗胖谈折腾的作用(得到APP罗辑思维第345期),总是在不经意之间这个胖子会给我一个全新的角度...
    想飞翔的蜗牛阅读 5,686评论 0 6
  • 春节过完回京后我一直想自己做一道想吃的菜,一是能够对重复的日常生活多一点点变化,二是期待食物做成的样子。这种体验家...
    小张张大皮皮阅读 544评论 0 0
  • (原创歌词 寒xuan) 该走的人总要走 雪停了不久 我也不挽留 若大雪落于发稍也算白头 日出后化冰水将心也凉透...
    霍乱之间阅读 229评论 0 2
  • self.searchController.hidesNavigationBarDuringPresentatio...
    无法触碰阅读 293评论 0 0