iOS中手勢的應用

iOS設備現如今大受歡迎的最重要原因之一就在于其開創了觸控操作的潮流。發展到現在,無論是Android還是iPhone,現在APP與用戶進行交互,基本上都是依賴于各種各樣的觸控事件。例如用戶對屏幕進行了側滑,APP就需要對這個手勢進行相應的處理,給用戶一個反饋。這些相應的事件就都是在UIResponder中定義的。

廣告插播的措不及防:如果您要是覺得這篇文章讓您有點收獲,隨手點個贊會讓俺興奮好久吶。

UIResponder大體有四類事件:觸摸、加速計、遠程控制、按壓(iOS9.0以后出來的,3DTouch)。

觸摸事件.png

但是在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提供了一系列方法,我們分別來介紹一下。

  1. 如果想判定一個響應對象是否是第一響應者,則可以使用以下方法:
  • (BOOL)isFirstResponder
  1. 如果我們希望將一個響應對象作為第一響應者,則可以使用以下方法:
  • (BOOL)becomeFirstResponder
  1. 一個響應對象只有在當前響應者能放棄第一響應者狀態(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;
}

謝謝您看完這么長一篇文章,辛苦啦~!點個贊吧。哈哈??

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,836評論 6 540
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,275評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,904評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,633評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,368評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,736評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,740評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,919評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,481評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,235評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,427評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,968評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,656評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,055評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,348評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,160評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,380評論 2 379

推薦閱讀更多精彩內容