iOS設備現如今大受歡迎的最重要原因之一就在于其開創了觸控操作的潮流。發展到現在,無論是Android還是iPhone,現在APP與用戶進行交互,基本上都是依賴于各種各樣的觸控事件。例如用戶對屏幕進行了側滑,APP就需要對這個手勢進行相應的處理,給用戶一個反饋。這些相應的事件就都是在UIResponder中定義的。
廣告插播的措不及防:如果您要是覺得這篇文章讓您有點收獲,隨手點個贊會讓俺興奮好久吶。
UIResponder大體有四類事件:觸摸、加速計、遠程控制、按壓(iOS9.0以后出來的,3DTouch)。
但是在iOS中不是任何對象都能處理事件,只有繼承了UIResponder的對象才能接收并處理事件。我們稱之為“響應者對象”。
UIApplication、UIViewController、UIView都繼承自UIResponder,因此它們都是響應者對象,都能夠接收并處理事件。
- UIView繼承自UIResponder,因此所有的控件都是響應者對象
- UIWindow:是特殊的UIView,所以也是響應者對象
- UIApplication,所以也是響應者對象
1. 四類事件的主要方法
有的童鞋可能分不清楚手勢當中結束和取消的區別。舉個栗子,當正在撫摸自己的愛機屏幕的時候,突然來了一個電話,這個“愛撫”的動作就被臨時中斷了,這個時候就叫做“取消”,而不是結束。
1.1 觸摸事件
觸摸事件分成了四部分:開始、移動、結束、取消。
-(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;
1.2 加速計事件
加速計事件分成了三部分:開始、結束、取消。也有人叫做運動事件,motion events。
-(void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event;
-(void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event;
-(void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event;
1.3 遠程控制事件
收到控制事件:
-(void)remoteControlReceivedWithEvent:(UIEvent *)event;
1.4 按壓事件
按壓事件分成四部分:按壓開始、按壓改變、按壓結束、按壓取消。
-(void)pressesBegan:(NSSet *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);
-(void)pressesChanged:(NSSet *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);
-(void)pressesEnded:(NSSet *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);
-(void)pressesCancelled:(NSSet *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);
2. 響應者鏈
概念吶,我們就不說了,網上的文章應該一搜一大堆。這里是比較理論化的知識,是比較考驗我們對于iOS中觸摸事件的理解深度的。這里我就只是用簡單的方式,寫一下自己對于這部分的理解。
根據第一部分的內容,知道UIResponder有好多好多。用戶點擊屏幕之后,系統到底讓誰來響應這個觸摸事件吶?例如用戶點了一個button,是應該讓誰來處理呢?
UIButton肯定是放在一個UIView上面,UIView也肯定是放在一個Controller里面。這幾個都是響應者對象,總不能讓大家一起給用戶反饋吧。系統這里的原則其實是越具體越好,也就是干活的肯定是最小的那個小兵兵。要是什么事情都讓UIApplication或者UIWindow干,還不讓它兩兒累死啊,那系統效率要低成神馬樣子。
最終找到這個干活的控件,我們學術上就叫做第一響應者對象。找到了負責處理的按鈕之后如何給出相應處理呢?大概過程就是這樣:
- button嘗試處理事件。如果它不能處理事件,則將事件傳遞給其父視圖。
- button的父視圖(superview)嘗試處理事件。如果這個父視圖還不能處理事件,則繼續將視圖繼續往上級傳。
- 上層視圖(topmost view)會嘗試處理事件。如果這個上層視圖還是不能處理事件,則將事件傳遞給視圖所在的視圖控制器。
- 視圖控制器會嘗試處理事件。如果這個視圖控制器不能處理事件,則將事件傳遞給窗口(window)對象。
- 窗口(window)對象嘗試處理事件。如果不能處理,則將事件傳遞給UIApplication。
- 如果UIApplication不能處理事件,則丟棄這個事件。就是白按嘍。
一次完整的觸摸事件的傳遞響應的過程大概是這樣的: UIAppliction --> UIWindow -->遞歸找到最適合處理事件的控件-->控件調用touches方法-->判斷是否實現touches方法-->沒有實現默認會將事件傳遞給上一個響應者-->找到上一個響應者。
對于第一響應者,UIResponder提供了一系列方法,我們分別來介紹一下。
- 如果想判定一個響應對象是否是第一響應者,則可以使用以下方法:
- (BOOL)isFirstResponder
- 如果我們希望將一個響應對象作為第一響應者,則可以使用以下方法:
- (BOOL)becomeFirstResponder
- 一個響應對象只有在當前響應者能放棄第一響應者狀態(
canResignFirstResponder
)且自身能成為第一響應者(canBecomeFirstResponder
)時才會成為第一響應者。換種說法就是,有人想當部門老大,那也得現在的部門頭愿意放權,并且這個人還有能力才能成為老大。
//判斷是否能夠成為第一響應者
- (BOOL)canBecomeFirstResponder
//響應者放棄第一響應者身份
- (BOOL)resignFirstResponder
- (BOOL)canResignFirstResponder
這些方法大家用的都會比較多,特別是想讓文本輸入框獲取到焦點的時候。
3. 手勢識別功能(Gesture Recognizer)
- 如果想監聽一個view上面的觸摸事件,之前的做法是
- 自定義一個view
- 實現view的touches方法,在方法內部實現具體處理代碼
- 通過touches方法監聽view觸摸事件,有很明顯的幾個缺點
- 必須得自定義view
- 由于是在view內部的touches方法中監聽觸摸事件,因此默認情況下,無法讓其他外界對象監聽view的觸摸事件
- 不容易區分用戶的具體手勢行為- iOS 3.2之后,蘋果推出了手勢識別功能(Gesture Recognizer),在觸摸事件處理方面,大大簡化了開發者的開發難度
3.1手勢識別器(UIGestureRecognizer)
- 為了完成手勢識別,必須借助于手勢識別器——UIGestureRecognizer
- 利用UIGestureRecognizer,能輕松識別用戶在某個view上面做的一些常見手勢
- UIGestureRecognizer是一個抽象類,定義了所有手勢的基本行為,使用它的子類才能處理具體的手勢
- UITapGestureRecognizer(點按)
- UIPinchGestureRecognizer(捏合,用于縮放)
- UIPanGestureRecognizer(拖動)
- UISwipeGestureRecognizer(輕掃)
- UIRotationGestureRecognizer(旋轉)
- UILongPressGestureRecognizer(長按)
3.2 手勢識別的使用方法
- 1.創建手勢識別實例
- 2.設置手勢識別屬性,例如手指數量,方向等
- 3.將手勢識別附加到指定的視圖之上
- 4.編寫手勢觸發監聽方法
- 每一個手勢識別器的用法都差不多,比如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:)];
3.3手勢識別的枚舉
typedef NS_ENUM(NSInteger, UIGestureRecognizerState) {
// 沒有觸摸事件發生,所有手勢識別的默認狀態
UIGestureRecognizerStatePossible,
// 一個手勢已經開始但尚未改變或者完成時
UIGestureRecognizerStateBegan, (類似于 touchesBegan)
// 手勢狀態改變
UIGestureRecognizerStateChanged, (類似于 touchesMoved)
// 手勢完成
UIGestureRecognizerStateEnded, (類似于 touchesEnded)
// 手勢取消,恢復至Possible狀態
UIGestureRecognizerStateCancelled, (比如手指按下按鈕,然后從其他地方抬起)
// 手勢失敗,恢復至Possible狀態
UIGestureRecognizerStateFailed,
// 識別到手勢識別
UIGestureRecognizerStateRecognized = UIGestureRecognizerStateEnded
};
4. 手勢的使用
4.1 長按手勢
- 長按手勢一定要判斷狀態,否則方法會在手勢開始和結束時分別調用!方法會被調用兩次!
- (void)addLongPressGesture
{
//創建長按手勢識別并添加監聽事件
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
// 給圖片添加長按手勢
[self.imageView addGestureRecognizer:longPress];
}
//識別到長按手勢后回調的方法
- (void)longPress:(UILongPressGestureRecognizer *)recognizer
{
// 判斷手勢的狀態,長按手勢一定要判斷狀態,否則方法會在手勢開始和結束時分別調用!方法會被調用兩次!
if (recognizer.state == UIGestureRecognizerStateBegan) {
//動畫 改變圖片的透明度
[UIView animateWithDuration:0.5 animations:^{
self.imageView.alpha = 0.5;
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.5 animations:^{
self.imageView.alpha = 1.0;
}];
}];
}
}
4.2 清掃手勢
- 如果要監聽多個輕掃方向,需要添加多個輕掃手勢
- 輕掃手勢默認支持向右的掃動方向
- 因為輕掃手勢要求用戶比較放松的掃動,因此最好不要將此手勢添加到某一個視圖上,會局限用戶的操作
- (void)addSwipeGesture
{
// 如果要監聽多個輕掃方向,需要添加多個輕掃手勢
// 輕掃手勢默認支持向右的掃動方向
//創建輕掃手勢識別并添加監聽事件(默認是向右掃動)
UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipe:)];
//創建輕掃手勢識別并添加監聽事件
UISwipeGestureRecognizer *swipeLeft = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipe:)];
//direction 方向 向左輕掃
swipeLeft.direction = UISwipeGestureRecognizerDirectionLeft;
/**
UISwipeGestureRecognizerDirectionRight 向右輕掃(默認)不設輕掃方向 就是向右
UISwipeGestureRecognizerDirectionLeft 向左輕掃
UISwipeGestureRecognizerDirectionUp 向上輕掃
UISwipeGestureRecognizerDirectionDown 向下輕掃
*/
// 因為輕掃手勢要求用戶比較放松的掃動,因此最好不要將此手勢添加到某一個視圖上,會局限用戶的操作
// 添加手勢
[self.view addGestureRecognizer:swipe];
[self.view addGestureRecognizer:swipeLeft];
}
//識別到輕掃手勢后回調的方法
- (void)swipe:(UISwipeGestureRecognizer *)recognizer
{
//當前獲取中心位置
CGPoint from = self.imageView.center;
//目標
CGPoint to;
//向左輕掃
if (recognizer.direction == UISwipeGestureRecognizerDirectionLeft) {
to = CGPointMake(-from.x, from.y);
} else {//向右輕掃
to = CGPointMake(3 * from.x, from.y);
}
//動畫移動圖片
[UIView animateWithDuration:0.5 animations:^{
self.imageView.center = to;
}completion:^(BOOL finished) {
[UIView animateWithDuration:0.5 animations:^{
self.imageView.center = from;
}];
}];
}
4.3 拖動手勢
- (void)addPanGesture
{
//創建拖動手勢 并添加手勢的監聽事件
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
//添加手勢
[self.imageView addGestureRecognizer:pan];
}
//識別到拖動手勢后回調的方法
- (void)pan:(UIPanGestureRecognizer *)recognizer
{
//獲取手指按在圖片上的位置 以圖片左上角為原點
CGPoint translation = [recognizer translationInView:self.imageView];
// 移動圖片
recognizer.view.transform = CGAffineTransformTranslate(recognizer.view.transform, translation.x, translation.y);
//給平移復位 因為他是在原有基礎上當前遞增平移 如果不復位 或清空他會越變越大
[recognizer setTranslation:CGPointZero inView:self.imageView];
}
4.4 捏合手勢
- (void)addPinchGesture
{
//創建縮放(捏合)手勢 并添加手勢的監聽事件
UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinch:)];
//設置控制器為縮放手勢的代理 可以實現同時識別兩個手勢
pinch.delegate = self;
[self.imageView addGestureRecognizer:pinch];
}
//識別到 縮放(捏合)手勢后回調的方法
- (void)pinch:(UIPinchGestureRecognizer *)recognizer
{
//綻放圖片
recognizer.view.transform = CGAffineTransformScale(recognizer.view.transform, recognizer.scale, recognizer.scale);
//將縮放比例復位
recognizer.scale = 1.0;
}
4.5 旋轉手勢
- (void)addRotateGesture
{
//創建縮放 旋轉并添加手勢的監聽事件
UIRotationGestureRecognizer *rotate = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(rotate:)];
//設置控制器為縮放手勢的代理 可以實現同時識別兩個手勢
rotate.delegate = self;
// 添加手勢
[self.imageView addGestureRecognizer:rotate];
}
//識別到旋轉手勢后的回調方法
- (void)rotate:(UIRotationGestureRecognizer *)recognizer
{
//圖片旋轉
recognizer.view.transform = CGAffineTransformRotate(recognizer.view.transform, recognizer.rotation);
//將手勢識別的旋轉角度復位
recognizer.rotation = 0.0; //非常重要 角度也會疊加
}
4.6 單擊手勢
- (void)addTapGesture
{
//創建縮放點按(單擊,點擊)并添加手勢的監聽事件
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap)];
// 添加手勢
[self.imageView addGestureRecognizer:tapGesture];
}
//識別到手勢后的回調方法
- (void)tap
{
NSLog(@"點我了");
}
4.7 手勢的總結
- 一定記住設置完transform之后,需要將對應的形變參數復位
- 手勢識別,是單獨添加到某一個視圖上的
- 如果要同時支持多個手勢識別,需要設置手勢識別的代理
是否支持多手勢觸摸的代理方法
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
謝謝您看完這么長一篇文章,辛苦啦~!點個贊吧。哈哈??