iOS觸摸事件鳖藕、手勢識別魔慷、搖晃事件、耳機(jī)線控

概覽
iPhone的成功很大一部分得益于它多點(diǎn)觸摸的強(qiáng)大功能著恩,喬布斯讓人們認(rèn)識到手機(jī)其實是可以不用按鍵和手寫筆直接操作的院尔,這不愧為一項偉大的設(shè)計蜻展。今天我們就針對iOS的觸摸事件(手勢操作)、運(yùn)動事件邀摆、遠(yuǎn)程控制事件等展開學(xué)習(xí):
iOS事件簡介
觸摸事件
手勢識別
運(yùn)動事件
遠(yuǎn)程控制事件

iOS事件
在iOS中事件分為三類:
觸摸事件:通過觸摸纵顾、手勢進(jìn)行觸發(fā)(例如手指點(diǎn)擊、縮放)
運(yùn)動事件:通過加速器進(jìn)行觸發(fā)(例如手機(jī)晃動)
遠(yuǎn)程控制事件:通過其他遠(yuǎn)程設(shè)備觸發(fā)(例如耳機(jī)控制按鈕)

下圖是蘋果官方對于這三種事件的形象描述:


在iOS中并不是所有的類都能處理接收并事件隧熙,只有繼承自UIResponder類的對象才能處理事件(如我們常用的UIView片挂、UIViewController幻林、UIApplication都繼承自UIResponder贞盯,它們都能接收并處理事件)。在UIResponder中定義了上面三類事件相關(guān)的處理方法:
事件
說明

觸摸事件

  • (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
    一根或多根手指開始觸摸屏幕時執(zhí)行沪饺;

  • (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
    一根或多根手指在屏幕上移動時執(zhí)行躏敢,注意此方法在移動過程中會重復(fù)調(diào)用客峭;

  • (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
    一根或多根手指觸摸結(jié)束離開屏幕時執(zhí)行祥得;

  • (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
    觸摸意外取消時執(zhí)行(例如正在觸摸時打入電話)吨凑;

運(yùn)動事件

  • (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event NS_AVAILABLE_IOS(3_0);
    運(yùn)動開始時執(zhí)行趁冈;

  • (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event NS_AVAILABLE_IOS(3_0);
    運(yùn)動結(jié)束后執(zhí)行把夸;

  • (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event NS_AVAILABLE_IOS(3_0);
    運(yùn)動被意外取消時執(zhí)行厚者;

遠(yuǎn)程控制事件

  • (void)remoteControlReceivedWithEvent:(UIEvent *)event NS_AVAILABLE_IOS(4_0);
    接收到遠(yuǎn)程控制消息時執(zhí)行窥岩;

觸摸事件
基礎(chǔ)知識
三類事件中觸摸事件在iOS中是最常用的事件税课,這里我們首先介紹觸摸事件俱萍。
在下面的例子中定義一個KCImage端壳,它繼承于UIView,在KCImage中指定一個圖片作為背景枪蘑。定義一個視圖控制器KCTouchEventViewController损谦,并且在其中聲明一個KCImage變量,添加到視圖控制器中岳颇。既然UIView和UIViewController都繼承于UIResponder照捡,那么也就就意味著所有的UIKit控件和視圖控制器均能接收觸摸事件。首先我們在KCTouchEventViewController中添加觸摸事件话侧,并利用觸摸移動事件來移動KCImage栗精,具體代碼如下:

//// KCTouchEvenViewController.m// TouchEventAndGesture//// Created by Kenshin Cui on 14-3-16.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import "KCTouchEvenViewController.h"#import "KCImage.h"@interface KCTouchEvenViewController (){ KCImage *_image;}@end@implementation KCTouchEvenViewController- (void)viewDidLoad { [super viewDidLoad]; _image=[[KCImage alloc]initWithFrame:CGRectMake(50, 50, 150, 169 )]; //_image.userInteractionEnabled=NO; [self.view addSubview:_image];}#pragma mark - 視圖控制器的觸摸事件-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ NSLog(@"UIViewController start touch...");}-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{ //取得一個觸摸對象(對于多點(diǎn)觸摸可能有多個對象) UITouch *touch=[touches anyObject]; //NSLog(@"%@",touch); //取得當(dāng)前位置 CGPoint current=[touch locationInView:self.view]; //取得前一個位置 CGPoint previous=[touch previousLocationInView:self.view]; //移動前的中點(diǎn)位置 CGPoint center=_image.center; //移動偏移量 CGPoint offset=CGPointMake(current.x-previous.x, current.y-previous.y); //重新設(shè)置新位置 _image.center=CGPointMake(center.x+offset.x, center.y+offset.y); NSLog(@"UIViewController moving...");}-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{ NSLog(@"UIViewController touch end.");}@end

現(xiàn)在運(yùn)行程序:


上面示例中我們用到了UITouch類,當(dāng)執(zhí)行觸摸事件時會將這個對象傳入瞻鹏。在這個對象中包含了觸摸的所有信息:
window:觸摸時所在的窗口
view:觸摸時所在視圖
tapCount:短時間內(nèi)點(diǎn)擊的次數(shù)
timestamp:觸摸產(chǎn)生或變化的時間戳
phase:觸摸周期內(nèi)的各個狀態(tài)
locationInView:方法:取得在指定視圖的位置
previousLocationInView:方法:取得移動的前一個位置

從上面運(yùn)行效果可以看到無論是選擇KCImage拖動還是在界面其他任意位置拖動都能達(dá)到移動圖片的效果悲立。既然KCImage是UIView當(dāng)然在KCImage中也能觸發(fā)相應(yīng)的觸摸事件,假設(shè)在KCImage中定義三個對應(yīng)的事件:

//// KCImage.m// TouchEventAndGesture//// Created by Kenshin Cui on 14-3-16.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import "KCImage.h"@implementation KCImage- (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { UIImage *img=[UIImage imageNamed:@"photo.png"]; [self setBackgroundColor:[UIColor colorWithPatternImage:img]]; } return self;}#pragma mark - UIView的觸摸事件-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ NSLog(@"UIView start touch...");}-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{ NSLog(@"UIView moving...");}-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{ NSLog(@"UIView touch end.");}@end

此時如果運(yùn)行程序會發(fā)現(xiàn)如果拖動KCImage無法達(dá)到預(yù)期的效果乙漓,但是可以發(fā)現(xiàn)此時會調(diào)用KCImage的觸摸事件而不會調(diào)用KCTouchEventViewController中的觸摸事件级历。如果直接拖拽其他空白位置則可以正常拖拽,而且從輸出信息可以發(fā)現(xiàn)此時調(diào)用的是視圖控制器的觸摸事件叭披。這是為什么呢寥殖?要解答這個問題我們需要了解iOS中事件的處理機(jī)制玩讳。
事件處理機(jī)制
在iOS中發(fā)生觸摸后,事件會加入到UIApplication事件隊列(在這個系列關(guān)于iOS開發(fā)的第一篇文章中我們分析iOS程序原理的時候就說過程序運(yùn)行后UIApplication會循環(huán)監(jiān)聽用戶操作)嚼贡,UIApplication會從事件隊列取出最前面的事件并分發(fā)處理熏纯,通常先分發(fā)給應(yīng)用程序主窗口,主窗口會調(diào)用hitTest:withEvent:方法(假設(shè)稱為方法A粤策,注意這是UIView的方法)樟澜,查找合適的事件觸發(fā)視圖(這里通常稱為“hit-test view”):
在頂級視圖(key window的視圖)上調(diào)用pointInside:withEvent:方法判斷觸摸點(diǎn)是否在當(dāng)前視圖內(nèi);
如果返回NO叮盘,那么A返回nil秩贰;
如果返回YES,那么它會向當(dāng)前視圖的所有子視圖(key window的子視圖)發(fā)送hitTest:withEvent:消息柔吼,遍歷所有子視圖的順序是從subviews數(shù)組的末尾向前遍歷(從界面最上方開始向下遍歷)毒费。
如果有subview的hitTest:withEvent:返回非空對象則A返回此對象,處理結(jié)束(注意這個過程愈魏,子視圖也是根據(jù)pointInside:withEvent:的返回值來確定是返回空還是當(dāng)前子視圖對象的觅玻。并且這個過程中如果子視圖的hidden=YES、userInteractionEnabled=NO或者alpha小于0.1都會并忽略)培漏;
如果所有subview遍歷結(jié)束仍然沒有返回非空對象溪厘,則A返回頂級視圖;

上面的步驟就是點(diǎn)擊檢測的過程牌柄,其實就是查找事件觸發(fā)者的過程畸悬。觸摸對象并非就是事件的響應(yīng)者(例如上面第一個例子中沒有重寫KCImage觸摸事件時,KCImge作為觸摸對象友鼻,但是事件響應(yīng)者卻是UIViewController)傻昙,檢測到了觸摸的對象之后,事件到底是如何響應(yīng)呢彩扔?這個過程就必須引入一個新的概念“響應(yīng)者鏈”妆档。
什么是響應(yīng)者鏈呢?我們知道在iOS程序中無論是最后面的UIWindow還是最前面的某個按鈕虫碉,它們的擺放是有前后關(guān)系的贾惦,一個控件可以放到另一個控件上面或下面,那么用戶點(diǎn)擊某個控件時是觸發(fā)上面的控件還是下面的控件呢敦捧,這種先后關(guān)系構(gòu)成一個鏈條就叫“響應(yīng)者鏈”须板。在iOS中響應(yīng)者鏈的關(guān)系可以用下圖表示:


當(dāng)一個事件發(fā)生后首先看initial view能否處理這個事件,如果不能則會將事件傳遞給其上級視圖(inital view的superView)兢卵;如果上級視圖仍然無法處理則會繼續(xù)往上傳遞习瑰;一直傳遞到視圖控制器view controller,首先判斷視圖控制器的根視圖view是否能處理此事件秽荤;如果不能則接著判斷該視圖控制器能否處理此事件甜奄,如果還是不能則繼續(xù)向上傳遞柠横;(對于第二個圖視圖控制器本身還在另一個視圖控制器中,則繼續(xù)交給父視圖控制器的根視圖课兄,如果根視圖不能處理則交給父視圖控制器處理)牍氛;一直到window,如果window還是不能處理此事件則繼續(xù)交給application(UIApplication單例對象)處理烟阐,如果最后application還是不能處理此事件則將其丟棄搬俊。
這個過程大家理解起來并不難,關(guān)鍵問題是在這個過程中各個對象如何知道自己能不能處理該事件呢蜒茄?對于繼承UIResponder的對象唉擂,其不能處理事件有幾個條件:
userInteractionEnabled=NO
hidden=YES
alpha=0~0.01
沒有實現(xiàn)開始觸摸方法(注意是touchesBegan:withEvent:而不是移動和結(jié)束觸摸事件)

當(dāng)然前三點(diǎn)都是針對UIView控件或其子控件而言的,第四點(diǎn)可以針對UIView也可以針對視圖控制器等其他UIResponder子類扩淀。對于第四種情況這里再次強(qiáng)調(diào)是對象中重寫了開始觸摸方法楔敌,則會處理這個事件,如果僅僅寫了移動驻谆、停止觸摸或取消觸摸事件(或者這三個事件都重寫了)沒有寫開始觸摸事件,則此事件該對象不會進(jìn)行處理庆聘。
相信到了這里大家對于上面點(diǎn)擊圖片為什么不能拖拽已經(jīng)很明確了胜臊。事實上通過前面的解釋大家應(yīng)該可以猜到即使KCImage實現(xiàn)了開始拖拽方法,如果在KCTouchEventViewController中設(shè)置KCImage對象的userInteractionEnabled為NO也是可以拖拽的伙判。
注意:上面提到hitTest:withEvent:可以指定觸發(fā)事件的視圖象对,這里就不再舉例說明,這個方法重寫情況比較少宴抚,一般用于自定義手勢勒魔,有興趣的童鞋可以訪問:Event Delivery: The Responder Chain

手勢識別
簡介
通過前面的內(nèi)容我們可以看到觸摸事件使用起來比較容易菇曲,但是對于多個手指觸摸并進(jìn)行不同的變化操作就要復(fù)雜的多了冠绢。例如說如果兩個手指捏合,我們雖然在觸摸開始常潮、移動等事件中可以通過UITouchs得到兩個觸摸對象弟胀,但是我們?nèi)绾文芘袛嘤脩羰怯脙蓚€手指捏合還是橫掃或者拖動呢?在iOS3.2之后蘋果引入了手勢識別,對于用戶常用的手勢操作進(jìn)行了識別并封裝成具體的類供開發(fā)者使用喊式,這樣在開發(fā)過程中我們就不必再自己編寫算法識別用戶的觸摸操作了孵户。在iOS中有六種手勢操作:
手勢
說明

UITapGestureRecognizer
點(diǎn)按手勢

UIPinchGestureRecognizer
捏合手勢

UIPanGestureRecognizer
拖動手勢

UISwipeGestureRecognizer
輕掃手勢,支持四個方向的輕掃岔留,但是不同的方向要分別定義輕掃手勢

UIRotationGestureRecognizer
旋轉(zhuǎn)手勢

UILongPressGestureRecognizer
長按手勢

所有的手勢操作都繼承于UIGestureRecognizer夏哭,這個類本身不能直接使用。這個類中定義了這幾種手勢共有的一些屬性和方法(下表僅列出常用屬性和方法):
名稱
說明

屬性

@property(nonatomic,readonly) UIGestureRecognizerState state;
手勢狀態(tài)

@property(nonatomic, getter=isEnabled) BOOL enabled;
手勢是否可用

@property(nonatomic,readonly) UIView *view;
觸發(fā)手勢的視圖(一般在觸摸執(zhí)行操作中我們可以通過此屬性獲得觸摸視圖進(jìn)行操作)

@property(nonatomic) BOOL delaysTouchesBegan;
手勢識別失敗前不執(zhí)行觸摸開始事件献联,默認(rèn)為NO竖配;如果為YES厕吉,那么成功識別則不執(zhí)行觸摸開始事件,失敗則執(zhí)行觸摸開始事件械念;如果為NO头朱,則不管成功與否都執(zhí)行觸摸開始事件;

方法

  • (void)addTarget:(id)target action:(SEL)action;
    添加觸摸執(zhí)行事件

  • (void)removeTarget:(id)target action:(SEL)action;
    移除觸摸執(zhí)行事件

  • (NSUInteger)numberOfTouches;
    觸摸點(diǎn)的個數(shù)(同時觸摸的手指數(shù))

  • (CGPoint)locationInView:(UIView*)view;
    在指定視圖中的相對位置

  • (CGPoint)locationOfTouch:(NSUInteger)touchIndex inView:(UIView*)view;
    觸摸點(diǎn)相對于指定視圖的位置

  • (void)requireGestureRecognizerToFail:(UIGestureRecognizer *)otherGestureRecognizer;
    指定一個手勢需要另一個手勢執(zhí)行失敗才會執(zhí)行

代理方法

  • (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;
    一個控件的手勢識別后是否阻斷手勢識別繼續(xù)向下傳播龄减,默認(rèn)返回NO项钮;如果為YES,響應(yīng)者鏈上層對象觸發(fā)手勢識別后希停,如果下層對象也添加了手勢并成功識別也會繼續(xù)執(zhí)行烁巫,否則上層對象識別后則不再繼續(xù)傳播;

手勢狀態(tài)
這里著重解釋一下上表中手勢狀態(tài)這個對象宠能。在六種手勢識別中亚隙,只有一種手勢是離散手勢,它就是UITapGestureRecgnier违崇。離散手勢的特點(diǎn)就是一旦識別就無法取消阿弃,而且只會調(diào)用一次手勢操作事件(初始化手勢時指定的觸發(fā)方法)。換句話說其他五種手勢是連續(xù)手勢羞延,連續(xù)手勢的特點(diǎn)就是會多次調(diào)用手勢操作事件渣淳,而且在連續(xù)手勢識別后可以取消手勢。從下圖可以看出兩者調(diào)用操作事件的次數(shù)是不同的:


在iOS中將手勢狀態(tài)分為如下幾種:
typedef NS_ENUM(NSInteger, UIGestureRecognizerState) { UIGestureRecognizerStatePossible, // 尚未識別是何種手勢操作(但可能已經(jīng)觸發(fā)了觸摸事件)伴箩,默認(rèn)狀態(tài) UIGestureRecognizerStateBegan, // 手勢已經(jīng)開始入愧,此時已經(jīng)被識別,但是這個過程中可能發(fā)生變化嗤谚,手勢操作尚未完成 UIGestureRecognizerStateChanged, // 手勢狀態(tài)發(fā)生轉(zhuǎn)變 UIGestureRecognizerStateEnded, // 手勢識別操作完成(此時已經(jīng)松開手指) UIGestureRecognizerStateCancelled, // 手勢被取消棺蛛,恢復(fù)到默認(rèn)狀態(tài) UIGestureRecognizerStateFailed, // 手勢識別失敗,恢復(fù)到默認(rèn)狀態(tài) UIGestureRecognizerStateRecognized = UIGestureRecognizerStateEnded // 手勢識別完成巩步,同UIGestureRecognizerStateEnded};
對于離散型手勢UITapGestureRecgnizer要么被識別旁赊,要么失敗,點(diǎn)按(假設(shè)點(diǎn)按次數(shù)設(shè)置為1渗钉,并且沒有添加長按手勢)下去一次不松開則此時什么也不會發(fā)生彤恶,松開手指立即識別并調(diào)用操作事件,并且狀態(tài)為3(已完成)鳄橘。
但是連續(xù)手勢要復(fù)雜一些声离,就拿旋轉(zhuǎn)手勢來說,如果兩個手指點(diǎn)下去不做任何操作瘫怜,此時并不能識別手勢(因為我們還沒旋轉(zhuǎn))但是其實已經(jīng)觸發(fā)了觸摸開始事件术徊,此時處于狀態(tài)0;如果此時旋轉(zhuǎn)會被識別鲸湃,也就會調(diào)用對應(yīng)的操作事件赠涮,同時狀態(tài)變成1(手勢開始)子寓,但是狀態(tài)1只有一瞬間;緊接著狀態(tài)變?yōu)?(因為我們的旋轉(zhuǎn)需要持續(xù)一會)笋除,并且重復(fù)調(diào)用操作事件(如果在事件中打印狀態(tài)會重復(fù)打印2)斜友;松開手指,此時狀態(tài)變?yōu)?垃它,并調(diào)用1次操作事件鲜屏。

為了大家更好的理解這個狀態(tài)的變化,不妨在操作事件中打印事件狀態(tài)国拇,會發(fā)現(xiàn)在操作事件中的狀態(tài)永遠(yuǎn)不可能為0(默認(rèn)狀態(tài))洛史,因為只要調(diào)用此事件說明已經(jīng)被識別了。前面也說過酱吝,手勢識別從根本還是調(diào)用觸摸事件而完成的也殖,連續(xù)手勢之所以會發(fā)生狀態(tài)轉(zhuǎn)換完全是由于觸摸事件中的移動事件造成的,沒有移動事件也就不存在這個過程中狀態(tài)變化务热。
大家通過蘋果官方的分析圖再理解一下上面說的內(nèi)容:


使用手勢
在iOS中添加手勢比較簡單忆嗜,可以歸納為以下幾個步驟:
創(chuàng)建對應(yīng)的手勢對象;
設(shè)置手勢識別屬性【可選】陕习;
附加手勢到指定的對象霎褐;
編寫手勢操作方法;

為了幫助大家理解该镣,下面以一個圖片查看程序演示一下上面幾種手勢,在這個程序中我們完成以下功能:
如果點(diǎn)按圖片會在導(dǎo)航欄顯示圖片名稱响谓;
如果長按圖片會顯示刪除按鈕损合,提示用戶是否刪除;
如果捏合會放大娘纷、縮小圖片嫁审;
如果輕掃會切換到下一張或上一張圖片;
如果旋轉(zhuǎn)會旋轉(zhuǎn)圖片赖晶;
如果拖動會移動圖片律适;
具體布局草圖如下:


為了顯示導(dǎo)航條,我們首先將主視圖控制器KCPhotoViewController放入一個導(dǎo)航控制器遏插,然后在主視圖控制器中放一個UIImage用于展示圖片捂贿。下面是主要代碼:

//// KCGestureViewController.m// TouchEventAndGesture//// Created by Kenshin Cui on 14-3-16.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import "KCPhotoViewController.h"#define kImageCount 3@interface KCPhotoViewController (){ UIImageView *_imageView;//圖片展示控件 int _currentIndex;//當(dāng)前圖片索引}@end@implementation KCPhotoViewController- (void)viewDidLoad { [super viewDidLoad]; [self initLayout]; [self addGesture];}#pragma mark 布局-(void)initLayout{ /*添加圖片展示控件*/ CGSize screenSize=[UIScreen mainScreen].applicationFrame.size; CGFloat topPadding=20; CGFloat y=22+44+topPadding,height=screenSize.height-y-topPadding; CGRect imageFrame=CGRectMake(0, y, screenSize.width, height); _imageView=[[UIImageView alloc]initWithFrame:imageFrame]; _imageView.contentMode=UIViewContentModeScaleToFill;//設(shè)置內(nèi)容模式為縮放填充 _imageView.userInteractionEnabled=YES;//這里必須設(shè)置為YES,否則無法接收手勢操作 [self.view addSubview:_imageView]; //添加默認(rèn)圖片 UIImage *image=[UIImage imageNamed:@"0.jpg"]; _imageView.image=image; [self showPhotoName]; }#pragma mark 添加手勢-(void)addGesture{ /*添加點(diǎn)按手勢*/ //創(chuàng)建手勢對象 UITapGestureRecognizer *tapGesture=[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapImage:)]; //設(shè)置手勢屬性 tapGesture.numberOfTapsRequired=1;//設(shè)置點(diǎn)按次數(shù)胳嘲,默認(rèn)為1厂僧,注意在iOS中很少用雙擊操作 tapGesture.numberOfTouchesRequired=1;//點(diǎn)按的手指數(shù) //添加手勢到對象(注意,這里添加到了控制器視圖中了牛,而不是圖片上颜屠,否則點(diǎn)擊空白無法隱藏導(dǎo)航欄) [self.view addGestureRecognizer:tapGesture]; /*添加長按手勢*/ UILongPressGestureRecognizer *longPressGesture=[[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(longPressImage:)]; longPressGesture.minimumPressDuration=0.5;//設(shè)置長按時間辰妙,默認(rèn)0.5秒,一般這個值不要修改 //注意由于我們要做長按提示刪除操作甫窟,因此這個手勢不再添加到控制器視圖上而是添加到了圖片上 [_imageView addGestureRecognizer:longPressGesture]; /*添加捏合手勢*/ UIPinchGestureRecognizer *pinchGesture=[[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(pinchImage:)]; [self.view addGestureRecognizer:pinchGesture]; /*添加旋轉(zhuǎn)手勢*/ UIRotationGestureRecognizer *rotationGesture=[[UIRotationGestureRecognizer alloc]initWithTarget:self action:@selector(rotateImage:)]; [self.view addGestureRecognizer:rotationGesture]; /*添加拖動手勢*/ UIPanGestureRecognizer *panGesture=[[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panImage:)]; [_imageView addGestureRecognizer:panGesture]; /*添加輕掃手勢*/ //注意一個輕掃手勢只能控制一個方向密浑,默認(rèn)向右,通過direction進(jìn)行方向控制 UISwipeGestureRecognizer *swipeGestureToRight=[[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipeImage:)]; //swipeGestureToRight.direction=UISwipeGestureRecognizerDirectionRight;//默認(rèn)為向右輕掃 [self.view addGestureRecognizer:swipeGestureToRight]; UISwipeGestureRecognizer *swipeGestureToLeft=[[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipeImage:)]; swipeGestureToLeft.direction=UISwipeGestureRecognizerDirectionLeft; [self.view addGestureRecognizer:swipeGestureToLeft];}#pragma mark 顯示圖片名稱-(void)showPhotoName{ NSString *title=[NSString stringWithFormat:@"%i.jpg",_currentIndex]; [self setTitle:title];}#pragma mark 下一張圖片-(void)nextImage{ int index=(_currentIndex+kImageCount+1)%kImageCount; NSString *imageName=[NSString stringWithFormat:@"%i.jpg",index]; _imageView.image=[UIImage imageNamed:imageName]; _currentIndex=index; [self showPhotoName];}#pragma mark 上一張圖片-(void)lastImage{ int index=(_currentIndex+kImageCount-1)%kImageCount; NSString *imageName=[NSString stringWithFormat:@"%i.jpg",index]; _imageView.image=[UIImage imageNamed:imageName]; _currentIndex=index; [self showPhotoName];}#pragma mark - 手勢操作#pragma mark 點(diǎn)按隱藏或顯示導(dǎo)航欄-(void)tapImage:(UITapGestureRecognizer *)gesture{ //NSLog(@"tap:%i",gesture.state); BOOL hidden=!self.navigationController.navigationBarHidden; [self.navigationController setNavigationBarHidden:hidden animated:YES];}#pragma mark 長按提示是否刪除-(void)longPressImage:(UILongPressGestureRecognizer *)gesture{ //NSLog(@"longpress:%i",gesture.state); //注意其實在手勢里面有一個view屬性可以獲取點(diǎn)按的視圖 //UIImageView *imageView=(UIImageView *)gesture.view; //由于連續(xù)手勢此方法會調(diào)用多次粗井,所以需要判斷其手勢狀態(tài) if (gesture.state==UIGestureRecognizerStateBegan) { UIActionSheet *actionSheet=[[UIActionSheet alloc]initWithTitle:@"System Info" delegate:nil cancelButtonTitle:@"Cancel" destructiveButtonTitle:@"Delete the photo" otherButtonTitles:nil]; [actionSheet showInView:self.view]; }}#pragma mark 捏合時縮放圖片-(void)pinchImage:(UIPinchGestureRecognizer *)gesture{ //NSLog(@"pinch:%i",gesture.state); if (gesture.state==UIGestureRecognizerStateChanged) { //捏合手勢中scale屬性記錄的縮放比例 _imageView.transform=CGAffineTransformMakeScale(gesture.scale, gesture.scale); }else if(gesture.state==UIGestureRecognizerStateEnded){//結(jié)束后恢復(fù) [UIView animateWithDuration:.5 animations:^{ _imageView.transform=CGAffineTransformIdentity;//取消一切形變 }]; }}#pragma mark 旋轉(zhuǎn)圖片-(void)rotateImage:(UIRotationGestureRecognizer *)gesture{ //NSLog(@"rotate:%i",gesture.state); if (gesture.state==UIGestureRecognizerStateChanged) { //旋轉(zhuǎn)手勢中rotation屬性記錄了旋轉(zhuǎn)弧度 _imageView.transform=CGAffineTransformMakeRotation(gesture.rotation); }else if(gesture.state==UIGestureRecognizerStateEnded){ [UIView animateWithDuration:0.8 animations:^{ _imageView.transform=CGAffineTransformIdentity;//取消形變 }]; }}#pragma mark 拖動圖片-(void)panImage:(UIPanGestureRecognizer *)gesture{ if (gesture.state==UIGestureRecognizerStateChanged) { CGPoint translation=[gesture translationInView:self.view];//利用拖動手勢的translationInView:方法取得在相對指定視圖(這里是控制器根視圖)的移動 _imageView.transform=CGAffineTransformMakeTranslation(translation.x, translation.y); }else if(gesture.state==UIGestureRecognizerStateEnded){ [UIView animateWithDuration:0.5 animations:^{ _imageView.transform=CGAffineTransformIdentity; }]; } }#pragma mark 輕掃則查看下一張或上一張//注意雖然輕掃手勢是連續(xù)手勢尔破,但是只有在識別結(jié)束才會觸發(fā),不用判斷狀態(tài)-(void)swipeImage:(UISwipeGestureRecognizer *)gesture{// NSLog(@"swip:%i",gesture.state);// if (gesture.state==UIGestureRecognizerStateEnded) { //direction記錄的輕掃的方向 if (gesture.direction==UISwipeGestureRecognizerDirectionRight) {//向右 [self nextImage];// NSLog(@"right"); }else if(gesture.direction==UISwipeGestureRecognizerDirectionLeft){//向左// NSLog(@"left"); [self lastImage]; }// }}//-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{// //NSLog(@"touch begin...");//}@end

運(yùn)行效果:


在上面示例中需要強(qiáng)調(diào)幾點(diǎn):
UIImageView默認(rèn)是不支持交互的背传,也就是userInteractionEnabled=NO 呆瞻,因此要接收觸摸事件(手勢識別),必須設(shè)置userInteractionEnabled=YES(在iOS中UILabel径玖、UIImageView的userInteractionEnabled默認(rèn)都是NO痴脾,UIButton、UITextField梳星、UIScrollView赞赖、UITableView等默認(rèn)都是YES)。
輕掃手勢雖然是連續(xù)手勢但是它的操作事件只會在識別結(jié)束時調(diào)用一次冤灾,其他連續(xù)手勢都會調(diào)用多次前域,一般需要進(jìn)行狀態(tài)判斷;此外輕掃手勢支持四個方向韵吨,但是如果要支持多個方向需要添加多個輕掃手勢匿垄。

手勢沖突
細(xì)心的童鞋會發(fā)現(xiàn)在上面的演示效果圖中當(dāng)切換到下一張或者上一張圖片時并沒有輕掃圖片而是在空白地方輕掃完成,原因是如果我輕掃圖片會引起拖動手勢而不是輕掃手勢归粉。換句話說椿疗,兩種手勢發(fā)生了沖突。
沖突的原因很簡單糠悼,拖動手勢的操作事件是在手勢的開始狀態(tài)(狀態(tài)1)識別執(zhí)行的届榄,而輕掃手勢的操作事件只有在手勢結(jié)束狀態(tài)(狀態(tài)3)才能執(zhí)行,因此輕掃手勢就作為了犧牲品沒有被正確識別倔喂。我們理想的情況當(dāng)然是如果在圖片上拖動就移動圖片铝条,如果在圖片上輕掃就翻動圖片。如何解決這個沖突呢席噩?
在iOS中班缰,如果一個手勢A的識別部分是另一個手勢B的子部分時,默認(rèn)情況下A就會先識別班挖,B就無法識別了鲁捏。要解決這個沖突可以利用*- (void)requireGestureRecognizerToFail:(UIGestureRecognizer )otherGestureRecognizer;方法來完成。正是前面表格中UIGestureRecognizer的最后一個方法,這個方法可以指定某個手勢執(zhí)行的前提是另一個手勢失敗才會識別執(zhí)行给梅。也就是說如果我們指定拖動手勢的執(zhí)行前提為輕掃手勢失敗就可以了假丧,這樣一來當(dāng)我們手指輕輕滑動時系統(tǒng)會優(yōu)先考慮輕掃手勢,如果最后發(fā)現(xiàn)該操作不是輕掃动羽,那么就會執(zhí)行拖動包帚。只要將下面的代碼添加到添加手勢之后就能解決這個問題了(注意為了更加清晰的區(qū)分拖動和輕掃[模擬器中拖動稍微快一點(diǎn)就識別成了輕掃],這里將長按手勢的前提設(shè)置為拖動失敗运吓,避免演示拖動時長按手勢會被識別):

 //解決在圖片上滑動時拖動手勢和輕掃手勢的沖突 [panGesture requireGestureRecognizerToFail:swipeGestureToRight]; [panGesture requireGestureRecognizerToFail:swipeGestureToLeft]; //解決拖動和長按手勢之間的沖突 [longPressGesture requireGestureRecognizerToFail:panGesture];

運(yùn)行效果:


兩個不同控件的手勢同時執(zhí)行
我們知道在iOS的觸摸事件中渴邦,事件觸發(fā)是根據(jù)響應(yīng)者鏈進(jìn)行的,上層觸摸事件執(zhí)行后就不再向下傳播拘哨。默認(rèn)情況下手勢也是類似的谋梭,先識別的手勢會阻斷手勢識別操作繼續(xù)傳播。那么如何讓兩個有層次關(guān)系并且都添加了手勢的控件都能正確識別手勢呢倦青?答案就是利用代理的**-(BOOL)gestureRecognizer:(UIGestureRecognizer )gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer )otherGestureRecognizer方法瓮床。這個代理方法默認(rèn)返回NO,會阻斷繼續(xù)向下識別手勢产镐,如果返回YES則可以繼續(xù)向下傳播識別隘庄。
下面的代碼控制演示了當(dāng)在圖片上長按時同時可以識別控制器視圖的長按手勢(注意其中我們還控制了只有在UIImageView中操作的手勢才能向下傳遞,如果不控制則所有控件都可以向下傳遞)

//// KCGestureViewController.m// TouchEventAndGesture//// Created by Kenshin Cui on 14-3-16.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import "KCPhotoViewController.h"#define kImageCount 3@interface KCPhotoViewController ()<UIGestureRecognizerDelegate>{ UIImageView *_imageView;//圖片展示控件 int _currentIndex;//當(dāng)前圖片索引}@end@implementation KCPhotoViewController- (void)viewDidLoad { [super viewDidLoad]; [self initLayout]; [self addGesture];}#pragma mark 布局-(void)initLayout{ /*添加圖片展示控件*/ CGSize screenSize=[UIScreen mainScreen].applicationFrame.size; CGFloat topPadding=20; CGFloat y=22+44+topPadding,height=screenSize.height-y-topPadding; CGRect imageFrame=CGRectMake(0, y, screenSize.width, height); _imageView=[[UIImageView alloc]initWithFrame:imageFrame]; _imageView.contentMode=UIViewContentModeScaleToFill;//設(shè)置內(nèi)容模式為縮放填充 _imageView.userInteractionEnabled=YES;//這里必須設(shè)置位YES癣亚,否則無法接收手勢操作 //_imageView.multipleTouchEnabled=YES;//支持多點(diǎn)觸摸丑掺,默認(rèn)就是YES [self.view addSubview:_imageView]; //添加默認(rèn)圖片 UIImage *image=[UIImage imageNamed:@"0.jpg"]; _imageView.image=image; [self showPhotoName]; }#pragma mark 添加手勢-(void)addGesture{ /*添加點(diǎn)按手勢*/ //創(chuàng)建手勢對象 UITapGestureRecognizer *tapGesture=[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapImage:)]; //設(shè)置手勢屬性 tapGesture.numberOfTapsRequired=1;//設(shè)置點(diǎn)按次數(shù),默認(rèn)為1述雾,注意在iOS中很少用雙擊操作 tapGesture.numberOfTouchesRequired=1;//點(diǎn)按的手指數(shù) //添加手勢到對象(注意街州,這里添加到了控制器視圖中,而不是圖片上玻孟,否則點(diǎn)擊空白無法隱藏導(dǎo)航欄) [self.view addGestureRecognizer:tapGesture]; /*添加長按手勢*/ UILongPressGestureRecognizer *longPressGesture=[[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(longPressImage:)]; longPressGesture.minimumPressDuration=0.5;//設(shè)置長按時間菇肃,默認(rèn)0.5秒,一般這個值不要修改 //注意由于我們要做長按提示刪除操作取募,因此這個手勢不再添加到控制器視圖上而是添加到了圖片上 [_imageView addGestureRecognizer:longPressGesture]; /*添加捏合手勢*/ UIPinchGestureRecognizer *pinchGesture=[[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(pinchImage:)]; [self.view addGestureRecognizer:pinchGesture]; /*添加旋轉(zhuǎn)手勢*/ UIRotationGestureRecognizer *rotationGesture=[[UIRotationGestureRecognizer alloc]initWithTarget:self action:@selector(rotateImage:)]; [self.view addGestureRecognizer:rotationGesture]; /*添加拖動手勢*/ UIPanGestureRecognizer *panGesture=[[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panImage:)]; [_imageView addGestureRecognizer:panGesture]; /*添加輕掃手勢*/ //注意一個輕掃手勢只能控制一個方向,默認(rèn)向右蟆技,通過direction進(jìn)行方向控制 UISwipeGestureRecognizer *swipeGestureToRight=[[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipeImage:)]; //swipeGestureToRight.direction=UISwipeGestureRecognizerDirectionRight;//默認(rèn)位向右輕掃 [self.view addGestureRecognizer:swipeGestureToRight]; UISwipeGestureRecognizer *swipeGestureToLeft=[[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipeImage:)]; swipeGestureToLeft.direction=UISwipeGestureRecognizerDirectionLeft; [self.view addGestureRecognizer:swipeGestureToLeft]; //解決在圖片上滑動時拖動手勢和輕掃手勢的沖突 [panGesture requireGestureRecognizerToFail:swipeGestureToRight]; [panGesture requireGestureRecognizerToFail:swipeGestureToLeft]; //解決拖動和長按手勢之間的沖突 [longPressGesture requireGestureRecognizerToFail:panGesture]; /*演示不同視圖的手勢同時執(zhí)行 *在上面_imageView已經(jīng)添加了長按手勢玩敏,這里給視圖控制器的視圖也加上長按手勢讓兩者都執(zhí)行 * */ self.view.tag=100; _imageView.tag=200; UILongPressGestureRecognizer *viewLongPressGesture=[[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(longPressView:)]; viewLongPressGesture.delegate=self; [self.view addGestureRecognizer:viewLongPressGesture];}#pragma mark 顯示圖片名稱-(void)showPhotoName{ NSString *title=[NSString stringWithFormat:@"%i.jpg",_currentIndex]; [self setTitle:title];}#pragma mark 下一張圖片-(void)nextImage{ int index=(_currentIndex+kImageCount+1)%kImageCount; NSString *imageName=[NSString stringWithFormat:@"%i.jpg",index]; _imageView.image=[UIImage imageNamed:imageName]; _currentIndex=index; [self showPhotoName];}#pragma mark 上一張圖片-(void)lastImage{ int index=(_currentIndex+kImageCount-1)%kImageCount; NSString *imageName=[NSString stringWithFormat:@"%i.jpg",index]; _imageView.image=[UIImage imageNamed:imageName]; _currentIndex=index; [self showPhotoName];}#pragma mark - 手勢操作#pragma mark 點(diǎn)按隱藏或顯示導(dǎo)航欄-(void)tapImage:(UITapGestureRecognizer *)gesture{ //NSLog(@"tap:%i",gesture.state); BOOL hidden=!self.navigationController.navigationBarHidden; [self.navigationController setNavigationBarHidden:hidden animated:YES];}#pragma mark 長按提示是否刪除-(void)longPressImage:(UILongPressGestureRecognizer *)gesture{ //NSLog(@"longpress:%i",gesture.state); //注意其實在手勢里面有一個view屬性可以獲取點(diǎn)按的視圖 //UIImageView *imageView=(UIImageView *)gesture.view; //由于連續(xù)手勢此方法會調(diào)用多次,所以需求判斷其手勢狀態(tài) if (gesture.state==UIGestureRecognizerStateBegan) { UIActionSheet *actionSheet=[[UIActionSheet alloc]initWithTitle:@"System Info" delegate:nil cancelButtonTitle:@"Cancel" destructiveButtonTitle:@"Delete the photo" otherButtonTitles:nil]; [actionSheet showInView:self.view]; }}#pragma mark 捏合時縮放圖片-(void)pinchImage:(UIPinchGestureRecognizer *)gesture{ //NSLog(@"pinch:%i",gesture.state); if (gesture.state==UIGestureRecognizerStateChanged) { //捏合手勢中scale屬性記錄的縮放比例 _imageView.transform=CGAffineTransformMakeScale(gesture.scale, gesture.scale); }else if(gesture.state==UIGestureRecognizerStateEnded){//結(jié)束后恢復(fù) [UIView animateWithDuration:.5 animations:^{ _imageView.transform=CGAffineTransformIdentity;//取消一切形變 }]; }}#pragma mark 旋轉(zhuǎn)圖片-(void)rotateImage:(UIRotationGestureRecognizer *)gesture{ //NSLog(@"rotate:%i",gesture.state); if (gesture.state==UIGestureRecognizerStateChanged) { //旋轉(zhuǎn)手勢中rotation屬性記錄了旋轉(zhuǎn)弧度 _imageView.transform=CGAffineTransformMakeRotation(gesture.rotation); }else if(gesture.state==UIGestureRecognizerStateEnded){ [UIView animateWithDuration:0.8 animations:^{ _imageView.transform=CGAffineTransformIdentity;//取消形變 }]; }}#pragma mark 拖動圖片-(void)panImage:(UIPanGestureRecognizer *)gesture{ if (gesture.state==UIGestureRecognizerStateChanged) { CGPoint translation=[gesture translationInView:self.view];//利用拖動手勢的translationInView:方法取得在相對指定視圖(控制器根視圖)的移動 _imageView.transform=CGAffineTransformMakeTranslation(translation.x, translation.y); }else if(gesture.state==UIGestureRecognizerStateEnded){ [UIView animateWithDuration:0.5 animations:^{ _imageView.transform=CGAffineTransformIdentity; }]; } }#pragma mark 輕掃則查看下一張或上一張//注意雖然輕掃手勢是連續(xù)手勢质礼,但是只有在識別結(jié)束才會觸發(fā)旺聚,不用判斷狀態(tài)-(void)swipeImage:(UISwipeGestureRecognizer *)gesture{// NSLog(@"swip:%i",gesture.state);// if (gesture.state==UIGestureRecognizerStateEnded) { //direction記錄的輕掃的方向 if (gesture.direction==UISwipeGestureRecognizerDirectionRight) {//向右 [self nextImage];// NSLog(@"right"); }else if(gesture.direction==UISwipeGestureRecognizerDirectionLeft){//向左// NSLog(@"left"); [self lastImage]; }// }}#pragma mark 控制器視圖的長按手勢-(void)longPressView:(UILongPressGestureRecognizer *)gesture{ NSLog(@"view long press!");}#pragma mark 手勢代理方法-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{ //NSLog(@"%i,%i",gestureRecognizer.view.tag,otherGestureRecognizer.view.tag); //注意,這里控制只有在UIImageView中才能向下傳播眶蕉,其他情況不允許 if ([otherGestureRecognizer.view isKindOfClass:[UIImageView class]]) { return YES; } return NO;}#pragma mark - 觸摸事件-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ NSLog(@"touch begin...");}-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{ NSLog(@"touch end.");}@end

運(yùn)動事件
前面我們主要介紹了觸摸事件以及由觸摸事件引出的手勢識別砰粹,下面我們簡單介紹一下運(yùn)動事件。在iOS中和運(yùn)動相關(guān)的有三個事件:開始運(yùn)動造挽、結(jié)束運(yùn)動碱璃、取消運(yùn)動弄痹。
監(jiān)聽運(yùn)動事件對于UI控件有個前提就是監(jiān)聽對象必須是第一響應(yīng)者(對于UIViewController視圖控制器和UIAPPlication沒有此要求)。這也就意味著如果監(jiān)聽的是一個UI控件那么-(BOOL)canBecomeFirstResponder;方法必須返回YES嵌器。同時控件顯示時(在-(void)viewWillAppear:(BOOL)animated;事件中)調(diào)用視圖控制器的becomeFirstResponder方法肛真。當(dāng)視圖不再顯示時(在-(void)viewDidDisappear:(BOOL)animated;事件中)注銷第一響應(yīng)者身份。
由于視圖控制器默認(rèn)就可以調(diào)用運(yùn)動開始爽航、運(yùn)動結(jié)束事件在此不再舉例◎救茫現(xiàn)在不妨假設(shè)我們現(xiàn)在在開發(fā)一個搖一搖找人的功能,這里我們就自定義一個圖片展示控件讥珍,在這個圖片控件中我們可以通過搖晃隨機(jī)切換界面圖片历极。代碼比較簡單:

KCImageView.m
//// KCImageView.m// TouchEventAndGesture//// Created by Kenshin Cui on 14-3-16.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import "KCImageView.h"#define kImageCount 3@implementation KCImageView- (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { self.image=[self getImage]; } return self;}#pragma mark 設(shè)置控件可以成為第一響應(yīng)者-(BOOL)canBecomeFirstResponder{ return YES;}#pragma mark 運(yùn)動開始-(void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event{ //這里只處理搖晃事件 if (motion==UIEventSubtypeMotionShake) { self.image=[self getImage]; }}#pragma mark 運(yùn)動結(jié)束-(void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event{ }#pragma mark 隨機(jī)取得圖片-(UIImage *)getImage{ int index= arc4random()%kImageCount; NSString *imageName=[NSString stringWithFormat:@"avatar%i.png",index]; UIImage *image=[UIImage imageNamed:imageName]; return image;}@end
KCShakeViewController.m
//// KCShakeViewController.m// TouchEventAndGesture//// Created by Kenshin Cui on 14-3-16.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import "KCShakeViewController.h"#import "KCImageView.h"@interface KCShakeViewController (){ KCImageView *_imageView;}@end@implementation KCShakeViewController- (void)viewDidLoad { [super viewDidLoad]; }#pragma mark 視圖顯示時讓控件變成第一響應(yīng)者-(void)viewDidAppear:(BOOL)animated{ _imageView=[[KCImageView alloc]initWithFrame:[UIScreen mainScreen].applicationFrame]; _imageView.userInteractionEnabled=true; [self.view addSubview:_imageView]; [_imageView becomeFirstResponder];}#pragma mark 視圖不顯示時注銷控件第一響應(yīng)者的身份-(void)viewDidDisappear:(BOOL)animated{ [_imageView resignFirstResponder];}/*視圖控制器的運(yùn)動事件*///-(void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event{// NSLog(@"motion begin...");//}////-(void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event{// NSLog(@"motion end.");//}@end
運(yùn)行效果(下圖演示時使用了模擬器搖晃操作的快捷鍵,沒有使用鼠標(biāo)操作):
[![MotionEffect](http://upload-images.jianshu.io/upload_images/664334-b37a997d26bb0f6b.gif?imageMogr2/auto-orient/strip)](http://images.cnitblog.com/blog/62046/201409/020633047975098.gif)

遠(yuǎn)程控制事件
在今天的文章中還剩下最后一類事件:遠(yuǎn)程控制,遠(yuǎn)程控制事件這里主要說的就是耳機(jī)線控操作衷佃。在前面的事件列表中趟卸,大家可以看到在iOS中和遠(yuǎn)程控制事件有關(guān)的只有一個- (void)remoteControlReceivedWithEvent:(UIEvent )event NS_AVAILABLE_IOS(4_0);事件要監(jiān)聽到這個事件有三個前提(視圖控制器UIViewController或應(yīng)用程序UIApplication只有兩個)
啟用遠(yuǎn)程事件接收(
使用[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];*方法)纲酗。
對于UI控件同樣要求必須是第一響應(yīng)者(對于視圖控制器UIViewController或者應(yīng)用程序UIApplication對象監(jiān)聽無此要求)衰腌。
應(yīng)用程序必須是當(dāng)前音頻的控制者,也就是在iOS 7中通知欄中當(dāng)前音頻播放程序必須是我們自己開發(fā)程序觅赊。

基于第三點(diǎn)我們必須明確右蕊,如果我們的程序不想要控制音頻,只是想利用遠(yuǎn)程控制事件做其他的事情吮螺,例如模仿iOS7中的按音量+鍵拍照是做不到的饶囚,目前iOS7給我們的遠(yuǎn)程控制權(quán)限還僅限于音頻控制(當(dāng)然假設(shè)我們確實想要做一個和播放音頻無關(guān)的應(yīng)用但是又想進(jìn)行遠(yuǎn)程控制,也可以隱藏一個音頻播放操作鸠补,拿到遠(yuǎn)程控制操作權(quán)后進(jìn)行遠(yuǎn)程控制)萝风。
運(yùn)動事件中我們也提到一個枚舉類型UIEventSubtype,而且我們利用它來判斷是否運(yùn)動事件紫岩,在枚舉中還包含了我們運(yùn)程控制的子事件類型规惰,我們先來熟悉一下這個枚舉(從遠(yuǎn)程控制子事件類型也不難發(fā)現(xiàn)它和音頻播放有密切關(guān)系):
typedef NS_ENUM(NSInteger, UIEventSubtype) { // 不包含任何子事件類型 UIEventSubtypeNone = 0, // 搖晃事件(從iOS3.0開始支持此事件) UIEventSubtypeMotionShake = 1, //遠(yuǎn)程控制子事件類型(從iOS4.0開始支持遠(yuǎn)程控制事件) //播放事件【操作:停止?fàn)顟B(tài)下,按耳機(jī)線控中間按鈕一下】 UIEventSubtypeRemoteControlPlay = 100, //暫停事件 UIEventSubtypeRemoteControlPause = 101, //停止事件 UIEventSubtypeRemoteControlStop = 102, //播放或暫停切換【操作:播放或暫停狀態(tài)下泉蝌,按耳機(jī)線控中間按鈕一下】 UIEventSubtypeRemoteControlTogglePlayPause = 103, //下一曲【操作:按耳機(jī)線控中間按鈕兩下】 UIEventSubtypeRemoteControlNextTrack = 104, //上一曲【操作:按耳機(jī)線控中間按鈕三下】 UIEventSubtypeRemoteControlPreviousTrack = 105, //快退開始【操作:按耳機(jī)線控中間按鈕三下不要松開】 UIEventSubtypeRemoteControlBeginSeekingBackward = 106, //快退停止【操作:按耳機(jī)線控中間按鈕三下到了快退的位置松開】 UIEventSubtypeRemoteControlEndSeekingBackward = 107, //快進(jìn)開始【操作:按耳機(jī)線控中間按鈕兩下不要松開】 UIEventSubtypeRemoteControlBeginSeekingForward = 108, //快進(jìn)停止【操作:按耳機(jī)線控中間按鈕兩下到了快進(jìn)的位置松開】 UIEventSubtypeRemoteControlEndSeekingForward = 109,};
這里我們將遠(yuǎn)程控制事件放到視圖控制器(事實上很少直接添加到UI控件歇万,一般就是添加到UIApplication或者UIViewController),模擬一個音樂播放器勋陪。
1.首先在應(yīng)用程序啟動后設(shè)置接收遠(yuǎn)程控制事件贪磺,并且設(shè)置音頻會話保證后臺運(yùn)行可以播放(注意要在應(yīng)用配置中設(shè)置允許多任務(wù))

//// AppDelegate.m// TouchEventAndGesture//// Created by Kenshin Cui on 14-3-16.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import "AppDelegate.h"#import "ViewController.h"#import <AVFoundation/AVFoundation.h>#import "KCApplication.h"@interface AppDelegate ()@end@implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { _window=[[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds]; _window.backgroundColor =[UIColor colorWithRed:249/255.0 green:249/255.0 blue:249/255.0 alpha:1]; //設(shè)置全局導(dǎo)航條風(fēng)格和顏色 [[UINavigationBar appearance] setBarTintColor:[UIColor colorWithRed:23/255.0 green:180/255.0 blue:237/255.0 alpha:1]]; [[UINavigationBar appearance] setBarStyle:UIBarStyleBlack]; ViewController *mainController=[[ViewController alloc]init]; _window.rootViewController=mainController; //設(shè)置播放會話,在后臺可以繼續(xù)播放(還需要設(shè)置程序允許后臺運(yùn)行模式) [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil]; if(![[AVAudioSession sharedInstance] setActive:YES error:nil]) { NSLog(@"Failed to set up a session."); } //啟用遠(yuǎn)程控制事件接收 [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];// [self becomeFirstResponder];  [_window makeKeyAndVisible]; return YES;}//-(void)remoteControlReceivedWithEvent:(UIEvent *)event{// NSLog(@"remote");//}- (void)applicationWillResignActive:(UIApplication *)application { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.}- (void)applicationDidEnterBackground:(UIApplication *)application { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];}- (void)applicationWillEnterForeground:(UIApplication *)application { // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.}- (void)applicationDidBecomeActive:(UIApplication *)application { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.}- (void)applicationWillTerminate:(UIApplication *)application { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.}@end
2.在視圖控制器中添加遠(yuǎn)程控制事件并音頻播放進(jìn)行控制
//// ViewController.m// RemoteEvent//// Created by Kenshin Cui on 14-3-16.// Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import "ViewController.h"@interface ViewController (){ UIButton *_playButton; BOOL _isPlaying;}@end@implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [self initLayout];}-(BOOL)canBecomeFirstResponder{ return NO;}-(void)viewDidAppear:(BOOL)animated{ [super viewDidAppear:animated]; _player = [[AVPlayer alloc] initWithURL:[NSURL URLWithString:@"http://stream.jewishmusicstream.com:8000"]]; //[_player play]; //_isPlaying=true;}#pragma mark 遠(yuǎn)程控制事件-(void)remoteControlReceivedWithEvent:(UIEvent *)event{ NSLog(@"%i,%i",event.type,event.subtype); if(event.type==UIEventTypeRemoteControl){ switch (event.subtype) { case UIEventSubtypeRemoteControlPlay: [_player play]; _isPlaying=true; break; case UIEventSubtypeRemoteControlTogglePlayPause: if (_isPlaying) { [_player pause]; }else{ [_player play]; } _isPlaying=!_isPlaying; break; case UIEventSubtypeRemoteControlNextTrack: NSLog(@"Next..."); break; case UIEventSubtypeRemoteControlPreviousTrack: NSLog(@"Previous..."); break; case UIEventSubtypeRemoteControlBeginSeekingForward: NSLog(@"Begin seek forward..."); break; case UIEventSubtypeRemoteControlEndSeekingForward: NSLog(@"End seek forward..."); break; case UIEventSubtypeRemoteControlBeginSeekingBackward: NSLog(@"Begin seek backward..."); break; case UIEventSubtypeRemoteControlEndSeekingBackward: NSLog(@"End seek backward..."); break; default: break; } [self changeUIState]; }}#pragma mark 界面布局-(void)initLayout{ //專輯封面 UIImage *image=[UIImage imageNamed:@"wxl.jpg"]; UIImageView *imageView=[[UIImageView alloc]initWithFrame:[UIScreen mainScreen].applicationFrame]; imageView.image=image; imageView.contentMode=UIViewContentModeScaleAspectFill; [self.view addSubview:imageView]; //播放控制面板 UIView *view=[[UIView alloc]initWithFrame:CGRectMake(0, 480, 320, 88)]; view.backgroundColor=[UIColor lightGrayColor]; view.alpha=0.9; [self.view addSubview:view]; //添加播放按鈕 _playButton=[UIButton buttonWithType:UIButtonTypeCustom]; _playButton.bounds=CGRectMake(0, 0, 50, 50); _playButton.center=CGPointMake(view.frame.size.width/2, view.frame.size.height/2); [self changeUIState]; [_playButton addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside]; [view addSubview:_playButton];}#pragma mark 界面狀態(tài)-(void)changeUIState{ if(_isPlaying){ [_playButton setImage:[UIImage imageNamed:@"playing_btn_pause_n.png"] forState:UIControlStateNormal]; [_playButton setImage:[UIImage imageNamed:@"playing_btn_pause_h.png"] forState:UIControlStateHighlighted]; }else{ [_playButton setImage:[UIImage imageNamed:@"playing_btn_play_n.png"] forState:UIControlStateNormal]; [_playButton setImage:[UIImage imageNamed:@"playing_btn_play_h.png"] forState:UIControlStateHighlighted]; }}-(void)btnClick:(UIButton *)btn{ if (_isPlaying) { [_player pause]; }else{ [_player play]; } _isPlaying=!_isPlaying; [self changeUIState];}@end

運(yùn)行效果(真機(jī)截圖):


注意:
為了模擬一個真實的播放器诅愚,程序中我們啟用了后臺運(yùn)行模式寒锚,配置方法:在info.plist中添加UIBackgroundModes并且添加一個元素值為audio。
即使利用線控進(jìn)行音頻控制我們也無法監(jiān)控到耳機(jī)增加音量、減小音量的按鍵操作(另外注意模擬器無法模擬遠(yuǎn)程事件刹前,請使用真機(jī)調(diào)試)泳赋。
子事件的類型跟當(dāng)前音頻狀態(tài)有直接關(guān)系,點(diǎn)擊一次播放/暫停按鈕究竟是【播放】還是【播放/暫腿迹】狀態(tài)切換要看當(dāng)前音頻處于什么狀態(tài)摹蘑,如果處于停止?fàn)顟B(tài)則點(diǎn)擊一下是播放,如果處于暫驮桑或播放狀態(tài)點(diǎn)擊一下是暫停和播放切換衅鹿。
上面的程序已在真機(jī)調(diào)試通過,無論是線控還是點(diǎn)擊應(yīng)用按鈕都可以控制播放或暫停过咬。

轉(zhuǎn)載至崔江濤(KenshinCui)大渤,
iOS觸摸事件、手勢識別掸绞、搖晃事件泵三、耳機(jī)線控

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市衔掸,隨后出現(xiàn)的幾起案子烫幕,更是在濱河造成了極大的恐慌,老刑警劉巖敞映,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件较曼,死亡現(xiàn)場離奇詭異,居然都是意外死亡振愿,警方通過查閱死者的電腦和手機(jī)捷犹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來冕末,“玉大人萍歉,你說我怎么就攤上這事〉堤遥” “怎么了枪孩?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長藻肄。 經(jīng)常有香客問我销凑,道長,這世上最難降的妖魔是什么仅炊? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮澎蛛,結(jié)果婚禮上抚垄,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好呆馁,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布桐经。 她就那樣靜靜地躺著,像睡著了一般浙滤。 火紅的嫁衣襯著肌膚如雪阴挣。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天纺腊,我揣著相機(jī)與錄音畔咧,去河邊找鬼。 笑死揖膜,一個胖子當(dāng)著我的面吹牛誓沸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播壹粟,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼拜隧,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了趁仙?” 一聲冷哼從身側(cè)響起洪添,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎雀费,沒想到半個月后干奢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡坐儿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年律胀,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片貌矿。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡炭菌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出逛漫,到底是詐尸還是另有隱情黑低,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布酌毡,位于F島的核電站克握,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏枷踏。R本人自食惡果不足惜菩暗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望旭蠕。 院中可真熱鬧停团,春花似錦旷坦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至舌胶,卻和暖如春捆蜀,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背幔嫂。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工辆它, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人婉烟。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓娩井,卻偏偏與公主長得像,于是被迫代替她去往敵國和親似袁。 傳聞我的和親對象是個殘疾皇子洞辣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容