手指在螢幕上能達到的精度和滑鼠指標有很大的不同。當使用者觸擊螢幕時,接觸
地區實際上是橢圓形的,而且比使用者想像的位置更靠下一點。根據觸控螢幕幕的手指、手指的尺寸、手指接觸螢幕的力量、手指的方向、以及其它因素的不同,其“接觸部位”的尺寸和形狀也有所不同。底層的多點觸摸系統會分析所有的這些資訊,為您計算出單一的觸點。
UIResponder 是所有響應者對象的基類,
它不僅為事件處理,而且也為常見的響應者行為定義編程介面。UIApplication、UIView、和所有從UIView 派生出來的UIKit 類(包括UIWindow)都直接或間接地繼承自UIResponder類。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch* touch = [touches anyObject];
NSUInteger numTaps = [touch tapCount];
if (numTaps < 2) {
[self.nextResponder touchesBegan:touches withEvent:event];
} else {
[self handleDoubleTap:touch];
}
}
預設情況下,視圖會接收觸摸事件。但是,您可以將其userInteractionEnabled
屬性聲明設定為NO,關閉事件傳遞的功能。
在一定的時間內關閉事件的傳遞。應用程式可以調用UIApplication 的
beginIgnoringInteractionEvents 方法,並在隨後調用endIgnoringInteractionEvents 方法來實現這個目的。
預設情況下,視圖只接收多點觸摸序列的第一個觸摸事件,而忽略
所有其它事件。如果您希望視圖處理多點觸摸,就必須使它啟用這個功能。在代碼或Interface Builder 的查看器視窗中將視圖的multipleTouchEnabled 屬性設定為YES,就可以實現這個目標。
將事件傳遞限制在某個單獨的視圖上。預設情況下,視圖的exclusiveTouch 屬性被設定為NO。將這個屬性設定為YES 會使相應的視圖具有這樣的特性:即當該視圖正在跟蹤觸摸動作時,視窗中的其它視圖無法同時進行跟蹤,它們不能接收到那些觸摸事件。
多點觸摸:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
當一個或多個手指觸碰螢幕時,發送touchesBegan:withEvent:訊息。
當一個或多個手指在螢幕上移動時,發送touchesMoved:withEvent:訊息。
當一個或多個手指離開螢幕時,發送touchesEnded:withEvent:訊息。
當觸摸序列被諸如電話呼入這樣的系統事件所取消時,發送touchesCancelled:withEvent:訊息。
上面這些方法都和特定的觸摸階段(比如UITouchPhaseBegan)相關聯,該資訊存在於UITouch 對象的phase 屬性聲明中。
為了處理給定階段的事件,響應者對象常常從傳入的集合參數中取得一或多個UITouch 對象,然後考察這些對象的屬性或取得它們的位置(如果需要處理所有觸摸對象,可以向該NSSet 對象發送anyObject 訊息)。UITouch 類中有一個名為locationInView:的重要方法,如果傳入self 參數值,它會給出觸摸動作在響應者座標系統中的位置(假定該響應者是一個UIView 對象,且傳入的視圖參數不為nil)。另外,還有一個與之平行的方法,可以給出觸摸動作之前位置(previousLocationInView:)。UITouch 執行個體的屬性還可以給出發生多少次觸
碰(tapCount)、觸摸對象的建立或最後一次變化發生在什麼時間(timestamp)、以及觸摸處於什麼階段(phase)。
- (void) touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
{
UITouch *touch = [touches anyObject];
if ([touch tapCount] == 2) {
CGPoint tapPoint = [theTouch locationInView:self];
// Process a double-tap gesture
}
}
在touchesEnded:withEvent:方法中,當觸擊次數為一時,響應者對象就向自身發送一個performSelector:withObject:afterDelay:訊息,其中的選取器標識由響應者對象實現的、用於處理單擊手勢的方法;第二個參數是一個NSValue 或NSDictionary 對象,用於儲存相關的UITouch 對象;時延參數則表示單擊和雙擊手勢之間的合理時間間隔。
在touchesBegan:withEvent:方法中,如果觸擊次數為二,響應者對象會向自身發送一個cancelPreviousPerformRequestsWithTarget:訊息,取消當前被掛起和延期執行的調用。如果觸碰次數不為二,則在指定的延時之後,先前步驟中由選取器標識的方法就會被調用,以處理單擊手勢。
在視圖中跟蹤碰擦手勢
#define HORIZ_SWIPE_DRAG_MIN 12
#define VERT_SWIPE_DRAG_MAX 4
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
startTouchPosition = [touch locationInView:self];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint currentTouchPosition = [touch locationInView:self];
// If the swipe tracks correctly.
if (fabsf(startTouchPosition.x - currentTouchPosition.x) >= HORIZ_SWIPE_DRAG_MIN
&&
fabsf(startTouchPosition.y - currentTouchPosition.y) <=
VERT_SWIPE_DRAG_MAX)
{
// It appears to be a swipe.
if (startTouchPosition.x < currentTouchPosition.x)
[self myProcessRightSwipe:touches withEvent:event];
else
[self myProcessLeftSwipe:touches withEvent:event];
}
else
{
// Process a non-swipe event.
}
}
處理複雜的多點觸摸序列
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
// Only move the placard view if the touch was in the placard view
if ([touch view] != placardView) {
// On double tap outside placard view, update placard's display string
if ([touch tapCount] == 2) {
[placardView setupNextDisplayString];
}
return;
}
// "Pulse" the placard view by scaling up then down
// Use UIView's built-in animation
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.5];
CGAffineTransform transform = CGAffineTransformMakeScale(1.2, 1.2);
placardView.transform = transform;
[UIView commitAnimations];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.5];
transform = CGAffineTransformMakeScale(1.1, 1.1);
placardView.transform = transform;
[UIView commitAnimations];
// Move the placardView to under the touch
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.25];
placardView.center = [self convertPoint:[touch locationInView:self]
fromView:placardView];
[UIView commitAnimations];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
// If the touch was in the placardView, move the placardView to its location
if ([touch view] == placardView) {
CGPoint location = [touch locationInView:self];
location = [self convertPoint:location fromView:placardView];
placardView.center = location;
return;
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
// If the touch was in the placardView, bounce it back to the center
if ([touch view] == placardView) {
// Disable user interaction so subsequent touches don't interfere with animation
self.userInteractionEnabled = NO;
[self animatePlacardViewToCenter];
return;
}
}
在事件處理代碼中,您可以將觸摸狀態的相關位置儲存下來,以便在必要時和變化之後的UITouch 執行個體進行比較。定製視圖可以用UIView 的hitTest:withEvent:方法或CALayer 的hitTest:方法來尋找接收觸摸事件的子視圖或層,進而正確地處理事件。下面的例子用於檢測定製視圖的層中的“Info” 映像是否被觸碰。
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
CGPoint location = [[touches anyObject] locationInView:self];
CALayer *hitLayer = [[self layer] hitTest:[self convertPoint:location
fromView:nil]];
if (hitLayer == infoImage) {
[self displayInfo];
}
}
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
if ([touches count] == [[event touchesForView:self] count]) {
// last finger has lifted....
}
}
觸摸事件中包含一個觸摸對象的集合及其相關的狀態,而運動事件中除了事件類型、子類型、和時間戳記之外,沒有其它狀態。系統以這種方式來解析運動手勢,避免和方向變化事件造成衝突。
為了處理運動事件,UIResponder 的子類必須實現motionBegan:withEvent: 或
motionEnded:withEvent:方法之一,或者同時實現這兩個方法
UIKit 架構在UITextView、UITextField、和UIWebView 類中實現了拷貝-剪下-粘貼支援。如果您希望在自己的應用程式中得到這個行為,可以使用這些類的對象,或者自行實現。
UIPasteboard 類提供了粘貼板的介面。粘貼板是用於在一個應用程式內或不同應用程式間進行資料共用的受保護地區。該類提供了讀寫剪貼簿上資料項目的方法。
UIMenuController 類可以在選定的拷貝、剪下、和粘貼對象的上下方顯示一個編輯菜單。編輯菜單上的命令可以有拷貝、剪下、粘貼、選定、和全部選定。
UIResponder 類聲明了canPerformAction:withSender:方法。響應者類可以實現這個方法,以根據當前的上下文顯示或移除編輯菜單上的命令。
UIResponderStandardEditActions 非正式協議聲明了處理拷貝、剪下、粘貼、選定、和全部選定命令的介面。當使用者觸碰編輯菜單上的某個命令時, 相應的
UIResponderStandardEditActions 方法就會被調用。
UIPasteboardNameGeneral 用於剪下、拷貝、和粘貼操作,涉及到廣泛的資料類型。您可以通過該類的generalPasteboard 類方法來取得代表通用(General)粘貼板的單件對象。
UIPasteboardNameFind 用於檢索操作。目前使用者在檢索條(UISearchBar)鍵入的字串會被寫入到這個粘貼板中, 因此可以在不同的應用程式中共用。您可以通過調用pasteboardWithName:create:類方法,並在名字參數中傳入UIPasteboardNameFind 值來取得代表檢索粘貼板的對象。典型情況下, 您只需使用系統定義的粘貼板就夠了。但在必要時, 您也可以通過pasteboardWithName:create: 方法來建立自己的應用程式粘貼板。如果您調用pasteboardWithUniqueName 方法,UIPasteboard 會為您提供一個具有唯一名稱的應用程式粘貼板。您可以通過其name 屬性聲明來取得這個名稱。
調用UIMenuController 的sharedMenuController 類方法來取得全域的,即菜單控制器執行個體。
計算選定內容的邊界,並用得到的邊界矩形調用setTargetRect:inView:方法。系統會根據選定內容與螢幕頂部和底部的距離,將編輯菜單顯示在該矩形的上方或下方。
調用setMenuVisible:animated:方法(兩個參數都傳入YES),在選定內容的上方或下方動畫顯示編輯菜單。
程式清單3-4示範了如何在touchesEnded:withEvent:方法的實現中顯示編輯菜單(注意,例子中省略了處理選擇的代碼)。在這個程式碼片段中, 定製視圖還向自己發送一個becomeFirstResponder 訊息,確保自己在隨後的拷貝、剪下、和粘貼操作中是第一響應者。
- (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];
}
}
有條件地啟用功能表命令
- (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 子句調用了超類的實現,使超類有機會處理子類忽略的命令。
拷貝和剪下操作
- (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];
}
}
將粘貼板的資料粘貼到選定位置上
- (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];
}
}
}
Quartz 是主要的描畫介面,支援基於路徑的描畫、
消除鋸齒渲染、漸層填充模式、映像、顏色、座標空間變換、以及PDF 文檔的建立、顯示、和分析。UIKit 為Quartz 的映像和顏色操作提供了Objective-C 的封裝。Core Animation 為很多UIKit 的視圖屬性聲明的動畫效果提供底層支援,也可以用於實現定製的動畫。
在調用您提供的drawRect:方法之前,視圖對象會自動設定其描畫環境,使您的代碼可以立即進行描畫。作為這些配置的一部分,UIView 對象會為當前描畫環境建立一個圖形上下文(對應於CGContextRef 封裝類型)
使用者座標空間是您發出的所有描畫命令的工作環境。該空間的單位由點來表示。裝置座標空間指的是裝置內在的座標空間,由像素來表示。預設情況下,使用者座標空間上的一個點等於裝置座標空間的一個像素,這意味著一個點等於1/160英寸。然而,您不應該假定這個比例總是1:1。
UIColor 對象提供了一些便利方法,用於通過RGB、HSB、和灰階值指定顏色值。
您也可以使用Core Graphics 架構中的CGContextSetRGBStrokeColor 和
CGContextSetRGBFillColor 函數來建立和設定顏色。
可移植網狀圖像格式(PNG) .png
TIFF 格式(TIFF) .tiff, .tif
聯合影像專家組格式(JPEG) .jpeg, .jpg
圖形交換格式(GIF) .gif
視窗位元影像格式(DIB) .bmp, .BMPf
視窗表徵圖格式.ico
視窗游標.cur
XWindow 位元影像.xbm
UIImage, 一個不可變類,用於映像顯示。
UIColor, 為裝置顏色提供基本的支援。
UIFont, 為需要字型的類提供字型資訊。
UIScreen, 提供螢幕的基本資料。
NSString* imagePath = [[NSBundle mainBundle] pathForResource:@"myImage"
ofType:@"png"];
UIImage* myImageObj = [[UIImage alloc] initWithContentsOfFile:imagePath];
- (void)drawRect:(CGRect)rect
{
// Draw the image
[anImage drawAtPoint:CGPointMake(10,10)];
}
重要提示:如果您使用CGContextDrawImage 函數來直接描畫位元影像,則在預設情況下,映像資料會上下倒置,因為Quartz 映像假定座標系統的原點在左下角,且座標軸的正向是向上和向右。雖然您可以在描畫之前對其進行轉換,但是將Quartz 映像封裝為一個UIImage 對象是更簡單的方法,這樣可以自動補償座標空間的差別。
UIKit 中的UIRectFrame 和UIRectFill 函數(以及其它函數)的功能是在視圖中描畫象矩形這樣的簡單路徑。
在建立路徑時,需要首先通過CGContextBeginPath 函數配置一個接收路徑命令的圖形上下文。調用該函數之後,就可以使用與路徑相關的函數來設定路徑的起始點,描畫直線和曲線,加入矩形和橢圓形等等。路徑的幾何形狀指定完成後,就可以直接進行描畫,或者將其引用儲存在CGPathRef 或CGMutablePathRef 資料類型中,以備後用。在視圖上描畫路徑時,可以描畫輪廓,也可以進行填充,或者同時進行這兩種操作。路徑輪廓可以用像CGContextStrokePath 這樣的函數來畫,即用當前的筆劃顏色畫出以路徑為中心位置的線。路徑的填充則可以用CGContextFillPath 函數來實現,它的功能是用當前的填充顏色或樣式填充路徑線段包圍的地區。
Core Animation 的關鍵技術是層對象。層是一種輕量級的對象,在本質上類似於視圖,但實際上是模型對象,負責封裝顯示內容的幾何屬性、顯示時機、和視覺屬性變數。內容本身可以通過如下三種方式來提供:
您可以將一個CGImageRef 類型的資料賦值給層對象的contents 屬性變數。
您可以為層分配一個委託,讓它負責描畫工作。
您可以從CALayer 派生出子類,並對其顯示方法進行重載。
UIKit 架構提供三個顯示常值內容的基本類:
UILabel 顯示靜態文本字串
UITextField 顯示單行可編輯文本
UITextView 顯示多行可編輯文本
為UITextField類提供支援的委託需要實現UITextFieldDelegate 協議定義的方法。類似地,為UITextView類提供支援的委託需要實現UITextViewDelegate 協議定義的方法。
當鍵盤被顯示或隱藏的時候,iPhone OS 會向所有經過註冊的觀察者對象發出如下通告:
UIKeyboardWillShowNotification
UIKeyboardDidShowNotification
UIKeyboardWillHideNotification
UIKeyboardDidHideNotification
您可以通過調用視圖對象的becomeFirstResponder 方法來為可編輯的文本視圖顯
示鍵盤。調用這個方法可以使目標視圖成為第一響應者,並開始編輯過程,其效果和使用者觸擊該視圖是一樣的。
需要做的調整通常包括暫時調整一或多個視圖的尺寸和位置,從而使文字物件可見。管理帶
有鍵盤的文字物件的最簡單方法是將它們嵌入到一個UIScrollView ( 或其子類,如UITableView)對象。當鍵盤被顯示出來時,您需要做的只是調整滾動視圖的尺寸,並將目標文字物件滾動到合適的位置。為此,在UIKeyboardDidShowNotification 通告的處理代碼中需要進行如下操作:
取得鍵盤的尺寸。
將滾動視圖的高度減去鍵盤的高度。
將目標文字框滾動到視圖中。
在配置滾動視圖時,請務必為所有的內容視圖配置恰當的自動尺寸調整規則。在之
前的圖中,文字框實際上是一個UIView 對象的子視圖,該UIView 對象又是UIScrollView對象的子視圖。如果該UIView 對象的UIViewAutoresizingFlexibleWidth 和UIViewAutoresizingFlexibleHeight 選項被設定了,則改變滾動視圖的邊框尺寸會同時改變它的邊框,因而可能導致不可預料的結果。禁用這些選項可以確保該視圖保持尺寸不變,並正確滾動。
處理鍵盤通告
// Call this method somewhere in your view controller setup code.
- (void)registerForKeyboardNotifications
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWasShown:)
name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWasHidden:)
name:UIKeyboardDidHideNotification object:nil];
}
// Called when the UIKeyboardDidShowNotification is sent.
- (void)keyboardWasShown:(NSNotification*)aNotification
{
if (keyboardShown)
return;
NSDictionary* info = [aNotification userInfo];
// Get the size of the keyboard.
NSValue* aValue = [info
objectForKey:UIKeyboardBoundsUserInfoKey];
CGSize keyboardSize = [aValue CGRectValue].size;
// Resize the scroll view (which is the root view of the window)
CGRect viewFrame = [scrollView frame];
viewFrame.size.height -= keyboardSize.height;
scrollView.frame = viewFrame;
// Scroll the active text field into view.
CGRect textFieldRect = [activeField frame];
[scrollView scrollRectToVisible:textFieldRect animated:YES];
keyboardShown = YES;
}
// Called when the UIKeyboardDidHideNotification is sent
- (void)keyboardWasHidden:(NSNotification*)aNotification
{
NSDictionary* info = [aNotification userInfo];
// Get the size of the keyboard.
NSValue* aValue = [info
objectForKey:UIKeyboardBoundsUserInfoKey];
CGSize keyboardSize = [aValue CGRectValue].size;
// Reset the height of the scroll view to its original value
CGRect viewFrame = [scrollView frame];
viewFrame.size.height += keyboardSize.height;
scrollView.frame = viewFrame;
keyboardShown = NO;
}
跟蹤活動文字框的方法
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
activeField = textField;
}
- (void)textFieldDidEndEditing:(UITextField *)textField
{
activeField = nil;
}