觸摸事件 iOS中的事件: 在用戶使用app過程中,會產生各種各樣的事件。iOS中的事件可以分為3大類型: view的觸摸事件處理: 響應者對象: 在iOS中不是任何對象都能處理事件,只有繼承了UIResponder的對象才能接收並處理事件。我們稱之為“響應者對象”。 UIApplication、U
觸摸事件
iOS中的事件:
在用戶使用app過程中,會產生各種各樣的事件。iOS中的事件可以分為3大類型:
view的觸摸事件處理:
響應者對象:
在iOS中不是任何對象都能處理事件,只有繼承了UIResponder的對象才能接收並處理事件。我們稱之為“響應者對象”。
UIApplication、UIViewController、UIView都繼承自UIResponder,因此它們都是響應者對象,都能夠接收並處理事件。
UIResponder:
UIResponder內部提供了以下方法來處理事件:
// 觸摸事件
- (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;
// 加速計事件
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event;
// 遠程式控制制事件
- (void)remoteControlReceivedWithEvent:(UIEvent *)event;
UIView的觸摸事件處理:
UIView是UIResponder的子類,可以覆蓋下列4個方法處理不同的觸摸事件:
/* 一根或者多根手指開始觸摸view,系統會自動調用view的下麵方法 */
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
/* 一根或者多根手指在view上移動,系統會自動調用view的下麵方法(隨著手指的移動,會持續調用該方法) */
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
/* 一根或者多根手指離開view,系統會自動調用view的下麵方法 */
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
/* 觸摸結束前,某個系統事件(例如電話呼入)會打斷觸摸過程,系統會自動調用view的下麵方法 */
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
// 提示:touches中存放的都是UITouch對象
UITouch:
當用戶用一根手指觸摸屏幕時,會創建一個與手指相關聯的UITouch對象。一根手指對應一個UITouch對象。
UITouch的作用:
保存著跟手指相關的信息,比如觸摸的位置、時間、階段。
當手指移動時,系統會更新同一個UITouch對象,使之能夠一直保存該手指在的觸摸位置。當手指離開屏幕時,系統會銷毀相應的UITouch對象。
提示:iPhone開發中,要避免使用雙擊事件!
UITouch的屬性:
/* 觸摸產生時所處的視窗 */
@property(nonatomic,readonly,retain) UIWindow *window;
/* 觸摸產生時所處的視圖 */
@property(nonatomic,readonly,retain) UIView *view;
/* 短時間內點按屏幕的次數,可以根據tapCount判斷單擊、雙擊或更多的點擊 */
@property(nonatomic,readonly) NSUInteger tapCount;
/*記錄了觸摸事件產生或變化時的時間,單位是秒 */
@property(nonatomic,readonly) NSTimeInterval timestamp;
/* 當前觸摸事件所處的狀態 */
@property(nonatomic,readonly) UITouchPhase phase;
UITouch的方法:
/*
* 返回值表示觸摸在view上的位置
* 這裡返回的位置是針對view的坐標系的(以view的左上角為原點(0, 0))
* 調用時傳入的view參數為nil的話,返回的是觸摸點在UIWindow的位置
*/
- (CGPoint)locationInView:(UIView *)view;
/* 該方法記錄了前一個觸摸點的位置 */
- (CGPoint)previousLocationInView:(UIView *)view;
UIEvent:
每產生一個事件,就會產生一個UIEvent對象。
UIEvent:稱為事件對象,記錄事件產生的時刻和類型。
UIEvent常見屬性:
// 事件類型
@property(nonatomic,readonly) UIEventType type;
@property(nonatomic,readonly) UIEventSubtype subtype;
// 事件產生的時間
@property(nonatomic,readonly) NSTimeInterval timestamp;
UIEvent還提供了相應的方法可以獲得在某個view上面的觸摸對象(UITouch)。
touches和event參數:
一次完整的觸摸過程,會經歷3個狀態:
觸摸開始:- (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
4個觸摸事件處理方法中,都有NSSet *touches
和UIEvent *event
兩個參數。
一次完整的觸摸過程中,只會產生一個事件對象,4個觸摸方法都是同一個event參數。
如果兩根手指同時觸摸一個view,那麼view只會調用一次touchesBegan:withEvent:
方法,touches參數中裝著2個UITouch對象。
如果這兩根手指一前一後分開觸摸同一個view,那麼view會分別調用2次touchesBegan:withEvent:
方法,並且每次調用時的touches參數中只包含一個UITouch對象。
根據touches中UITouch的個數可以判斷出是單點觸摸還是多點觸摸。
事件的產生和傳遞:
發生觸摸事件後,系統會將該事件加入到一個由UIApplication
管理的事件隊列中。
UIApplication
會從事件隊列中取出最前面的事件,並將事件分發下去以便處理,通常,先發送事件給應用程式的主視窗(keyWindow)。
主視窗會在視圖層次結構中找到一個最合適的視圖來處理觸摸事件,這也是整個事件處理過程的第一步。
找到合適的視圖控制項後,就會調用視圖控制項的touches方法來作具體的事件處理:
- touchesBegan…
- touchesMoved…
- touchedEnded…
如何尋找最合適的View?
- 先判斷自己是否能夠接收觸摸事件,如果能夠接收事件再繼續往下判斷。
- 再判斷觸摸的當前點在不在自己的身上。
- 如果在自己身上,它會從後往前遍歷子控制項,遍歷出每一個子控制項後,重覆前面的兩個步驟。
- 如果沒有符合條件的子控制項,那麼它自己就是最適合的View。
- 註意:如果父控制項不能接收觸摸事件,那麼子控制項就不可能接收到觸摸事件。
UIView不能接收觸摸事件的三種情況:
- 不接收用戶交互時不能夠處理事件:
userInteractionEnabled = NO
- 當一個控制項隱藏的時候不能夠接收事件:
Hidden = YES
的時候 - 當一個控制項為透明白時候也不能夠接收事件:
alpha = 0.0 ~ 0.01
註意:UIImageView
的userInteractionEnabled預設就是NO,因此UIImageView以及它的子控制項預設是不能接收觸摸事件的。
事件傳遞示例:
觸摸事件的傳遞是從父控制項傳遞到子控制項:
- 點擊了綠色的view:UIApplication -> UIWindow -> 白色view -> 綠色view
- 點擊了藍色的view:UIApplication -> UIWindow -> 白色view -> 橙色view -> 藍色view
- 點擊了黃色的view:UIApplication -> UIWindow -> 白色view -> 橙色view -> 藍色view -> 黃色view
響應者鏈條:
響應者鏈條:是由多個響應者對象連接起來的鏈條。
作用:能很清楚的看見每個響應者之間的聯繫,並且可以讓一個事件多個對象處理。
事件傳遞的完整過程:
- 先將事件對象由上往下傳遞(由父控制項傳遞給子控制項),找到最合適的控制項來處理這個事件。
- 調用最合適控制項的touches….方法
- 如果調用了[supertouches….];就會將事件順著響應者鏈條往上傳遞,傳遞給上一個響應者
- 接著就會調用上一個響應者的touches….方法
事件響應的過程:
- 用戶點擊屏幕後產生的一個觸摸事件,經過一系列的傳遞過程後,會找到最合適的視圖控制項來處理這個事件
- 找到最合適的視圖控制項後,就會調用控制項的touches方法來作具體的事件處理
- 這些touches方法的預設做法是將事件順著響應者鏈條向上傳遞,將事件交給上一個響應者進行處理
事件傳遞與響應的完整過程:
- 在產生一個事件時,系統會將該事件加入到一個由UIApplication管理的事件隊列中
- UIApplication會從事件隊列中取出最前面的事件,將它傳遞給先發送事件給應用程式的主視窗
- 主視窗會調用hitTest方法尋找最適合的視圖控制項,找到後就會調用視圖控制項的touches方法來做具體的事情
- 當調用touches方法,它的預設做法,就會將事件順著響應者鏈條往上傳遞,傳遞給上一個響應者,接著就會調用上一個響應者的touches方法
如何去尋找上一個響應者?
- 如果當前的View是控制器的View,那麼控制器就是上一個響應者.
- 如果當前的View不是控制器的View,那麼它的父控制項就是上一個響應者.
- 在視圖層次結構的最頂級視圖,如果也不能處理收到的事件或消息,則其將事件或消息傳遞給window對象進行處理
- 如果window對象也不處理,則其將事件或消息傳遞給UIApplication對象
- 如果UIApplication也不能處理該事件或消息,則將其丟棄
手勢識別:
通過touches方法監聽view觸摸事件的缺點?
- 必須得自定義view,在自定義的View當中去實現touches方法.
- 由於是在view內部的touches方法中監聽觸摸事件,因此預設情況下,無法讓其他外界對象監聽view的觸摸事件
- 不容易區分用戶的具體手勢行為(不容易區分是長按手勢,還是縮放手勢)這些等.
iOS 3.2之後,蘋果推出了手勢識別功能(Gesture Recognizer)在觸摸事件處理方面大大簡化了開發者的開發難度。
UIGestureRecognizer:
利用UIGestureRecognizer,能輕鬆識別用戶在某個view上面做的一些常見手勢。
UIGestureRecognizer是一個抽象類,定義了所有手勢的基本行為,使用它的子類才能處理具體的手勢。
UITapGestureRecognizer(敲擊)
UIPinchGestureRecognizer(捏合,用於縮放)
UIPanGestureRecognizer(拖拽)
UISwipeGestureRecognizer(輕掃)
UIRotationGestureRecognizer(旋轉)
UILongPressGestureRecognizer(長按)
UITapGestureRecognizer(點按手勢):
每一個手勢識別器的用法都差不多,比如UITapGestureRecognizer的使用步驟如下:
// 創建手勢識別器對象
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] init];
// 設置手勢識別器對象的具體屬性
// 連續敲擊2次
tap.numberOfTapsRequired = 2;
// 需要2根手指一起敲擊
tap.numberOfTouchesRequired = 2;
// 添加手勢識別器到對應的view上
[self.iconView addGestureRecognizer:tap];
// 監聽手勢的觸發
[tap addTarget:self action:@selector(tapIconView:)];
手勢識別的狀態:
typedef NS_ENUM(NSInteger, UIGestureRecognizerState) {
// 沒有觸摸事件發生,所有手勢識別的預設狀態
UIGestureRecognizerStatePossible,
// 一個手勢已經開始但尚未改變或者完成時
UIGestureRecognizerStateBegan,
// 手勢狀態改變
UIGestureRecognizerStateChanged,
// 手勢完成
UIGestureRecognizerStateEnded,
// 手勢取消,恢復至Possible狀態
UIGestureRecognizerStateCancelled,
// 手勢失敗,恢復至Possible狀態
UIGestureRecognizerStateFailed,
// 識別到手勢識別
UIGestureRecognizerStateRecognized = UIGestureRecognizerStateEnded
};
代碼片段:
添加點按手勢:
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap)];
// 手勢也可以設置代理
tap.delegate = self;
// 添加手勢
[self.imageV addGestureRecognizer:tap];
// 代理方法:
// 是否允許接收手指
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{
// 讓圖片的左邊不可以點擊,
// 獲取當前手指所在的點.是在圖片的左邊還是在圖片的右邊.
CGPoint curP = [touch locationInView:self.imageV];
if (curP.x > self.imageV.bounds.size.width * 0.5) {
// 在圖片的右側
return YES;
}else{
// 在圖片的左側
return NO;
}
return YES;
}
添加長按手勢:
UILongPressGestureRecognizer *longP = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longP:)];
[self.imageV addGestureRecognizer:longP];
//當長按時調用這個方法會調用很多次,
// 當手指長按在上面不松,來回移動時,會持續調用,所以要判斷它的狀態.
- (void)longP:(UILongPressGestureRecognizer *)longP{
if(longP.state == UIGestureRecognizerStateBegan){
NSLog(@"開始長按");
}else if(longP.state == UIGestureRecognizerStateChanged){
NSLog(@"長按時手指移動");
}else if(longP.state == UIGestureRecognizerStateEnded){
NSLog(@"手指離開屏幕");
}
}
添加輕掃手勢:
// 添加輕掃手勢1
UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipe:)];
// 輕掃手勢預設是向右邊稱輕掃,可以設置輕掃的方法.
// 一個輕掃手勢只能設置一個方法的輕掃.想要讓它有多個方向的手勢,必須得要設置的
swipe.direction = UISwipeGestureRecognizerDirectionLeft;
[self.imageV addGestureRecognizer:swipe];
// 添加輕掃手勢2
UISwipeGestureRecognizer *swipe2 = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipe:)];
swipe2.direction = UISwipeGestureRecognizerDirectionUp; [self.imageV addGestureRecognizer:swipe2];
// 輕掃手勢的方法
- (void)swipe:(UISwipeGestureRecognizer *)swipe{
// 判斷的輕掃的方向
if (swipe.direction == UISwipeGestureRecognizerDirectionLeft) {
NSLog(@"向左輕掃");
}else if(swipe.direction == UISwipeGestureRecognizerDirectionUp){
NSLog(@"向上輕掃");
}
}
添加拖動手勢:
// 添加拖動手勢
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
[self.imageV addGestureRecognizer:pan];
// 當手指拖動時調用
- (void)pan:(UIPanGestureRecognizer *)pan{
// 拖動手勢也有狀態
if(pan.state == UIGestureRecognizerStateBegan){
// 開始拖動
}else if(pan.state == UIGestureRecognizerStateChanged){
// 獲取當前手指移動的距離,是相對於最原始的點
CGPoint transP = [pan translationInView:self.imageV];
// 清空上一次的形變
self.imageV.transform = CGAffineTransformMakeTranslation(transP.x,transP.y);
self.imageV.transform = CGAffineTransformTranslate(self.imageV.transform, transP.x, transP.y);
// 複位,讓它相對於上一次.
[pan setTranslation:CGPointZero inView:self.imageV];
}else if(pan.state == UIGestureRecognizerStateEnded){
// 結束拖動
}
}
添加捏合手勢:
// 添加捏合手勢
UIPinchGestureRecognizer *pinGes = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinGes:)];
// 設置代理使其能夠同時支持多個手勢
pinGes.delegate = self;
[self.imageV addGestureRecognizer:pinGes];
// 捏合時調用
- (void)pinGes:(UIPinchGestureRecognizer *)pin{
self.imageV.transform = CGAffineTransformScale(self.imageV.transform, pin.scale, pin.scale);
// 複位
[pin setScale:1];
}
添加旋轉手勢:
// 添加旋轉手勢
UIRotationGestureRecognizer *rotation = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(rotation:)];
// 設置代理使其能夠同時支持多個手勢
rotation.delegate = self;
[self.imageV addGestureRecognizer:rotation];
// 當手指開始旋轉時調用.
- (void)rotation:(UIRotationGestureRecognizer *)rotation{
self.imageV.transform = CGAffineTransformRotate(self.imageV.transform, rotation.rotation);
// 複位.
[rotation setRotation:0];
}