iOS中的文本处理,ios理论基础

作者:澳门新葡京平台游戏

用户可以在一个app中复制文本、图片、或者其他数据,并粘贴该数据到该app的其他位置,或不同的app中。例如,你可以在email信息中复制一个人的地址,并把它粘贴到“联系人”app的合适字段中。UIKit框架在UITextView和UITextField类中实现了复制-剪切-粘贴。如果你想在自己的app中使用该功能,你可以使用这些类的对象,也可以实现自己复制-剪切-粘贴。

复制和剪切选中内容

当用户点击编辑菜单的复制或者剪切命令,系统调用响应对象的copy:或者cut:方法。通常第一响应者(你的自定义视图)实现这些方法,但是如果第一响应者没有实现,消息会以通常的方式进入响应者链。注意,UIResponderStandardEditActions非正式协议声明了这些方法。

注意:因为UIResponderStandardEditActions是非正式协议,在app中的所有类都可以实现它的方法。但是为了充分利用响应者链的默认行为,实现这些方法类应该继承UIResponder并被安装到响应者链中去。

为了响应cut:或 paster:消息,你可以将所选择的对象或数据以尽可能多的不同的方式写入剪贴板。该操作涉及到以下步骤(假设是单一的剪贴板项目):

  1. 从所选内容中,识别或获取对象或该对象响应的二进制数据。
    二进制对象必须封装在NSData对象中,如果你打算把对象的其他类型写入到剪贴板中,它必须是属性列表对象,也就是下面类之一的对象:NSString、NSArray、NSDictionary、NSDate、NSNumber、或者NSURL。(更多属性列表对象的内容,参见Property List Programming Guide。)
  2. 如果有可能,生成对象或数据的一个或更多的表示法。
    例如,如果在之前的步骤中创建了一个UIImage对象来表示选中的图片,你可以使用UIImageKPEGRepresentation和UIImagePNGRepresentation函数来把该图片转换成不同的表示法。
  3. 获取一个剪贴板对象。
    在多数情况下是通用剪贴板,你可以通过generalPasteboard类方法得到。
  4. 分配合适的UTI给每个要写入剪贴板项目数据的表示法。
    参见“剪贴板概念”中对这一部分的讨论。
  5. 把数据写入每个表示法类型的第一个剪贴板项目:
  • 要想写入数据对象,发送setData:forPasteboardType:消息给剪贴板对象。
  • 要想写入属性列表对象,发送setValue:forPasteboardType:消息给剪贴板对象。
  1. 如果命令是Cut(cut:方法),从app数据模型移除所选表示的对象,并更新视图。

代码清单5-1展示了copy:和cut:方法的实现。cut:方法调用copy:方法,然后从视图和数据模型中移除选中的对象。注意,copy:方法归档一个自定义对象,来获取一个NSData对象,该对象可以在setData:forPasteboardType:中传递给剪贴板。

代码清单 5-1 复制和剪切操作

- (void)copy:(id)sender {
    UIPasteboard *gpBoard = [UIPasteboard generalPasteboard];
    ColorTile *theTile = [self colorTileForOrigin:currentSelection];
    if (theTile) {
        NSData *tileData = [NSKeyedArchiver archivedDataWithRootObject:theTile];
        if (tileData)
            [gpBoard setData:tileData forPasteboardType:ColorTileUTI];
    }
}

- (void)cut:(id)sender {
    [self copy:sender];
    ColorTile *theTile = [self colorTileForOrigin:currentSelection];

    if (theTile) {
        CGPoint tilePoint = theTile.tileOrigin;
        [tiles removeObject:theTile];
        CGRect tileRect = [self rectFromOrigin:tilePoint inset:TILE_INSET];
        [self setNeedsDisplayInRect:tileRect];
     }
}

iOS 拷贝、剪切和粘贴理论基础(转),ios理论基础

简介

在iPhone OS 3.0之后,用户可以在一个应用程序上拷贝文本、图像、或其它数据,然后粘贴到当前或其它应用程序的不同位置上。比如,您可以从某个电子邮件中拷贝一个地址,然后粘贴到Contacts程序的地址域中。目前,UIKit框架在UITextViewUITextField、和UIWebView类中实现了拷贝-剪切-粘贴支持。如果您希望在自己的应用程序中得到这个行为,可以使用这些类的对象,或者自行实现。

但在iOS7.0后,UIPasteboard由共享变为沙盒化了UIPasteboard本无问题,但是开发者开始使用它来存储标识符,和其他的相关app分享这些标识符的时候问题就出现了。有一个使用这种把戏的就是OpenUDID。在iOS7后,使用 [UIPasteboard pasteboardWithName:create:]和 [UIPasteboard pasteboardWithUniqueName]创建剪贴板,而且只对相同的app group可见,这样就让OpenUDID不那么有用了。

本文的下面部分将描述UIKit中用于拷贝、剪切、和粘贴操作的编程接口,并解释其用法。

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

接下来的部分将描述使用复制、剪切、和粘贴操作的UIKit编程接口,并解释它们是如何做到的。

粘贴所选内容

当用户点击菜单中的粘贴命令时,系统调用响应对象的paste:方法。通常第一响应者(你的自定义视图)实现该方法,但是如果第一响应者没有实现它,该消息会以通常的方式进入响应者链。paste:方法通过UIResponderStandardEditActions非正式协议声明。

响应paste:消息,你从应用支持的表示发中读取剪贴板对象。然后,你添加要粘贴的对象到app的数据密性中,并在用户指定的位置的视图中显示新的对象。这个操作会调用如下步骤(假设是单一的剪贴板项目):

  1. 获取剪贴板对象。
    在大多数情况下是通用剪贴板,你可以通过generalPasteboard类方法得到。
  2. 验证第一个剪贴板项目包含某种表示法的数据,该表示法是app可以通过调用containsPasteboardTypes:方法或pasteboardTypes方法可以处理的,然后验证返回的类型的数组。
    注意,你应该已经在实现canPerformAction:withSender:中执行了本操作。
  3. 如果剪贴板的第一个项目包含app可以处理的数据,调用下面方法之一来读取它:
  • dataForPasteboardType: ,如果读取的数据被封装在NSData对象中。
  • valueForPasteboardType:,如果读取的数据被封装在属性列表对象中(参见“复制和剪切所选内容”)。
  1. 添加对象到app的数据模型。
  2. 在用户界面指定的位置显示对象的表示法。

代码清单5-2是一个实现paste:方法的例子。它是cut:和copy:方法的反过程。自定义视图首先看通用剪贴板是否持有它的自定义的数据表示法;如果它有,它就从剪贴板中读取数据,把它添加到app的数据模型中,并为重新绘制标记自身部分(当前选中的内容)。

代码清单 5-2 粘贴数据到所选处

- (void)paste:(id)sender {
    UIPasteboard *gpBoard = [UIPasteboard generalPasteboard];
    NSArray *pbType = [NSArray arrayWithObject:ColorTileUTI];
    ColorTile *theTile = [self colorTileForOrigin:currentSelection];
    if (theTile == nil && [gpBoard containsPasteboardTypes:pbType]) {
        NSData *tileData = [gpBoard dataForPasteboardType:ColorTileUTI];
        ColorTile *theTile = (ColorTile *)[NSKeyedUnarchiver unarchiveObjectWithData:tileData];
        if (theTile) {
            theTile.tileOrigin = self.currentSelection;
            [tiles addObject:theTile];
            CGRect tileRect = [self rectFromOrigin:currentSelection inset:TILE_INSET];
            [self setNeedsDisplayInRect:tileRect];
        }
    }
}

拷贝、剪切、和粘贴操作

在iPhone OS 3.0之后,用户可以在一个应用程序上拷贝文本、图像、或其它数据,然后粘贴到当前或其它应用程序的不同位置上。比如,您可以从某个电子邮件中拷贝一个地址,然后粘贴到Contacts程序的地址域中。目前,UIKit框架在UITextViewUITextField、和UIWebView类中实现了拷贝-剪切-粘贴支持。如果您希望在自己的应用程序中得到这个行为,可以使用这些类的对象,或者自行实现。

本文的下面部分将描述UIKit中用于拷贝、剪切、和粘贴操作的编程接口,并解释其用法。

请注意:与拷贝和粘贴操作相关的使用指南,请参见iPhone人机界面指南文档中的“支持拷贝和粘贴”部分。

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

注意:对于复制和粘贴操作相关的指南,参见iOS Human Interface Guidelines中的“Supporting Copy and Paste”。

编辑操作

当你实现 cut:, copy: 或 paste: 命令返回时,编辑菜单自动隐藏。如果你愿意,你可以通过编程的方式让它们可见。更多信息,参见Dismissing the Edit Menu。a

图片 1文本metrics

UIKit中的复制-粘贴操作

UIKit框架中的多个类和一个非正式协议,给了你在app中实现复制、剪切、和粘贴所需方法和机制:

  • UIPasteboard类提供剪贴板:在app中或app之间共享数据的保护区域。该类提供方法,可以从剪贴板读取数据的项目,也可以向粘贴半写入数据的项目。
  • UIMenuController类在选中部分的上面或者下面显示一个编辑按钮,用以复制、剪切、或者粘贴。默认的编辑菜单命令是复制、剪切、粘贴、选择、及选择全部。你还可以添加自定义菜单项目到编辑菜单(参见Adding Custom Items to the Edit Menu)。
  • UIResponder类声明了canPerformAction:withSender:方法。Responder类可以实现该方法来显示和移除基于当前内容的编辑菜单的命令。
  • UIResponderStandardEditActions非正式协议声明了处理复制、剪切、粘贴、选择、以及选择全部命令的接口。当用户点击在编辑菜单中点击其中一个命令时,对应的UIResponderStandardEditActions方法会被调用。

UIKit中支持拷贝-粘贴操作的设施

UIKit框架提供几个类和一个非正式协议,用于为应用程序中的拷贝、剪切、和粘贴操作提供方法和机制。具体如下:

  • UIPasteboard类提供了粘贴板的接口。粘贴板是用于在一个应用程序内或不同应用程序间进行数据共享的受保护区域。该类提供了读写剪贴板上数据项目的方法。

  • UIMenuController类可以在选定的拷贝、剪切、和粘贴对象的上下方显示一个编辑菜单。编辑菜单上的命令可以有拷贝、剪切、粘贴、选定、和全部选定。

  • UIResponder类声明了canPerformAction:withSender:方法。响应者类可以实现这个方法,以根据当前的上下文显示或移除编辑菜单上的命令。

  • UIResponderStandardEditActions非正式协议声明了处理拷贝、剪切、粘贴、选定、和全部选定命令的接口。当用户触碰编辑菜单上的某个命令时,相应的UIResponderStandardEditActions方法就会被调用。

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

剪贴板概念

剪贴板是在app内或app间交换数据的标准化机制。剪贴板最常用于处理复制、剪切、以及粘贴操作:

  • 当用户选择app中的数据并选择复制命令时,该被选中的数据会被放置到剪贴板上。
  • 当用户选择粘贴命令时(可以在相同或者不同的app中),剪贴板上的数据被复制到当前的app中。

在iOS中,剪贴板也用于支持查找操作。此外,您可以使用剪贴板在应用程序之间使用自定义URL方案传输数据,而不是复制,剪切和粘贴命令;有关此技术的信息,参见Updating Your Info.plist Settings。

无论操作如何,使用剪贴板对象的基本任务就是向其写入数据以及从其中读取数据。尽管这些任务概念上很简单,但是它们掩盖了一些重要的细节。主要的复杂性是可以有多中方法表示数据,而这种复杂性带来了效率问题。这些以及其他问题在下面部分讨论。

文本处理概览

命名剪贴板

剪贴板可以是公共的也可以是私有的。公共的剪贴板被称为系统剪贴板;私有的剪贴板是由app创建的,于是被称为app剪贴板。剪贴板必须有唯一的名字。UIPasteboard定义了两个系统剪贴板,它们都有自己的名字和目的:

  • UIPasteboardNameGeneral用于涉及广泛数据类型的剪切、复制、以及粘贴操作。你可以通过涉及的generalPasteboard类方法来获取表示通用剪贴板的单例对象。
  • UIPasteboardNameFind是用于查找操作。当前用户在搜索栏中输入的字符串是写入到剪贴板,因此可以在不同app中共享。你可以通过调用pasteboardWithName:create:方法来获取到表示剪贴板的对象,传入UIPasteboardNameFind作为名字。

通常你使用一种系统定义的剪贴板,但是如有必要,你可以使用pasteboardWithName:create: 来创建你自己app的剪贴板。如果你嗲用了pasteboardWithUniqueName,UIPasteboard给你一个唯一名字的app剪贴板。你可以通过name属性发现剪贴板的名字。

粘贴板的概念

粘贴板是同一应用程序内或不同应用程序间交换数据的标准化机制。粘贴板最常见的的用途是处理拷贝、剪贴、和粘贴操作:

  • 当用户在一个应用程序中选定数据并选择拷贝(或剪切)菜单命令时,被选择的数据就会被放置在粘贴板上。

  • 当用户选择粘贴命令时(可以在同一或不同应用程序中),粘贴板上的数据就会被拷贝到当前应用程序上。

在iPhone OS中,粘贴板也用于支持查找(Find)操作。此外,还可以用于在不同应用程序间通过定制的URL类型传输数据(而不是通过拷贝、剪切、和粘贴命令,关于这个技巧的信息请参见“和其它应用程序间的通讯”部分。

无论是哪种操作,您通过粘贴板执行的基本任务是读写粘贴板数据。虽然这些任务在概念上很简单,但是它们屏蔽了很多重要的细节。复杂的原因主要在于数据的表现方式可能有很多种,而这个复杂性又引入了效率的考虑。本文的下面部分将对这些以及其它的问题进行讨论。

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

剪贴板持久化

剪贴板可以被持久化。当剪贴板是持久化的时候,它可以在系统重启之后继续存在与之前的app中。系统剪贴板是持久化的。尽管app的剪贴板默认是不持久化的,但是app可以通过把持久化属性设置为YES来标记它的持久化功能。App剪贴板不会持久化,知道创建它或拥有它的app退出。持久化的app剪贴板在该app卸载的时候被移除。

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

剪贴板的所有者和项目

最后将数据放入剪贴板的对象被称为剪贴板的所有者。被放入剪贴板的数据的每个部分被称为剪贴板的项目。剪贴板可以有一个或者多个项目。App可以根据需要替换或者检索多个项目。例如,用户在一个包含文本和图片的视图中进行选择。剪贴板让你把复制的文本和图片分别作为项目。从剪贴板读取多个项目的app,可以选择只使用它支持的项目(例如,只有文本,而没有图片)。

重要:当app把数据写入剪贴板的时候,即使只有单个项目,该数据也会替换剪贴板中原油的内容。即使你使用UIPasteboard的addItems:方法来添加项目,类的写入方法也不会在剪贴板的当前内容后增加项目。

命名粘贴板

粘贴板可能是公共的,也可能是私有的。公共粘贴板被称为系统粘贴板;私有粘贴板则由应用程序自行创建,因此被称为应用程序粘贴板。粘贴板必须有唯一的名字。UIPasteboard定义了两个系统粘贴板,每个都有自己的名字和用途:

  • UIPasteboardNameGeneral用于剪切、拷贝、和粘贴操作,涉及到广泛的数据类型。您可以通过该类的generalPasteboard类方法来取得代表通用(General)粘贴板的单件对象。

  • UIPasteboardNameFind用于检索操作。当前用户在检索条(UISearchBar)键入的字符串会被写入到这个粘贴板中,因此可以在不同的应用程序中共享。您可以通过调用pasteboardWithName:create:类方法,并在名字参数中传入UIPasteboardNameFind值来取得代表检索粘贴板的对象。

典型情况下,您只需使用系统定义的粘贴板就够了。但在必要时,您也可以通过pasteboardWithName:create:方法来创建自己的应用程序粘贴板。如果您调用pasteboardWithUniqueName方法,UIPasteboard会为您提供一个具有唯一名称的应用程序粘贴板。您可以通过其name属性声明来取得这个名称。

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

表示法和UTI

剪贴板操作经常被用到不同的app之间。应用之间无需知道彼此,包括它能处理的数据种类。为了最大化发挥共享的作用,一个剪贴板可以持有同一个剪贴板项目的多个表示法。例如,一个富文本编辑器或许提供了复制数据的HTML、PDF、以及纯文本表示法。剪贴板的一个项目包括了所有app可以提供的数据项目的表示法。

每个剪贴板项目的表示法通常通过唯一类型标识符(Unique Type Identifier,UTI)标识。(UTI是简单的字符串,它是一个特定数据类型的唯一标记。)UTI提供标记数据类型的通用方法。如果你有想要支持的自定义的数据类型,你必须为它创建唯一的标识符。为此,你可以使用反向DNS(reverse-DNS)符号表示类型字符串来确保唯一性;例如,一个自定义百澳是类型可以是com.myCompany.myApp.myType。更多UTI的信息,参见Uniform Type Identifiers Overview。

例如,假设一个app支持富文本和图片的选择。它或许想把富文本和所选文本的Unicode版本,以及所选图片的不同表示法放置到剪贴板。每个项目的每个表示法都存储有它们自己的数据,如图5-1所示。

图 5-1 剪贴板项目和表示法

通常情况下,为了最大化发挥共享的作用,剪贴板项目应该包含尽可能多的不同的表示法。

剪贴板读取器必须找到最合适它功能的数据类型。通常,这意味着选择最丰富的可用类型。例如,一个文本编辑器或许提供了复制的数据的HTML和纯文本表示法。支持副文本的app应该检索HTML表示法,而支持纯文本的app应该检索纯文本版本。

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

改变计数

改变计数是每个剪贴板都有的变量,每次剪贴板特定改变内容(当项目增加、修改、或者移动时)时它都会增加。通过检查改变计数(通过changeCount属性),app可以确定剪贴板中的当前数据和它最后接受的数据是否相同。每次改变计数都会增加,剪贴板会发送一个通知给感兴趣的观察者。

粘贴板的持久保留

您可以将粘贴板标识为持久保留,使其内容在当前使用的应用程序终止后继续存在。不持久保留的粘贴板在其创建应用程序退出后就会被移除。系统粘贴板是持久保留的,而应用程序粘贴板在缺省情况下是不持久保留的。将其应用程序粘贴板的persistent属性设置为YES可以使其持久保留。当持久粘贴板的拥有者程序被用户卸载时,其自身也会被移除。

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

第一步:识别选择并显示编辑菜单

如果你打算复制、剪切、或粘贴什么的话,你首先必须选中它。(粘贴操作通常在一个空的位置操作,例如插入符,指示项目集合中的位置。)在选中了项目并可视化的指示了所选后,你应该显示编辑菜单。编辑菜单是系统菜单,它可能有如下命令:复制、剪切、粘贴、选择、和选择全部。编辑菜单在选中的位置。当用户点击菜单项时,响应的UIResponderStandardEditActions方法实现(例如 cut:或 paster:)会被调用。

更多关于选择和如何显示和管理编辑菜单的内容,参见 Managing the Selection and the Edit Menu。

自定义数据的输入和编辑

粘贴板的拥有者和数据项

最后将数据放到粘贴板的对象被称为该粘贴板的拥有者。放到粘贴板上的每一片数据都称为一个粘贴板数据项。粘贴板可以保有一个或多个数据项。应用程序可以放入或取得期望数量的数据项。举例来说,假定用户在视图中选择的内容包含一些文本和一个图像,粘贴板允许您将文本和图像作为不同的数据项进行拷贝。从粘贴板读取多个数据项的应用程序可以选择只读取被支持的数据项(比如只是文本,而不支持图像)。

重要提示:当一个应用程序将数据写入粘贴板时,即使只是单一的数据项,该数据也会取代粘贴板的当前内容。虽然您可能使用UIPasteboardaddItems:方法来添加项目,但是该写入方法并不会将那些项目加入到粘贴板当前内容之后。

 

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

iOS中的排版概念

管理text fields和text view

数据的表示和UTI

粘贴板操作经常在不同的应用程序间执行。系统并不要求应用程序了解对方的信息,包括对方可以处理的数据种类。为了最大化潜在的数据分享能力,粘贴板可以保留同一个数据项的多种表示。例如,一个富文本编辑器可以提供被拷贝数据的HTML、PDF、和纯文本表示。粘贴板上的一个数据项包括应用程序可为该数据提供的所有表示。

粘贴板数据项的每种表示通常都有一个唯一类型标识符(Unique Type Identifier,缩写为UTI)。UTI简单定义为一个唯一标识特定数据类型的字符串。UTI提供了一个标识数据类型的常用手段。如果您希望支持一个定制的数据类型,就必须为其创建一个唯一的标识符。为此,您可以用反向DNS表示法来定义类型标识字符串,以确保其唯一性。例如,您可以用com.myCompany.myApp.myType来表示一个定制的类型标识。更多有关UTI的信息请参见统一类型标识符概述

作为例子,假定一个应用程序支持富文本和图像的选择,它可能希望将富文本和Unicode版本的选定文本,以及选定图像的不同表示放到粘贴板上。在这样的场景下,每个数据项的每种表示都和它自己的数据一起保存,如图3-3所示。

图3-3  粘贴板及其表示

图片 2

一般情况下,为了最大化潜在的共享可能性,粘贴板数据项应该包括尽可能多的表示。

粘贴板的读取程序必须找到最适合自身能力(如果有的话)的数据类型。通常情况下,这意味着选择内涵最丰富的可用类型。举例来说,一个文本编辑器可能为被拷贝的数据提供HTML(富文本)和纯文本表示,支持富文本的应用程序应该选择HTML表示,而只支持纯文本的应用程序则应该选择纯文本的表示。

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

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。

变化记数

变化记数是每个粘贴板都有的变量,它随着每次粘贴板内容的变化而递增—特别是发生增加、修改、或移除数据项的时候。应用程序可以通过考察变化记数(通过changeCount属性)来确定粘贴板的当前数据是否和最后一次取得的数据相同。每次变化记数递增时,粘贴板都会向对此感兴趣的观察者发送通告。

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

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

选择和菜单管理

在拷贝或剪切视图中的某些内容之前,必须首先选择“某些内容”。它可能是一些文本、一个图像、一个URL、一种颜色、或者其它类型的数据,包括定制对象。为了在定制视图中实现拷贝-和-粘贴行为,您必须自行管理该视图中对象的选择。如果用户通过特定的触摸手势(比如双击)来选择视图中的对象,您就必须处理该事件,即在程序内部记录该选择(同时取消之前的选择),可能还要在视图中指示新的选择。如果用户可以在视图中选择多个对象,然后进行拷贝-剪切-粘贴操作,您就必须实现多选的行为。

请注意:触摸事件及其处理技巧在“触摸事件”部分进行讨论。

当应用程序确定用户请求了编辑菜单时—可能就是一个选择的动作—您应该执行下面的步骤来显示菜单:

程序清单3-4演示了如何在touchesEnded:withEvent:方法的实现中显示编辑菜单(注意,例子中省略了处理选择的代码)。在这个代码片段中,定制视图还向自己发送一个becomeFirstResponder消息,确保自己在随后的拷贝、剪切、和粘贴操作中是第一响应者。

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

程序清单3-4  显示编辑菜单

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *theTouch = [touches anyObject];
 
    if ([theTouch tapCount] == 2  && [self becomeFirstResponder]) {
 
        // selection management code goes here...
 
        // bring up editing menu.
        UIMenuController *theMenu = [UIMenuController sharedMenuController];
        CGRect selectionRect = CGRectMake(currentSelection.x, currentSelection.y, SIDE, SIDE);
        [theMenu setTargetRect:selectionRect inView:self];
        [theMenu setMenuVisible:YES animated:YES];
 
    }
}

初始的菜单包含所有的命令,因此第一响应者提供了相应的UIResponderStandardEditActions方法的实现(copy:paste:等)。但是在菜单被显示之前,系统会向第一响应者发送一个canPerformAction:withSender:消息。在很多情况下,第一响应者就是定制视图的本身。在该方法的实现中,响应者考察给定的命令(由第一个参数传入的选择器表示)是否适合当前的上下文。举例来说,如果该选择器是paste:,而粘贴板上没有该视图可以处理的数据,则响应者应该返回NO,以便禁止粘贴命令。如果第一响应者没有实现canPerformAction:withSender:方法,或者没有处理给定的命令,该消息就会进入响应者链。

程序清单3-5展示了canPerformAction:withSender:方法的一个实现。该实现首先寻找和copy:copy:、及paste:选择器相匹配的消息,并根据当前选择的上下文激活或禁用拷贝、剪切、和粘贴菜单命令。对于粘贴命令,还考虑了粘贴板的内容。

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

程序清单3-5  有条件地激活菜单命令

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
    BOOL retValue = NO;
    ColorTile *theTile = [self colorTileForOrigin:currentSelection];
 
    if (action == @selector(paste:) )
        retValue = (theTile == nil) &&
             [[UIPasteboard generalPasteboard] containsPasteboardTypes:
             [NSArray arrayWithObject:ColorTileUTI]];
    else if ( action == @selector(cut:) || action == @selector(copy:) )
        retValue = (theTile != nil);
    else
        retValue = [super canPerformAction:action withSender:sender];
    return retValue;
}

请注意,这个方法的最后一个else子句调用了超类的实现,使超类有机会处理子类忽略的命令。

还要注意,操作一个菜单命令可能会改变其它菜单命令的上下文。比如,当用户选择视图中的所有对象时,拷贝和剪切命令就应该被包含在菜单中。在这种情况下,虽然菜单仍然可见,但是响应者可以调用菜单控制器的update方法,使第一响应者的canPerformAction:withSender:再次被调用。

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

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

拷贝和剪切选定的内容

当用户触碰编辑菜单上的拷贝或剪切命令时,系统会分别调用响应者对象的copy:cut:方法。通常情况下,第一响应者—也就是您的定制视图—会实现这些方法,但如果没有实现的话,该消息会按正常的方式进入响应者链。请注意,UIResponderStandardEditActions非正式协议声明了这些方法。

请注意:由于UIResponderStandardEditActions是非正式协议,应用程序中的任何类都可以实现它的方法。但是,为了使命令可以按缺省的方式在响应者链上传递,实现这些方法的类应该继承自UIResponder类,且应该被安装到响应者链中。

copy:cut:消息的响应代码中,您需要把和选定内容相对应的对象或数据以尽可能多的表示形式写入到粘贴板上。这个操作涉及到如下这些步骤(假定只有一个的粘贴板数据项):

程序清单3-6展示了copy:cut:方法的一个实现。cut:方法调用了copy:方法,然后从视图和数据模型中移除选定的对象。注意,copy:方法对定制对象进行归档,目的是得到一个NSData对象,以便作为参数传递给粘贴板的setData:forPasteboardType:方法。

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

程序清单3-6  拷贝和剪切操作

- (void)copy:(id)sender {
    UIPasteboard *gpBoard = [UIPasteboard generalPasteboard];
    ColorTile *theTile = [self colorTileForOrigin:currentSelection];
    if (theTile) {
        NSData *tileData = [NSKeyedArchiver archivedDataWithRootObject:theTile];
        if (tileData)
            [gpBoard setData:tileData forPasteboardType:ColorTileUTI];
    }
}
 
- (void)cut:(id)sender {
     [self copy:sender];
     ColorTile *theTile = [self colorTileForOrigin:currentSelection];
 
     if (theTile) {
         CGPoint tilePoint = theTile.tileOrigin;
         [tiles removeObject:theTile];
          CGRect tileRect = [self rectFromOrigin:tilePoint inset:TILE_INSET];
         [self setNeedsDisplayInRect:tileRect];
     }
}

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

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

粘贴选定内容

当用户触碰编辑菜单上的粘贴命令时,系统会调用响应者对象的paste:方法。通常情况下,第一响应者—也就是您的定制视图—会实现这些方法,但如果没有实现的话,该消息会按正常的方式进入响应者链。paste:方法在UIResponderStandardEditActions非正式协议中声明。

paste: 消息的响应代码中,您可以从粘贴板中读取应用程序支持的表示,然后将被粘贴对象加入到应用程序的数据模型中,并将新对象显示在用户指定的视图位置上。这个操作涉及到如下这些步骤(假定只有单一的粘贴板数据项):

程序清单3-7是paste:方法的一个实现实例,该方法执行与cut:copy:方法相反的操作。示例中的视图首先确认粘贴板是否包含自身支持的定制表示数据,如果是的话,就读取该数据并将它加入到应用程序的数据模型中,然后将视图的一部分—当前选定区域—标识为需要重画。

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

程序清单3-7  将粘贴板的数据粘贴到选定位置上

- (void)paste:(id)sender {
     UIPasteboard *gpBoard = [UIPasteboard generalPasteboard];
     NSArray *pbType = [NSArray arrayWithObject:ColorTileUTI];
     ColorTile *theTile = [self colorTileForOrigin:currentSelection];
     if (theTile == nil && [gpBoard containsPasteboardTypes:pbType]) {
 
        NSData *tileData = [gpBoard dataForPasteboardType:ColorTileUTI];
        ColorTile *theTile = (ColorTile *)[NSKeyedUnarchiver unarchiveObjectWithData:tileData];
         if (theTile) {
             theTile.tileOrigin = self.currentSelection;
             [tiles addObject:theTile];
             CGRect tileRect = [self rectFromOrigin:currentSelection inset:TILE_INSET];
             [self setNeedsDisplayInRect:tileRect];
         }
     }
}

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

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

消除编辑菜单

在您实现的cut:copy:、或paste:命令返回后,编辑菜单会被自动隐藏。通过下面的代码使它保持可见:

[UIMenuController setMenuController].menuVisible = YES;

系统可能在任何时候隐藏编辑菜单,比如当显示警告信息或用户触碰屏幕其它区域时,编辑菜单就会被隐藏。如果您有某些状态或屏幕显示需要依赖于编辑菜单是否显示的话,就应该侦听UIMenuControllerWillHideMenuNotification通告,并执行恰当的动作。

 

原文地址:

 

拷贝、剪切和粘贴理论基础(转),ios理论基础 简介 在iPhone OS 3.0之后,用户可以在一个应用程序上拷贝文本、图像、或其它数据,然后...

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

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

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

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

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

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

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

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

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

图片 3overlay 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即可。

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属性。

图片 4webview释放代码示例

管理键盘

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

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

图片 5HTML元素控制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中实现了复制-剪切-粘贴,如果想使用这些特性,可以使用这些类,或者自己实现这些特性。这部分接下来的部分描述实现这些操作的接口。

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提供了一种识别数据的能用方法。如果想支持自定义类型,可以使用反DNS记号规则,比如com.jeff.pastetype,具体可参考Uniform Type Identifiers Overview。

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

图片 6剪贴板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到菜单中,并通过target-action来完成action操作,当然,为了action能够在responder chain中找到,通常需要将你的view becomeFirstResponder,因为UIMenuItem的形式是[[UIMenuItem alloc] initWithTitle:@"Change Color" action:@selector(changeColor:)],随后将其放置进menu controller的menuItems中。

[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类提供文本存储和布局功能。

图片 7Text Kit框架层级图图片 8基本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

图片 9attributed 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,需要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是一个使用诸如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替换。

图片 10激活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:所请求的区域与这些路径包围的区域重叠的时候,会返回调整过的区域。

图片 11行fragment适配

指定多页和多列布局

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

图片 12单文本流的对象配置

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

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

图片 13分页文本配置

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

图片 14多列文本对象配置

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

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

图片 15同一段文本的多个布局

更底层的文本处理技术

core text先等一等

本文由新葡京8455发布,转载请注明来源

关键词: