簡介
在iphone OS 3.0之后,用戶可以在一個應用程序上拷貝文本、圖像、或其它數據,然后粘貼到當前或其它應用程序的不同位置上。比如,您可以從某個電子郵件中拷貝一個地址,然后粘貼到Contacts程序的地址域中。目前,UIKit框架在UITextView
、UITextField
、和UIWebView
類中實現了拷貝-剪切-粘貼支持。如果您希望在自己的應用程序中得到這個行為,可以使用這些類的對象,或者自行實現。
但在iOS7.0后,UIPasteboard由共享變為沙盒化了,UIPasteboard本無問題,但是開發者開始使用它來存儲標識符,和其他的相關app分享這些標識符的時候問題就出現了。有一個使用這種把戲的就是OpenUDID。在iOS7后,使用 +[UIPasteboard pasteboardWithName:create:]和 +[UIPasteboard pasteboardWithUniqueName]創建剪貼板,而且只對相同的app group可見,這樣就讓OpenUDID不那么有用了。
本文的下面部分將描述UIKit中用于拷貝、剪切、和粘貼操作的編程接口,并解釋其用法。
在iPhone OS 3.0之后,用戶可以在一個應用程序上拷貝文本、圖像、或其它數據,然后粘貼到當前或其它應用程序的不同位置上。比如,您可以從某個電子郵件中拷貝一個地址,然后粘貼到Contacts程序的地址域中。目前,UIKit框架在UITextView
、UITextField
、和UIWebView
類中實現了拷貝-剪切-粘貼支持。如果您希望在自己的應用程序中得到這個行為,可以使用這些類的對象,或者自行實現。
本文的下面部分將描述UIKit中用于拷貝、剪切、和粘貼操作的編程接口,并解釋其用法。
請注意:與拷貝和粘貼操作相關的使用指南,請參見iPhone人機界面指南文檔中的“支持拷貝和粘貼”部分。
UIKit框架提供幾個類和一個非正式協議,用于為應用程序中的拷貝、剪切、和粘貼操作提供方法和機制。具體如下:
UIPasteboard
類提供了粘貼板的接口。粘貼板是用于在一個應用程序內或不同應用程序間進行數據共享的受保護區域。該類提供了讀寫剪貼板上數據項目的方法。
UIMenuController
類可以在選定的拷貝、剪切、和粘貼對象的上下方顯示一個編輯菜單。編輯菜單上的命令可以有拷貝、剪切、粘貼、選定、和全部選定。
UIResponder
類聲明了canPerformAction:withSender:
方法。響應者類可以實現這個方法,以根據當前的上下文顯示或移除編輯菜單上的命令。
UIResponderStandardEditActions
非正式協議聲明了處理拷貝、剪切、粘貼、選定、和全部選定命令的接口。當用戶觸碰編輯菜單上的某個命令時,相應的UIResponderStandardEditActions
方法就會被調用。
粘貼板是同一應用程序內或不同應用程序間交換數據的標準化機制。粘貼板最常見的的用途是處理拷貝、剪貼、和粘貼操作:
當用戶在一個應用程序中選定數據并選擇拷貝(或剪切)菜單命令時,被選擇的數據就會被放置在粘貼板上。
當用戶選擇粘貼命令時(可以在同一或不同應用程序中),粘貼板上的數據就會被拷貝到當前應用程序上。
在iPhone OS中,粘貼板也用于支持查找(Find)操作。此外,還可以用于在不同應用程序間通過定制的URL類型傳輸數據(而不是通過拷貝、剪切、和粘貼命令,關于這個技巧的信息請參見“和其它應用程序間的通訊”部分。
無論是哪種操作,您通過粘貼板執行的基本任務是讀寫粘貼板數據。雖然這些任務在概念上很簡單,但是它們屏蔽了很多重要的細節。復雜的原因主要在于數據的表現方式可能有很多種,而這個復雜性又引入了效率的考慮。本文的下面部分將對這些以及其它的問題進行討論。
粘貼板可能是公共的,也可能是私有的。公共粘貼板被稱為系統粘貼板;私有粘貼板則由應用程序自行創建,因此被稱為應用程序粘貼板。粘貼板必須有唯一的名字。UIPasteboard
定義了兩個系統粘貼板,每個都有自己的名字和用途:
UIPasteboardNameGeneral
用于剪切、拷貝、和粘貼操作,涉及到廣泛的數據類型。您可以通過該類的generalPasteboard
類方法來取得代表通用(General)粘貼板的單件對象。
UIPasteboardNameFind
用于檢索操作。當前用戶在檢索條(UISearchBar
)鍵入的字符串會被寫入到這個粘貼板中,因此可以在不同的應用程序中共享。您可以通過調用pasteboardWithName:create:
類方法,并在名字參數中傳入UIPasteboardNameFind
值來取得代表檢索粘貼板的對象。
典型情況下,您只需使用系統定義的粘貼板就夠了。但在必要時,您也可以通過pasteboardWithName:create:
方法來創建自己的應用程序粘貼板。如果您調用pasteboardWithUniqueName
方法,UIPasteboard
會為您提供一個具有唯一名稱的應用程序粘貼板。您可以通過其name
屬性聲明來取得這個名稱。
您可以將粘貼板標識為持久保留,使其內容在當前使用的應用程序終止后繼續存在。不持久保留的粘貼板在其創建應用程序退出后就會被移除。系統粘貼板是持久保留的,而應用程序粘貼板在缺省情況下是不持久保留的。將其應用程序粘貼板的persistent
屬性設置為YES
可以使其持久保留。當持久粘貼板的擁有者程序被用戶卸載時,其自身也會被移除。
最后將數據放到粘貼板的對象被稱為該粘貼板的擁有者。放到粘貼板上的每一片數據都稱為一個粘貼板數據項。粘貼板可以保有一個或多個數據項。應用程序可以放入或取得期望數量的數據項。舉例來說,假定用戶在視圖中選擇的內容包含一些文本和一個圖像,粘貼板允許您將文本和圖像作為不同的數據項進行拷貝。從粘貼板讀取多個數據項的應用程序可以選擇只讀取被支持的數據項(比如只是文本,而不支持圖像)。
重要提示:當一個應用程序將數據寫入粘貼板時,即使只是單一的數據項,該數據也會取代粘貼板的當前內容。雖然您可能使用UIPasteboard
的addItems:
方法來添加項目,但是該寫入方法并不會將那些項目加入到粘貼板當前內容之后。
粘貼板操作經常在不同的應用程序間執行。系統并不要求應用程序了解對方的信息,包括對方可以處理的數據種類。為了最大化潛在的數據分享能力,粘貼板可以保留同一個數據項的多種表示。例如,一個富文本編輯器可以提供被拷貝數據的HTML、PDF、和純文本表示。粘貼板上的一個數據項包括應用程序可為該數據提供的所有表示。
粘貼板數據項的每種表示通常都有一個唯一類型標識符(Unique Type Identifier,縮寫為UTI)。UTI簡單定義為一個唯一標識特定數據類型的字符串。UTI提供了一個標識數據類型的常用手段。如果您希望支持一個定制的數據類型,就必須為其創建一個唯一的標識符。為此,您可以用反向DNS表示法來定義類型標識字符串,以確保其唯一性。例如,您可以用com.myCompany.myApp.myType
來表示一個定制的類型標識。更多有關UTI的信息請參見統一類型標識符概述。
作為例子,假定一個應用程序支持富文本和圖像的選擇,它可能希望將富文本和Unicode版本的選定文本,以及選定圖像的不同表示放到粘貼板上。在這樣的場景下,每個數據項的每種表示都和它自己的數據一起保存,如圖3-3所示。
一般情況下,為了最大化潛在的共享可能性,粘貼板數據項應該包括盡可能多的表示。
粘貼板的讀取程序必須找到最適合自身能力(如果有的話)的數據類型。通常情況下,這意味著選擇內涵最豐富的可用類型。舉例來說,一個文本編輯器可能為被拷貝的數據提供HTML(富文本)和純文本表示,支持富文本的應用程序應該選擇HTML表示,而只支持純文本的應用程序則應該選擇純文本的表示。
變化記數是每個粘貼板都有的變量,它隨著每次粘貼板內容的變化而遞增—特別是發生增加、修改、或移除數據項的時候。應用程序可以通過考察變化記數(通過changeCount
屬性)來確定粘貼板的當前數據是否和最后一次取得的數據相同。每次變化記數遞增時,粘貼板都會向對此感興趣的觀察者發送通告。
在拷貝或剪切視圖中的某些內容之前,必須首先選擇“某些內容”。它可能是一些文本、一個圖像、一個URL、一種顏色、或者其它類型的數據,包括定制對象。為了在定制視圖中實現拷貝-和-粘貼行為,您必須自行管理該視圖中對象的選擇。如果用戶通過特定的觸摸手勢(比如雙擊)來選擇視圖中的對象,您就必須處理該事件,即在程序內部記錄該選擇(同時取消之前的選擇),可能還要在視圖中指示新的選擇。如果用戶可以在視圖中選擇多個對象,然后進行拷貝-剪切-粘貼操作,您就必須實現多選的行為。
請注意:觸摸事件及其處理技巧在“觸摸事件”部分進行討論。
當應用程序確定用戶請求了編輯菜單時—可能就是一個選擇的動作—您應該執行下面的步驟來顯示菜單:
調用UIMenuController
的sharedMenuController
類方法來取得全局的,即菜單控制器實例。
計算選定內容的邊界,并用得到的邊界矩形調用setTargetRect:inView:
方法。系統會根據選定內容與屏幕頂部和底部的距離,將編輯菜單顯示在該矩形的上方或下方。
調用setMenuVisible:animated:
方法(兩個參數都傳入YES
),在選定內容的上方或下方動畫顯示編輯菜單。
程序清單3-4演示了如何在touchesEnded:withEvent:
方法的實現中顯示編輯菜單(注意,例子中省略了處理選擇的代碼)。在這個代碼片段中,定制視圖還向自己發送一個becomeFirstResponder
消息,確保自己在隨后的拷貝、剪切、和粘貼操作中是第一響應者。
程序清單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:
選擇器相匹配的消息,并根據當前選擇的上下文激活或禁用拷貝、剪切、和粘貼菜單命令。對于粘貼命令,還考慮了粘貼板的內容。
程序清單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:
再次被調用。
當用戶觸碰編輯菜單上的拷貝或剪切命令時,系統會分別調用響應者對象的copy:
或cut:
方法。通常情況下,第一響應者—也就是您的定制視圖—會實現這些方法,但如果沒有實現的話,該消息會按正常的方式進入響應者鏈。請注意,UIResponderStandardEditActions
非正式協議聲明了這些方法。
請注意:由于UIResponderStandardEditActions
是非正式協議,應用程序中的任何類都可以實現它的方法。但是,為了使命令可以按缺省的方式在響應者鏈上傳遞,實現這些方法的類應該繼承自UIResponder
類,且應該被安裝到響應者鏈中。
在copy:
或cut:
消息的響應代碼中,您需要把和選定內容相對應的對象或數據以盡可能多的表示形式寫入到粘貼板上。這個操作涉及到如下這些步驟(假定只有一個的粘貼板數據項):
標識或取得和選定內容相對應的對象或二進制數據。
二進制數據必須封裝在NSData
對象中。其它可以寫入到粘貼板的對象必須是屬性列表對象—也就是說,必須是下面這些類的對象:NSString
、NSArray
、NSDictionary
、NSDate
、NSNumber
、或者NSURL
(有關屬性列表對象的更多信息,請參見屬性列表編程指南)。
可能的話,請為對象或數據生成一或多個其它的表示。
舉例來說,在之前提到的為選定圖像創建UIImage
對象的步驟中,您可以通過UIImageJPEGRePResentation
或UIImagePNGRepresentation
函數將圖像轉換為不同的表示。
取得粘貼板對象。
在很多情況下,使用通用粘貼板就可以了。您可以通過generalPasteboard
類方法來取得該對象。
為寫入到粘貼板數據項的每個數據表示分配一個合適的UTI。
這個主題的討論請參見“粘貼板的概念”部分。
將每種表示類型的數據寫入到第一個粘貼板數據項中:
向粘貼板對象發送setData:forPasteboardType:
消息可以寫入數據對象。
向粘貼板對象發送setValue:forPasteboardType:
消息可以寫入屬性列表對象。
cut:
方法)命令,需要從應用程序的數據模型中移除選定內容所代表的對象,并更新視圖。程序清單3-6展示了copy:
和cut:
方法的一個實現。cut:
方法調用了copy:
方法,然后從視圖和數據模型中移除選定的對象。注意,copy:
方法對定制對象進行歸檔,目的是得到一個NSData
對象,以便作為參數傳遞給粘貼板的setData:forPasteboardType:
方法。
程序清單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]; |
} |
} |
當用戶觸碰編輯菜單上的粘貼命令時,系統會調用響應者對象的paste:
方法。通常情況下,第一響應者—也就是您的定制視圖—會實現這些方法,但如果沒有實現的話,該消息會按正常的方式進入響應者鏈。paste:
方法在UIResponderStandardEditActions
非正式協議中聲明。
在paste:
消息的響應代碼中,您可以從粘貼板中讀取應用程序支持的表示,然后將被粘貼對象加入到應用程序的數據模型中,并將新對象顯示在用戶指定的視圖位置上。這個操作涉及到如下這些步驟(假定只有單一的粘貼板數據項):
取得粘貼板對象。
在很多情況下,使用通用粘貼板就可以了,您可以通過generalPasteboard
類方法來取得該對象。
確認第一個粘貼板數據項是否包含應用程序可以處理的表示,這可以通過調用containsPasteboardTypes:
方法,或者調用pasteboardTypes
方法并考察其返回的類型數組來實現。
請注意,您在canPerformAction:withSender:
方法的實現中應該已經執行過這個步驟。
如果粘貼板的第一個數據項包含應用程序可以處理的數據,則可以調用下面的方法來讀?。?/p>
dataForPasteboardType:
,如果要讀取的數據被封裝為NSData
對象,就可以使用這個方法。
valueForPasteboardType:
,如果要讀取的數據被封裝為屬性列表對象,請使用這個方法(請參見“拷貝和剪切選定的內容”部分)。
將對象加入到應用程序的數據模型中。
程序清單3-7是paste:
方法的一個實現實例,該方法執行與cut:
及copy:
方法相反的操作。示例中的視圖首先確認粘貼板是否包含自身支持的定制表示數據,如果是的話,就讀取該數據并將它加入到應用程序的數據模型中,然后將視圖的一部分—當前選定區域—標識為需要重畫。
程序清單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]; |
} |
} |
} |
在您實現的cut:
、copy:
、或paste:
命令返回后,編輯菜單會被自動隱藏。通過下面的代碼使它保持可見:
[UIMenuController setMenuController].menuVisible = YES; |
系統可能在任何時候隱藏編輯菜單,比如當顯示警告信息或用戶觸碰屏幕其它區域時,編輯菜單就會被隱藏。如果您有某些狀態或屏幕顯示需要依賴于編輯菜單是否顯示的話,就應該偵聽UIMenuControllerWillHideMenuNotification
通告,并執行恰當的動作。
原文地址:http://blog.sina.com.cn/s/blog_45e2b66c010102h9.html
新聞熱點
疑難解答