Text Programming Guide for iOS - 06 - Copy, Cut, and Paste Operations

363 阅读12分钟

复制、剪切和粘贴操作

用户可以在一个应用程序中复制文本、图像或其他数据,并将这些数据粘贴到同一应用程序或不同应用程序中的另一个位置。UIKit框架在UITextView和UITextField类中实现了复制-剪切-粘贴。如果您希望在自己的应用程序中使用此行为,则可以使用这些类的对象或自己实现复制-剪切-粘贴。

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

注意:有关复制粘贴操作的使用指南,请参见《iOS人机界面指南》中的“支持复制粘贴”。

UIKit中的复制粘贴操作

UIKit框架的几个类和一个协议为您提供了在应用程序中实现复制、剪切和粘贴操作所需的方法和机制:

  • UIPasteboard类提供了pasteboards:用于在应用程序内或应用程序之间共享数据的受保护区域。这个类提供了在粘贴板上读写数据项的方法。
  • UIMenuController类在要复制、剪切或粘贴到其中的选择上方或下方显示一个编辑菜单。编辑菜单的默认命令(可能)是Copy、Cut、Paste、Select和Select All。您还可以将自定义菜单项添加到“编辑”菜单中(请参阅Adding Custom Items to the Edit Menu)。
  • UIResponder类声明了canPerformAction:withSender:方法。Responder类可以实现此方法,以根据当前上下文显示和删除编辑菜单的命令。

UIResponderStandardEditActions协议声明了处理copy, cut, paste, select和select-all命令的接口。当用户点击edit菜单中的一个命令时,相应的UIResponderStandardEditActions方法会被调用。

剪贴板概念

剪贴板是一种标准化的机制,用于在应用程序内部或应用程序之间交换数据。粘贴板最常见的用法是处理复制、剪切和粘贴操作:

  • 当用户在应用程序中选择数据并选择复制(或剪切)菜单命令时,所选数据将被放置到剪贴板上。
  • 当用户选择粘贴菜单命令(无论是在同一或不同的应用程序),剪贴板上的数据是从剪贴板复制到当前应用程序。

命名剪贴板

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

  • UIPasteboardNameGeneral用于剪切、复制和粘贴操作,涉及很多数据类型。您可以通过调用generalPasteboard类方法获得一个表示通用剪贴板的单例对象。
  • UIPasteboardNameFind用于搜索操作。用户当前在搜索栏(UISearchBar)中键入的字符串被写入这个剪贴板,因此可以在应用程序之间共享。你可以通过调用pasteboardWithName:create: 类方法获得一个表示查找剪贴板的对象,为名称传递UIPasteboardNameFind。

通常你使用一个系统定义的剪贴板,但是如果有必要你可以使用pasteboardWithName:create:如果你调用pasteboardWithUniqueName, UIPasteboard给你一个唯一命名的应用剪贴板。您可以通过其name属性发现剪贴板的名称。

剪贴板持久性

剪贴板可以是持久的。系统剪贴板是持久的。虽然应用剪贴板默认情况下不是持久化的,但应用可以通过将persistent属性设置为YES来将其标记为持久化。不持久的应用剪贴板只能持续到拥有(创建)的应用退出。当创建它的应用被卸载时,一个持久的应用剪贴板就会被删除。

剪贴板所有者和项目

最后将数据放到剪贴板上的对象称为剪贴板所有者。放置在剪贴板上的每一块数据都被认为是一个剪贴板项。剪贴板可以容纳单个或多个项目。应用程序可以放置或检索任意数量的项。例如,假设视图中的用户选择同时包含文本和图像。剪贴板允许您将文本和图像作为单独的项目复制到剪贴板上。从剪贴板上读取多个项目的应用程序可以选择只读取它支持的项目(例如文本,但不包括图像)。

重要提示:当应用程序将数据写入剪贴板时,即使它只是一个单独的项目,该数据也会替换剪贴板的当前内容。虽然你可以使用UIPasteboard的addItems:方法来附加项目,但是类的write方法不会将项目附加到剪贴板的当前内容上。

Representations and UTIs

剪贴板操作通常在两个不同的应用程序之间进行。这两个应用程序都不需要了解对方,包括它可以处理的数据类型。为了最大限度地发挥共享的潜力,剪贴板可以保存同一剪贴板项的多个表示形式。例如,富文本编辑器可能提供复制数据的HTML、PDF和纯文本表示。剪贴板上的项包括应用程序可以提供的该数据项的所有表示形式。

剪贴板项目的每个表示通常由唯一类型标识符(UTI)标识。(UTI只是一个唯一标识特定数据类型的字符串。)UTI提供了一种识别数据类型的通用方法。如果您希望支持自定义数据类型,则必须为其创建唯一标识符。为此,你可以对表示类型的字符串使用反向dns表示法,以确保唯一性;例如,一个自定义的表示类型可以是com.myCompany.myApp.myType。有关UTIs的更多信息,请参见统一类型标识符概述。

例如,假设一个应用程序支持选择富文本和图像。它可能希望在剪贴板上放置文本选择的富文本和Unicode版本以及图像选择的不同表示。每个元素的每种表示形式都有自己的数据,如图5-1所示。

Figure 5-1  Pasteboard items and representations

Pasteboard items and representations

一般来说,为了最大限度地发挥共享的潜力,剪贴板项目应该包括尽可能多的不同表示。

剪贴板阅读器必须找到最适合其功能的数据类型(如果有的话)。通常,这意味着选择可用的最丰富的类型。例如,文本编辑器可能为复制的文本数据提供HTML(富文本)和纯文本表示。支持富文本的应用程序应该检索HTML表示,而只支持纯文本的应用程序应该检索纯文本版本。

Change Count

更改计数是每个粘贴板的变量,每当剪贴板的内容发生更改时,即添加、修改或删除项时,它都会增加。通过检查更改计数(通过changeCount属性),应用程序可以确定剪贴板中的当前数据是否与上次接收到的数据相同。每次更改计数增加时,剪贴板都会向感兴趣的观察者发送通知。

第一步:确定选择并显示编辑菜单

如果你要复制、剪切或粘贴一些东西,你首先必须选择它。(粘贴操作通常会对一个空选区进行操作,比如插入符号,用于表示元素在集合中的位置。)在选择一个项目之后——并且在视觉上指示选择——应该显示edit菜单。“编辑”菜单是一个系统菜单,其中可能包含以下命令:复制、剪切、粘贴、选择和全选。edit菜单指向所选内容。当用户点击菜单项时,会调用相应的UIResponderStandardEditActions方法的实现(例如cut:或paste:)。

有关选择的更多信息以及学习如何显示和管理“编辑”菜单,请参见Managing the Selection and the Edit Menu

复制和切割选区

当用户点击编辑菜单中的Copy或Cut命令时,系统调用实现该命令的responder对象的Copy:或Cut:方法。通常情况下,第一个响应者——你的自定义视图——会实现这些方法,但如果第一个响应程序没有实现这些方法,消息就会按照通常的方式沿着响应程序链向上传递。注意,UIResponderStandardEditActions非正式协议声明了这些方法。

注意:因为UIResponderStandardEditActions是一个非正式的协议,你的应用程序中的任何类都可以实现它的方法。但是为了利用默认行为来遍历响应者链,实现方法的类应该继承自UIResponder,并且应该安装在响应者链中。

作为对copy:或cut:消息的响应,您将选择的对象或数据以尽可能多的不同表示形式写入剪贴板。该操作包括以下步骤(假设只有一个剪贴板项):

  1. 从选择中,识别或获取对象或与对象相对应的二进制数据。

二进制数据必须封装在NSData对象中。如果你要写另一种类型的对象到剪贴板,它必须是一个属性列表对象——也就是说,一个下列类之一的对象:NSString, NSArray, NSDictionary, NSDate, NSNumber,或NSURL。(有关属性列表对象的更多信息,请参见属性列表编程指南。)

  1. 如果可能,生成一个或多个对象或数据的其他表示。

例如,如果在上一步中你创建了一个表示选定图像的UIImage对象,你可以使用UIImageJPEGRepresentation和UIImagePNGRepresentation函数将图像转换为不同的表示。

3.获取一个剪贴板对象。

在许多情况下,这是通用的剪贴板,您可以通过generalPasteboard类方法获得它。

  1. 为写入剪贴板项的每个数据表示分配一个合适的UTI。

关于这个主题的讨论,请参见剪贴板概念。

  1. 将数据写入每个表示类型的第一个剪贴板项:
  • 写一个数据对象,发送一个setData:forPasteboardType:消息给剪贴板对象。
  • 写一个属性列表对象,发送一个setValue:forPasteboardType:消息给剪贴板对象。
  1. 如果命令是Cut (Cut: method),则从应用程序的数据模型中删除选区所代表的对象并更新视图。

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

Listing 5-1  Copying and cutting operations

- (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];
     }
}

粘贴选区

当用户点击编辑菜单中的Paste命令时,系统调用实现该命令的responder对象的Paste:方法。通常情况下,第一响应者——你的自定义视图——会实现这个方法,但如果第一响应者没有实现这个方法,消息就会按照通常的方式沿着响应程序传递。paste:方法是由UIResponderStandardEditActions非正式协议声明的。

作为对paste:消息的响应,你从剪贴板中以应用程序支持的形式读取一个对象。然后将粘贴的对象添加到应用程序的数据模型中,并在用户指定的位置的视图中显示新对象。该操作包括以下步骤(假设只有一个剪贴板项):

  1. 获取一个剪贴板对象。

在许多情况下,这是通用的剪贴板,您可以通过generalPasteboard类方法获得它。

  1. 通过调用containsPasteboardTypes:方法或pasteboardTypes方法,然后检查返回的类型数组,验证第一个剪贴板项是否包含应用程序可以处理的表示形式的数据。

请注意,您应该已经在canPerformAction:withSender:的实现中执行了这一步。

  1. 如果剪贴板的第一项包含应用程序可以处理的数据,调用以下方法之一来读取它:
  • dataForPasteboardType:要读取的数据被封装在NSData对象中。
  • valueForPasteboardType:如果要读取的数据被封装在一个属性列表对象中(参见复制和切割选区)。
  1. 将该对象添加到应用程序的数据模型中。
  2. 在用户指定的位置显示用户界面中对象的表示形式。

代码清单5-2是paste:方法的一个实现。它的作用与cut:和copy:方法的作用相反。自定义视图首先查看通用剪贴板是否保留其自定义的数据表示;如果是这样,它就会从剪贴板中读取数据,将其添加到应用程序的数据模型中,并标记自身的一部分(当前选择)以进行重绘。

Listing 5-2  Pasting data to a selection

- (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];
        }
    }
}

结束操作

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