iOS 拷貝、剪下和粘貼理論基礎(轉),ios理論基礎
簡介
在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中支援拷貝-粘貼操作的設施
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:
方法來添加項目,但是該寫入方法並不會將那些項目加入到粘貼板當前內容之後。
資料的表示和UTI
粘貼板操作經常在不同的應用程式間執行。系統並不要求應用程式瞭解對方的資訊,包括對方可以處理的資料種類。為了最大化潛在的資料分享能力,粘貼板可以保留同一個資料項目的多種表示。例如,一個富文字編輯器可以提供被拷貝資料的HTML、PDF、和純文字表示。粘貼板上的一個資料項目包括應用程式可為該資料提供的所有表示。
粘貼板資料項目的每種表示通常都有一個唯一類型標識符(Unique Type Identifier,縮寫為UTI)。UTI簡單定義為一個唯一標識特定資料類型的字串。UTI提供了一個標識資料類型的常用手段。如果您希望支援一個定製的資料類型,就必須為其建立一個唯一的標識符。為此,您可以用反向DNS標記法來定義類型標識字串,以確保其唯一性。例如,您可以用com.myCompany.myApp.myType
來表示一個定製的類型標識。更多有關UTI的資訊請參見統一類型標識符概述。
作為例子,假定一個應用程式支援富文本和映像的選擇,它可能希望將富文本和Unicode版本的選定文本,以及選定映像的不同表示放到粘貼板上。在這樣的情境下,每個資料項目的每種表示都和它自己的資料一起儲存,3-3所示。
圖3-3 粘貼板及其表示
一般情況下,為了最大化潛在的共用可能性,粘貼板資料項目應該包括儘可能多的表示。
粘貼板的讀取程式必須找到最適合自身能力(如果有的話)的資料類型。通常情況下,這意味著選擇內涵最豐富的可用類型。舉例來說,一個文字編輯器可能為被拷貝的資料提供HTML(富文本)和純文字表示,支援富文本的應用程式應該選擇HTML表示,而只支援純文字的應用程式則應該選擇純文字的表示。
變化記數
變化記數是每個粘貼板都有的變數,它隨著每次粘貼板內容的變化而遞增—特別是發生增加、修改、或移除資料項目的時候。應用程式可以通過考察變化記數(通過changeCount
屬性)來確定粘貼板的當前資料是否和最後一次取得的資料相同。每次變化記數遞增時,粘貼板都會向對此感興趣的觀察者發送通告。
選擇和菜單管理
在拷貝或剪下視圖中的某些內容之前,必須首先選擇“某些內容”。它可能是一些文本、一個映像、一個URL、一種顏色、或者其它類型的資料,包括定製對象。為了在定製視圖中實現拷貝-和-粘貼行為,您必須自行管理該視圖中對象的選擇。如果使用者通過特定的觸摸手勢(比如雙擊)來選擇視圖中的對象,您就必須處理該事件,即在程式內部記錄該選擇(同時取消之前的選擇),可能還要在視圖中指示新的選擇。如果使用者可以在視圖中選擇多個對象,然後進行拷貝-剪下-粘貼操作,您就必須實現多選的行為。
請注意:觸摸事件及其處理技巧在“觸摸事件”部分進行討論。
當應用程式確定使用者請求了編輯菜單時—可能就是一個選擇的動作—您應該執行下面的步驟來顯示菜單:
程式清單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:
訊息的響應代碼中,您需要把和選定內容相對應的對象或資料以儘可能多的表示形式寫入到粘貼板上。這個操作涉及到如下這些步驟(假定只有一個的粘貼板資料項目):
程式清單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:
訊息的響應代碼中,您可以從粘貼板中讀取應用程式支援的表示,然後將被粘貼對象加入到應用程式的資料模型中,並將新對象顯示在使用者指定的視圖位置上。這個操作涉及到如下這些步驟(假定只有單一的粘貼板資料項目):
程式清單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