zoukankan      html  css  js  c++  java
  • iOS开发系列--触摸事件、手势识别、摇晃事件、耳机线控

    iOS事件

    在iOS中事件分为三类:

    1. 触摸事件:通过触摸、手势进行触发(比如手指点击、缩放) 
    2. 运动事件:通过加速器进行触发(比如手机晃动) 
    3. 远程控制事件:通过其它远程设备触发(比如耳机控制button)

    下图是苹果官方对于这三种事件的形象描写叙述:

    events_to_app_2x

    在iOS中并非全部的类都能处理接收并事件,仅仅有继承自UIResponder类的对象才干处理事件(如我们经常使用的UIView、UIViewController、UIApplication都继承自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 NS_AVAILABLE_IOS(3_0); 运动開始时运行;
    - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event NS_AVAILABLE_IOS(3_0); 运动结束后运行;
    - (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event NS_AVAILABLE_IOS(3_0); 运动被意外取消时运行。
    远程控制事件  
    - (void)remoteControlReceivedWithEvent:(UIEvent *)event NS_AVAILABLE_IOS(4_0); 接收到远程控制消息时运行。

    触摸事件

    基础知识

    三类事件中触摸事件在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{
        //取得一个触摸对象(对于多点触摸可能有多个对象)
        UITouch *touch=[touches anyObject];
        //NSLog(@"%@",touch);
        
        //取得当前位置
        CGPoint current=[touch locationInView:self.view];
        //取得前一个位置
        CGPoint previous=[touch previousLocationInView:self.view];
        
        //移动前的中点位置
        CGPoint center=_image.center;
        //移动偏移量
        CGPoint offset=CGPointMake(current.x-previous.x, current.y-previous.y);
        
        //又一次设置新位置
        _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

    如今执行程序:

    TouchEventEffect

    上面演示样例中我们用到了UITouch类,当运行触摸事件时会将这个对象传入。在这个对象中包括了触摸的全部信息:

    • window:触摸时所在的窗体 
    • view:触摸时所在视图 
    • tapCount:短时间内点击的次数 
    • timestamp:触摸产生或变化的时间戳 
    • phase:触摸周期内的各个状态 
    • locationInView:方法:取得在指定视图的位置 
    • previousLocationInView:方法:取得移动的前一个位置

    从上面执行效果能够看到不管是选择KCImage拖动还是在界面其它任何位置拖动都能达到移动图片的效果。既然KCImage是UIView当然在KCImage中也能触发相应的触摸事件,如果在KCImage中定义三个相应的事件:

    //
    //  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

    此时假设执行程序会发现假设拖动KCImage无法达到预期的效果,可是能够发现此时会调用KCImage的触摸事件而不会调用KCTouchEventViewController中的触摸事件。假设直接拖拽其它空白位置则能够正常拖拽。并且从输出信息能够发现此时调用的是视图控制器的触摸事件。

    这是为什么呢?要解答这个问题我们须要了解iOS中事件的处理机制。

    事件处理机制

    在iOS中发生触摸后,事件会增加到UIApplication事件队列(在这个系列关于iOS开发的第一篇文章中我们分析iOS程序原理的时候就说过程序执行后UIApplication会循环监听用户操作)。UIApplication会从事件队列取出最前面的事件并分发处理,通常先分发给应用程序主窗体,主窗体会调用hitTest:withEvent:方法(如果称为方法A,注意这是UIView的方法),查找合适的事件触发视图(这里通常称为“hit-test view”):

    1. 在顶级视图(key window的视图)上调用pointInside:withEvent:方法推断触摸点是否在当前视图内;
    2. 假设返回NO,那么A返回nil。
    3. 假设返回YES,那么它会向当前视图的全部子视图(key window的子视图)发送hitTest:withEvent:消息。遍历全部子视图的顺序是从subviews数组的末尾向前遍历(从界面最上方開始向下遍历)。
    4. 假设有subview的hitTest:withEvent:返回非空对象则A返回此对象,处理结束(注意这个过程。子视图也是依据pointInside:withEvent:的返回值来确定是返回空还是当前子视图对象的。而且这个过程中假设子视图的hidden=YES、userInteractionEnabled=NO或者alpha小于0.1都会并忽略)。
    5. 假设全部subview遍历结束仍然没有返回非空对象,则A返回顶级视图;

    上面的步骤就是点击检測的过程。事实上就是查找事件触发者的过程。

    触摸对象并不是就是事件的响应者(比如上面第一个样例中没有重写KCImage触摸事件时,KCImge作为触摸对象,可是事件响应者却是UIViewController),检測到了触摸的对象之后。事件究竟是怎样响应呢?这个过程就必须引入一个新的概念“响应者链”。

    什么是响应者链呢?我们知道在iOS程序中不管是最后面的UIWindow还是最前面的某个button,它们的摆放是有前后关系的,一个控件能够放到还有一个控件上面或以下,那么用户点击某个控件时是触发上面的控件还是以下的控件呢,这样的先后关系构成一个链条就叫“响应者链”。

    在iOS中响应者链的关系能够用下图表示:

    iOS_responder_chain_2x

    当一个事件发生后首先看initial view是否能处理这个事件,假设不能则会将事件传递给其上级视图(inital view的superView)。假设上级视图仍然无法处理则会继续往上传递;一直传递到视图控制器view controller,首先推断视图控制器的根视图view是否能处理此事件。假设不能则接着推断该视图控制器是否能处理此事件。假设还是不能则继续向上传递。(对于第二个图视图控制器本身还在还有一个视图控制器中,则继续交给父视图控制器的根视图,假设根视图不能处理则交给父视图控制器处理)。一直到window,假设window还是不能处理此事件则继续交给application(UIApplication单例对象)处理。假设最后application还是不能处理此事件则将其丢弃。

    这个过程大家理解起来并不难,关键问题是在这个过程中各个对象怎样知道自己能不能处理该事件呢?对于继承UIResponder的对象,其不能处理事件有几个条件:

    • userInteractionEnabled=NO 
    • hidden=YES 
    • alpha=0~0.01 
    • 没有实现開始触摸方法(注意是touchesBegan:withEvent:而不是移动和结束触摸事件)

    当然前三点都是针对UIView控件或其子控件而言的。第四点能够针对UIView也能够针对视图控制器等其它UIResponder子类。

    对于第四种情况这里再次强调是对象中重写了開始触摸方法,则会处理这个事件,假设只写了移动、停止触摸或取消触摸事件(或者这三个事件都重写了)没有写開始触摸事件。则此事件该对象不会进行处理。

    相信到了这里大家对于上面点击图片为什么不能拖拽已经非常明白了。其实通过前面的解释大家应该能够猜到即使KCImage实现了開始拖拽方法,假设在KCTouchEventViewController中设置KCImage对象的userInteractionEnabled为NO也是能够拖拽的。

    注意:上面提到hitTest:withEvent:能够指定触发事件的视图,这里就不再举例说明,这种方法重写情况比較少,一般用于自己定义手势,有兴趣的童鞋能够訪问:Event Delivery: The Responder Chain

     

    手势识别

    简单介绍

    通过前面的内容我们能够看到触摸事件使用起来比較easy。可是对于多个手指触摸并进行不同的变化操作就要复杂的多了。比如说假设两个手指捏合,我们尽管在触摸開始、移动等事件中能够通过UITouchs得到两个触摸对象,可是我们怎样能推断用户是用两个手指捏合还是横扫或者拖动呢?在iOS3.2之后苹果引入了手势识别,对于用户经常使用的手势操作进行了识别并封装成详细的类供开发人员使用,这样在开发过程中我们就不必再自己编写算法识别用户的触摸操作了。

    在iOS中有六种手势操作:

    手势 说明
    UITapGestureRecognizer 点按手势
    UIPinchGestureRecognizer 捏合手势
    UIPanGestureRecognizer 拖动手势
    UISwipeGestureRecognizer 轻扫手势,支持四个方向的轻扫。可是不同的方向要分别定义轻扫手势
    UIRotationGestureRecognizer 旋转手势
    UILongPressGestureRecognizer 长按手势

    全部的手势操作都继承于UIGestureRecognizer,这个类本身不能直接使用。这个类中定义了这几种手势共同拥有的一些属性和方法(下表仅列出经常使用属性和方法):

    名称 说明
    属性  
    @property(nonatomic,readonly) UIGestureRecognizerState state; 手势状态
    @property(nonatomic, getter=isEnabled) BOOL enabled; 手势是否可用
    @property(nonatomic,readonly) UIView *view; 触发手势的视图(一般在触摸运行操作中我们能够通过此属性获得触摸视图进行操作)
    @property(nonatomic) BOOL delaysTouchesBegan; 手势识别失败前不运行触摸開始事件。默觉得NO;假设为YES,那么成功识别则不运行触摸開始事件,失败则运行触摸開始事件;假设为NO,则无论成功与否都运行触摸開始事件;
    方法  
    - (void)addTarget:(id)target action:(SEL)action; 加入触摸运行事件
    - (void)removeTarget:(id)target action:(SEL)action; 移除触摸运行事件
    - (NSUInteger)numberOfTouches; 触摸点的个数(同一时候触摸的手指数)
    - (CGPoint)locationInView:(UIView*)view;  在指定视图中的相对位置
    - (CGPoint)locationOfTouch:(NSUInteger)touchIndex inView:(UIView*)view; 触摸点相对于指定视图的位置
    - (void)requireGestureRecognizerToFail:(UIGestureRecognizer *)otherGestureRecognizer; 指定一个手势须要还有一个手势运行失败才会运行
    代理方法  
    - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer; 一个控件的手势识别后是否阻断手势识别继续向下传播,默认返回NO;假设为YES,响应者链上层对象触发手势识别后,假设下层对象也加入了手势并成功识别也会继续运行。否则上层对象识别后则不再继续传播;

    手势状态

    这里着重解释一下上表中手势状态这个对象。在六种手势识别中,仅仅有一种手势是离散手势,它就是UITapGestureRecgnier。

    离散手势的特点就是一旦识别就无法取消。并且仅仅会调用一次手势操作事件(初始化手势时指定的触发方法)。换句话说其它五种手势是连续手势。连续手势的特点就是会多次调用手势操作事件,并且在连续手势识别后能够取消手势。

    从下图能够看出两者调用操作事件的次数是不同的:

    discrete_vs_continuous_2x

    在iOS中将手势状态分为例如以下几种:

    typedef NS_ENUM(NSInteger, UIGestureRecognizerState) {
        UIGestureRecognizerStatePossible,   // 尚未识别是何种手势操作(但可能已经触发了触摸事件),默认状态
        
        UIGestureRecognizerStateBegan,      // 手势已经開始,此时已经被识别,可是这个过程中可能发生变化,手势操作尚未完毕
        UIGestureRecognizerStateChanged,    // 手势状态发生转变
        UIGestureRecognizerStateEnded,      // 手势识别操作完毕(此时已经松开手指)
        UIGestureRecognizerStateCancelled,  // 手势被取消,恢复到默认状态
        
        UIGestureRecognizerStateFailed,     // 手势识别失败,恢复到默认状态
        
        UIGestureRecognizerStateRecognized = UIGestureRecognizerStateEnded // 手势识别完毕,同UIGestureRecognizerStateEnded
    };
    • 对于离散型手势UITapGestureRecgnizer要么被识别。要么失败,点按(假设点按次数设置为1,而且没有加入长按手势)下去一次不松开则此时什么也不会发生,松开手指马上识别并调用操作事件,而且状态为3(已完毕)。 
    • 可是连续手势要复杂一些,就拿旋转手势来说。假设两个手指点下去不做不论什么操作,此时并不能识别手势(由于我们还没旋转)可是事实上已经触发了触摸開始事件,此时处于状态0;假设此时旋转会被识别。也就会调用相应的操作事件,同一时候状态变成1(手势開始),可是状态1仅仅有一瞬间。紧接着状态变为2(由于我们的旋转须要持续一会),而且反复调用操作事件(假设在事件中打印状态会反复打印2)。松开手指,此时状态变为3。并调用1次操作事件。

    为了大家更好的理解这个状态的变化,最好还是在操作事件中打印事件状态。会发如今操作事件中的状态永远不可能为0(默认状态),由于仅仅要调用此事件说明已经被识别了。

    前面也说过。手势识别从根本还是调用触摸事件而完毕的,连续手势之所以会发生状态转换全然是由于触摸事件中的移动事件造成的,没有移动事件也就不存在这个过程中状态变化。

    大家通过苹果官方的分析图再理解一下上面说的内容:

    gr_state_transitions_2x

    使用手势

    在iOS中加入手势比較简单。能够归纳为下面几个步骤:

    1. 创建相应的手势对象; 
    2. 设置手势识别属性【可选】; 
    3. 附加手势到指定的对象; 
    4. 编写手势操作方法。

    为了帮助大家理解,以下以一个图片查看程序演示一下上面几种手势,在这个程序中我们完毕以下功能:

    假设点按图片会在导航栏显示图片名称;

    假设长按图片会显示删除button,提示用户是否删除;

    假设捏合会放大、缩小图片;

    假设轻扫会切换到下一张或上一张图片。

    假设旋转会旋转图片;

    假设拖动会移动图片。

    详细布局草图例如以下:

    PhotoViewerPrototype

    为了显示导航条,我们首先将主视图控制器KCPhotoViewController放入一个导航控制器,然后在主视图控制器中放一个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;//当前图片索引
    }
    
    @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;//设置内容模式为缩放填充
        _imageView.userInteractionEnabled=YES;//这里必须设置为YES。否则无法接收手势操作
        [self.view addSubview:_imageView];
        
        //加入默认图片
        UIImage *image=[UIImage imageNamed:@"0.jpg"];
        _imageView.image=image;
        [self showPhotoName];
        
    }
    
    #pragma mark 加入手势
    -(void)addGesture{
        /*加入点按手势*/
        //创建手势对象
        UITapGestureRecognizer *tapGesture=[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapImage:)];
        //设置手势属性
        tapGesture.numberOfTapsRequired=1;//设置点按次数,默觉得1,注意在iOS中非常少用双击操作
        tapGesture.numberOfTouchesRequired=1;//点按的手指数
        //加入手势到对象(注意,这里加入到了控制器视图中。而不是图片上,否则点击空白无法隐藏导航栏)
        [self.view addGestureRecognizer:tapGesture];
        
        
        /*加入长按手势*/
        UILongPressGestureRecognizer *longPressGesture=[[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(longPressImage:)];
        longPressGesture.minimumPressDuration=0.5;//设置长按时间。默认0.5秒。一般这个值不要改动
        //注意因为我们要做长按提示删除操作。因此这个手势不再加入到控制器视图上而是加入到了图片上
        [_imageView addGestureRecognizer:longPressGesture];
        
        /*加入捏合手势*/
        UIPinchGestureRecognizer *pinchGesture=[[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(pinchImage:)];
        [self.view addGestureRecognizer:pinchGesture];
        
        /*加入旋转手势*/
        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];
        
        /*加入轻扫手势*/
        //注意一个轻扫手势仅仅能控制一个方向,默认向右,通过direction进行方向控制
        UISwipeGestureRecognizer *swipeGestureToRight=[[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipeImage:)];
        //swipeGestureToRight.direction=UISwipeGestureRecognizerDirectionRight;//默觉得向右轻扫
        [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 点按隐藏或显示导航栏
    -(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属性能够获取点按的视图
        //UIImageView *imageView=(UIImageView *)gesture.view;
        
        //因为连续手势此方法会调用多次,所以须要推断其手势状态
        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){//结束后恢复
            [UIView animateWithDuration:.5 animations:^{
                _imageView.transform=CGAffineTransformIdentity;//取消一切形变
            }];
        }
    }
    
    #pragma mark 旋转图片
    -(void)rotateImage:(UIRotationGestureRecognizer *)gesture{
        //NSLog(@"rotate:%i",gesture.state);
        if (gesture.state==UIGestureRecognizerStateChanged) {
            //旋转手势中rotation属性记录了旋转弧度
            _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 轻扫则查看下一张或上一张
    //注意尽管轻扫手势是连续手势,可是仅仅有在识别结束才会触发,不用推断状态
    -(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

    执行效果:

    GestureRecognizerEffect

    在上面演示样例中须要强调几点:

    • UIImageView默认是不支持交互的,也就是userInteractionEnabled=NO 。因此要接收触摸事件(手势识别),必须设置userInteractionEnabled=YES(在iOS中UILabel、UIImageView的userInteractionEnabled默认都是NO,UIButton、UITextField、UIScrollView、UITableView等默认都是YES)。 
    • 轻扫手势尽管是连续手势可是它的操作事件仅仅会在识别结束时调用一次。其它连续手势都会调用多次,一般须要进行状态推断;此外轻扫手势支持四个方向。可是假设要支持多个方向须要加入多个轻扫手势。

    手势冲突

    细心的童鞋会发如今上面的演示效果图中当切换到下一张或者上一张图片时并没有轻扫图片而是在空白地方轻扫完毕。原因是假设我轻扫图片会引起拖动手势而不是轻扫手势。换句话说。两种手势发生了冲突。

    冲突的原因非常easy,拖动手势的操作事件是在手势的開始状态(状态1)识别运行的,而轻扫手势的操作事件仅仅有在手势结束状态(状态3)才干运行。因此轻扫手势就作为了牺牲品没有被正确识别。我们理想的情况当然是假设在图片上拖动就移动图片,假设在图片上轻扫就翻动图片。

    怎样解决这个冲突呢?

    在iOS中,假设一个手势A的识别部分是还有一个手势B的子部分时。默认情况下A就会先识别,B就无法识别了。

    要解决这个冲突能够利用- (void)requireGestureRecognizerToFail:(UIGestureRecognizer *)otherGestureRecognizer;方法来完毕。

    正是前面表格中UIGestureRecognizer的最后一个方法。这种方法能够指定某个手势运行的前提是还有一个手势失败才会识别运行。

    也就是说假设我们指定拖动手势的运行前提为轻扫手势失败就能够了,这样一来当我们手指轻轻滑动时系统会优先考虑轻扫手势,假设最后发现该操作不是轻扫,那么就会运行拖动。仅仅要将以下的代码加入到加入手势之后就能解决问题了(注意为了更加清晰的区分拖动和轻扫[模拟器中拖动略微快一点就识别成了轻扫],这里将长按手势的前提设置为拖动失败。避免演示拖动时长按手势会被识别):

        
        //解决在图片上滑动时拖动手势和轻扫手势的冲突
        [panGesture requireGestureRecognizerToFail:swipeGestureToRight];
        [panGesture requireGestureRecognizerToFail:swipeGestureToLeft];
        //解决拖动和长按手势之间的冲突
        [longPressGesture requireGestureRecognizerToFail:panGesture];

    执行效果:

    GestureRecognizerEffect2

    两个不同控件的手势同一时候运行

    我们知道在iOS的触摸事件中,事件触发是依据响应者链进行的。上层触摸事件运行后就不再向下传播。默认情况下手势也是类似的,先识别的手势会阻断手势识别操作继续传播。

    那么怎样让两个有层次关系而且都加入了手势的控件都能正确识别手势呢?答案就是利用代理的-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer方法。

    这个代理方法默认返回NO。会阻断继续向下识别手势,假设返回YES则能够继续向下传播识别。

    以下的代码控制演示了当在图片上长按时同一时候能够识别控制器视图的长按手势(注意当中我们还控制了仅仅有在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;//当前图片索引
    }
    
    @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;//设置内容模式为缩放填充
        _imageView.userInteractionEnabled=YES;//这里必须设置位YES。否则无法接收手势操作
        //_imageView.multipleTouchEnabled=YES;//支持多点触摸,默认就是YES
        [self.view addSubview:_imageView];
        
        //加入默认图片
        UIImage *image=[UIImage imageNamed:@"0.jpg"];
        _imageView.image=image;
        [self showPhotoName];
        
    }
    
    #pragma mark 加入手势
    -(void)addGesture{
        /*加入点按手势*/
        //创建手势对象
        UITapGestureRecognizer *tapGesture=[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapImage:)];
        //设置手势属性
        tapGesture.numberOfTapsRequired=1;//设置点按次数,默觉得1,注意在iOS中非常少用双击操作
        tapGesture.numberOfTouchesRequired=1;//点按的手指数
        //加入手势到对象(注意。这里加入到了控制器视图中,而不是图片上,否则点击空白无法隐藏导航栏)
        [self.view addGestureRecognizer:tapGesture];
        
        
        /*加入长按手势*/
        UILongPressGestureRecognizer *longPressGesture=[[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(longPressImage:)];
        longPressGesture.minimumPressDuration=0.5;//设置长按时间。默认0.5秒,一般这个值不要改动
        //注意因为我们要做长按提示删除操作。因此这个手势不再加入到控制器视图上而是加入到了图片上
        [_imageView addGestureRecognizer:longPressGesture];
        
        /*加入捏合手势*/
        UIPinchGestureRecognizer *pinchGesture=[[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(pinchImage:)];
        [self.view addGestureRecognizer:pinchGesture];
        
        /*加入旋转手势*/
        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];
        
        /*加入轻扫手势*/
        //注意一个轻扫手势仅仅能控制一个方向,默认向右。通过direction进行方向控制
        UISwipeGestureRecognizer *swipeGestureToRight=[[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipeImage:)];
        //swipeGestureToRight.direction=UISwipeGestureRecognizerDirectionRight;//默认位向右轻扫
        [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];
        
        
        /*演示不同视图的手势同一时候运行
         *在上面_imageView已经加入了长按手势,这里给视图控制器的视图也加上长按手势让两者都运行
         *
         */
        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 点按隐藏或显示导航栏
    -(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属性能够获取点按的视图
        //UIImageView *imageView=(UIImageView *)gesture.view;
        
        //因为连续手势此方法会调用多次,所以需求推断其手势状态
        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){//结束后恢复
            [UIView animateWithDuration:.5 animations:^{
                _imageView.transform=CGAffineTransformIdentity;//取消一切形变
            }];
        }
    }
    
    #pragma mark 旋转图片
    -(void)rotateImage:(UIRotationGestureRecognizer *)gesture{
        //NSLog(@"rotate:%i",gesture.state);
        if (gesture.state==UIGestureRecognizerStateChanged) {
            //旋转手势中rotation属性记录了旋转弧度
            _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 轻扫则查看下一张或上一张
    //注意尽管轻扫手势是连续手势。可是仅仅有在识别结束才会触发,不用推断状态
    -(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

    运动事件

    前面我们主要介绍了触摸事件以及由触摸事件引出的手势识别,以下我们简介一下运动事件。在iOS中和运动相关的有三个事件:開始运动、结束运动、取消运动。

    监听运动事件对于UI控件有个前提就是监听对象必须是第一响应者(对于UIViewController视图控制器和UIAPPlication没有此要求)。

    这也就意味着假设监听的是一个UI控件那么-(BOOL)canBecomeFirstResponder;方法必须返回YES。同一时候控件显示时(在-(void)viewWillAppear:(BOOL)animated;事件中)调用视图控制器的becomeFirstResponder方法。当视图不再显示时(在-(void)viewDidDisappear:(BOOL)animated;事件中)注销第一响应者身份。

    因为视图控制器默认就能够调用运动開始、运动结束事件在此不再举例。如今最好还是如果我们如今在开发一个摇一摇找人的功能,这里我们就自己定义一个图片展示控件。在这个图片控件中我们能够通过摇晃随机切换界面图片。代码比較简单:

    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 设置控件能够成为第一响应者
    -(BOOL)canBecomeFirstResponder{
        return YES;
    }
    
    #pragma mark 运动開始
    -(void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event{
        //这里仅仅处理摇晃事件
        if (motion==UIEventSubtypeMotionShake) {
            self.image=[self getImage];
        }
    }
    #pragma mark 运动结束
    -(void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event{
        
    }
    
    
    #pragma mark 随机取得图片
    -(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 视图显示时让控件变成第一响应者
    -(void)viewDidAppear:(BOOL)animated{
        _imageView=[[KCImageView alloc]initWithFrame:[UIScreen mainScreen].applicationFrame];
        _imageView.userInteractionEnabled=true;
        [self.view addSubview:_imageView];
        [_imageView becomeFirstResponder];
    }
    
    #pragma mark 视图不显示时注销控件第一响应者的身份
    -(void)viewDidDisappear:(BOOL)animated{
        [_imageView resignFirstResponder];
    }
    
    /*视图控制器的运动事件*/
    //-(void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event{
    //    NSLog(@"motion begin...");
    //}
    //
    //-(void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event{
    //    NSLog(@"motion end.");
    //}
    
    @end

    执行效果(下图演示时使用了模拟器摇晃操作的快捷键,没有使用鼠标操作):

    MotionEffect

    远程控制事件

    在今天的文章中还剩下最后一类事件:远程控制,远程控制事件这里主要说的就是耳机线控操作。在前面的事件列表中,大家能够看到在iOS中和远程控制事件有关的仅仅有一个- (void)remoteControlReceivedWithEvent:(UIEvent *)event NS_AVAILABLE_IOS(4_0);事件

    要监听到这个事件有三个前提(视图控制器UIViewController或应用程序UIApplication仅仅有两个)

    • 启用远程事件接收(使用[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];方法)。 
    • 对于UI控件相同要求必须是第一响应者(对于视图控制器UIViewController或者应用程序UIApplication对象监听无此要求)。 
    • 应用程序必须是当前音频的控制者,也就是在iOS 7中通知栏中当前音频播放程序必须是我们自己开发程序。

    基于第三点我们必须明白,如果我们的程序不想要控制音频,仅仅是想利用远程控制事件做其它的事情。比如模仿iOS7中的按音量+键拍照是做不到的,眼下iOS7给我们的远程控制权限还仅限于音频控制(当然如果我们确实想要做一个和播放音频无关的应用可是又想进行远程控制。也能够隐藏一个音频播放操作,拿到远程控制操作权后进行远程控制)。

    运动事件中我们也提到一个枚举类型UIEventSubtype。并且我们利用它来推断是否运动事件,在枚举中还包括了我们运程控制的子事件类型,我们先来熟悉一下这个枚举(从远程控制子事件类型也不难发现它和音频播放有密切关系):

    typedef NS_ENUM(NSInteger, UIEventSubtype) {
        // 不包括不论什么子事件类型
        UIEventSubtypeNone                              = 0,
        
        // 摇晃事件(从iOS3.0開始支持此事件)
        UIEventSubtypeMotionShake                       = 1,
        
        //远程控制子事件类型(从iOS4.0開始支持远程控制事件)
        //播放事件【操作:停止状态下。按耳机线控中间button一下】
        UIEventSubtypeRemoteControlPlay                 = 100,
        //暂停事件
        UIEventSubtypeRemoteControlPause                = 101,
        //停止事件
        UIEventSubtypeRemoteControlStop                 = 102,
        //播放或暂停切换【操作:播放或暂停状态下,按耳机线控中间button一下】
        UIEventSubtypeRemoteControlTogglePlayPause      = 103,
        //下一曲【操作:按耳机线控中间button两下】
        UIEventSubtypeRemoteControlNextTrack            = 104,
        //上一曲【操作:按耳机线控中间button三下】
        UIEventSubtypeRemoteControlPreviousTrack        = 105,
        //快退開始【操作:按耳机线控中间button三下不要松开】
        UIEventSubtypeRemoteControlBeginSeekingBackward = 106,
        //快退停止【操作:按耳机线控中间button三下到了快退的位置松开】
        UIEventSubtypeRemoteControlEndSeekingBackward   = 107,
        //快进開始【操作:按耳机线控中间button两下不要松开】
        UIEventSubtypeRemoteControlBeginSeekingForward  = 108,
        //快进停止【操作:按耳机线控中间button两下到了快进的位置松开】
        UIEventSubtypeRemoteControlEndSeekingForward    = 109,
    };

    这里我们将远程控制事件放到视图控制器(其实非常少直接加入到UI控件。一般就是加入到UIApplication或者UIViewController),模拟一个音乐播放器。

    1.首先在应用程序启动后设置接收远程控制事件,而且设置音频会话保证后台执行能够播放(注意要在应用配置中设置同意多任务)

    //
    //  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];
        
        //设置全局导航条风格和颜色
        [[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;
        
        //设置播放会话,在后台能够继续播放(还须要设置程序同意后台执行模式)
        [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
        if(![[AVAudioSession sharedInstance] setActive:YES error:nil])
        {
            NSLog(@"Failed to set up a session.");
        }
        
        
        //启用远程控制事件接收
        [[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.在视图控制器中加入远程控制事件并音频播放进行控制

    //
    //  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 远程控制事件
    -(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 界面状态
    -(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

    执行效果(真机截图):

    MusicPlayer

    注意:

    • 为了模拟一个真实的播放器。程序中我们启用了后台执行模式,配置方法:在info.plist中加入UIBackgroundModes而且加入一个元素值为audio。 
    • 即使利用线控进行音频控制我们也无法监控到耳机添加音量、减小音量的按键操作(另外注意模拟器无法模拟远程事件。请使用真机调试)。 
    • 子事件的类型跟当前音频状态有直接关系,点击一次播放/暂停button到底是【播放】还是【播放/暂停】状态切换要看当前音频处于什么状态,假设处于停止状态则点击一下是播放。假设处于暂停或播放状态点击一下是暂停和播放切换。

       

    • 上面的程序已在真机调试通过。不管是线控还是点击应用button都能够控制播放或暂停。
  • 相关阅读:
    vue类似tab切换的效果,显示和隐藏的判断。
    vue 默认展开详情页
    vue echarts圆角阴影效果
    vue画图运用echarts
    随机函数rand()
    Qt解析CSV文件
    Qt生成CSV 文件
    QRegExp解析
    Qt中csv文件的导入与导出
    Qt 生成word、pdf文档
  • 原文地址:https://www.cnblogs.com/yangykaifa/p/7371763.html
Copyright © 2011-2022 走看看