Objective-C的UIGestureRecognizer(手势识别器)学习笔记

UIGestureRecognizer -- 手势识别器的基类

具体手势识别器的基类。一个手势识别器对象,或者简单地说一个手势识别器,解耦识别了一系列触摸(或其他输入)的逻辑,并对该识别进行操作。当其中一个对象识别到一个常见的手势,或者在某些情况下,手势中的一个变化,它会向每个指定的目标对象发送一个动作消息。

UIGestureRecognizer的具体子类如下:

  • UITapGestureRecognizer 点击手势识别器
  • UIPinchGestureRecognizer 缩放手势识别器
  • UIRotationGestureRecognizer 旋转手势识别器
  • UISwipeGestureRecognizer 轻扫手势识别器
  • UIPanGestureRecognizer 平移手势识别器
  • UIScreenEdgePanGestureRecognizer 边缘侧滑手势识别器
  • UILongPressGestureRecognizer 长按手势识别器

UIGestureRecognizer类定义了一组可为所有具体手势识别器配置的常见行为。它还可以与其委托(一个采用UIGestureRecognizerDelegate协议的对象)通信,从而能够对某些行为进行更细腻的定制。

手势识别器对一个特定视图和该视图的所有子视图进行触摸检验。因此,它必须与那个视图相关联。要建立这种关联,必须调用UIView的addGestureRecognizer:方法。手势识别器不参与视图的响应器链

手势识别器有一个或多个与之关联的目标-操作对。如果有多个目标-操作对,它们是离散的,而不是连续的。对手势的识别导致将动作消息分派到每个相关对的目标。被调用的动作方法必须符合下列签名之一 :

- (void)handleGesture;

- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer;

符合后一种签名的方法允许目标在某些情况下查询发送消息的手势识别器以获取附加信息。例如,目标可以向UIRotationGestureRecognizer对象询问自上次调用该手势的动作方法以来的旋转角度(以弧度表示)。手势识别器的代理也可以通过调用locationInView:或locationOfTouch:inView:来询问手势的位置。

手势识别器解释的手势可以是离散的,也可以是连续的。一个离散的手势,如双击,只在多点触控序列中出现一次,结果是发送一个动作。然而,当手势识别器解释一个连续的手势(例如旋转手势)时,它会针对每个增量的变化发送一个动作消息,直到多点触控序列结束。

窗口优先将触摸事件传递给手势识别器。如果一个手势识别器分析了多点触摸序列中的触摸流,但没有识别它的手势,窗口会将触摸事件传递给命中的最佳响视图(hit-tested view)。如果手势识别器识别了它的手势,命中的最佳响视图(hit-tested view)的其余触摸将被取消。手势识别的动作序列通常遵循cancelsTouchesInView、delaysTouchesBegan、delaysTouchesEnded属性的默认值确定的路径:

cancelsTouchesInView - 如果手势识别器识别出了这个手势,它就会从视图中解除该手势剩余的触摸操作(这样窗口就不会传递这些操作了)。窗口通过(touchesCancelled:withEvent:)消息取消之前传递的触摸。如果手势识别器不能识别它的手势,视图会接收多点触摸序列中的所有触摸。

delaysTouchesBegan - 只要手势识别器在分析触摸事件时没有识别出它的手势,窗口就会在UITouchPhaseBegan(触摸开始)阶段中保留向附加视图发送的触摸对象。如果手势识别器随后识别了它的手势,视图就不会接收这些触摸对象。如果手势识别器不能识别它的手势,窗口将在视图的touchesBegan:withEvent:方法的调用中交付这些对象(可能还有后续的touchesMoved:withEvent:调用来通知它触摸当前位置)。

delaysTouchesEnded - 只要手势识别器在分析触摸事件时没有识别出它的手势,窗口就会在UITouchPhaseEnded(触摸结束)阶段中保留向附加视图发送的触摸对象。如果手势识别器随后识别出它的手势,触摸就会被取消(在touchesCancelled:withEvent: message中)。如果手势识别器不能识别它的手势,窗口将在视图的touchesEnded:withEvent:方法的调用中交付这些对象。

常用属性
@property(nullable, nonatomic,readonly) UIView *view;    

属性描述手势识别器附加到的视图。使用addGestureRecognizer:方法将手势识别器附加(或添加)到的UIView对象。

@property(nonatomic, getter=isEnabled) BOOL enabled;

属性描述指示是否启用手势识别器的布尔属性。禁用手势识别器,使其不接收触摸。默认值为“YES”。如果在手势识别器当前识别一个手势时将此属性更改为NO,则手势识别器将转换为取消状态。

@property(nonatomic,readwrite) UIGestureRecognizerState state; 

属性描述手势识别器的当前状态。手势识别器可能处于的状态由类型为UIGestureRecognizerState的常量表示。其中一些状态不适用于离散的手势。

  • UIGestureRecognizerState提供的枚举值如下:
typedef NS_ENUM(NSInteger, UIGestureRecognizerState) {
    //识别器尚未识别其手势,但可能正在评估触摸事件。这是默认状态
    UIGestureRecognizerStatePossible,  
    //识别器已经接收到被识别为手势的触摸。操作方法将在运行循环的下一个回合调用
    UIGestureRecognizerStateBegan,
    //识别器接收到的触摸被识别为手势的变化。操作方法将在运行循环的下一个回合调用
    UIGestureRecognizerStateChanged,    
    //识别器已经接收到被识别的触摸作为手势的结束。该操作方法将在运行循环的下一个回合被调用,识别器将被重置为UIGestureRecognizerStatePossible
    UIGestureRecognizerStateEnded, 
    //识别器已接收到导致手势取消的触摸。操作方法将在运行循环的下一轮调用。识别器将被重置为UIGestureRecognizerStatePossible
    UIGestureRecognizerStateCancelled,  
    //识别器接收到的触摸序列不能被识别为手势。操作方法将不会被调用,识别器将重置为UIGestureRecognizerStatePossible
    //离散手势——识别离散事件但不报告更改(例如,点击)的手势识别器不会在开始状态和更改状态之间转换,而且不会失败或被取消
    UIGestureRecognizerStateFailed,     
    //识别器已经接收到被识别为手势的触摸。该操作方法将在运行循环的下一个回合被调用,识别器将被重置为UIGestureRecognizerStatePossible
    UIGestureRecognizerStateRecognized = UIGestureRecognizerStateEnded 
};

@property(nullable,nonatomic,weak) id <UIGestureRecognizerDelegate> delegate;

属性描述手势识别器的代理。手势识别器保持对其代理的弱引用。代理必须采用UIGestureRecognizerDelegate协议并实现其一个或多个方法。

常用函数
- (CGPoint)locationInView:(nullable UIView*)view;  

函数描述返回由调用方表示的手势在给定视图中的作为位置计算的点。返回值是由UIKit框架计算的手势的通用单点位置。它通常是手势中涉及的触摸的质心。对于UISwipeGestureRecognizer和UITapGestureRecognizer类的对象,此方法返回的位置对于手势具有特殊的意义。

参数 :

view : 一个在其上发生手势的UIView对象。指定nil来表示窗口。

返回值 : 在视图的局部坐标系统中识别手势位置的点。如果视图为nil指定,该方法将返回窗口基础坐标系统中的手势位置。

- (void)addTarget:(id)target action:(SEL)action; 

函数描述向手势识别器对象添加目标和操作。可以多次调用此方法以指定多个目标-操作对。但是,如果请求添加已经添加的目标-操作对,则请求将被忽略。

参数 :

target : 当所表示的手势发生时,接收发送的动作信息的接收者。nil不是有效值。

action:一种选择器,用于标识要由操作消息调用的目标的方法。NULL不是有效值。

- (void)requireGestureRecognizerToFail:(UIGestureRecognizer *)otherGestureRecognizer;

函数描述创建对象时,在调用方和另一个手势识别器(otherGestureRecognizer)之间创建依赖关系(例如当想要一个单点手势时,需要一个双点手势失败后识别)。当手势识别器不是在应用程序或框架中的其他地方创建的,并且手势识别器集保持不变时,这种方法可以很好地工作。

此方法创建与另一个手势识别器(otherGestureRecognizer)的关系,该关系会延迟调用方从UIGestureRecognizerStatePossible状态的转换。调用方转换到的状态取决于其他识别器(otherGestureRecognizer)的情况:

  • 如果其他手势识别器(otherGestureRecognizer)转换到UIGestureRecognizerStateFailed,则调用方转换到它的正常下一个状态。
  • 如果其他手势识别器(otherGestureRecognizer)转换为UIGestureRecognizerStateRecognized或UIGestureRecognizerStateBegan,则调用方转换为UIGestureRecognizerStateFailed。

如果需要延迟地或在不同的视图层次结构中设置失败需求,使用gestureRecognizer:shouldRequireFailureOfGestureRecognizer:方法和gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer:方法代替。(注意shouldRequireFailureOfGestureRecognizer:方法和shouldBeRequiredToFailByGestureRecognizer:方法让子类定义类范围的失败需求。)

参数 :

otherGestureRecognizer :另一个手势识别器对象(UIGestureRecognizer子类的实例)。

UIGestureRecognizerDelegate -- 手势识别器代理

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer;

函数描述询问代理手势识别器是否应该开始识别触摸。当手势识别器试图转换出UIGestureRecognizerStatePossible状态时,将调用此方法。返回NO会导致手势识别器转换到UIGestureRecognizerStateFailed状态。

参数 :

gestureRecognizer : 抽象基类UIGestureRecognizer的子类的实例。这个手势识别器对象即将开始处理触摸,以确定它的手势是否正在发生。

返回值 : YES(默认)告诉手势识别器继续解释触摸,NO阻止它试图识别自己的手势。

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;

函数描述询问代理是否允许两个手势识别器同时识别手势。调用此方法,当手势识别器或其他手势识别器对一个手势的识别会阻止其他手势识别器识别其手势时。注意,返回YES保证允许同时识别;另一方面,返回NO不能保证防止同时识别,因为其他手势识别器的代理可能返回YES。

参数 :

gestureRecognizer : 抽象基类UIGestureRecognizer的子类的实例。这是向代理发送消息的对象。

otherGestureRecognizer:抽象基类UIGestureRecognizer的子类的实例。

返回值 : YES,允许手势识别器和其他手势识别器同时识别其手势。默认实现返回NO,任何两个手势都不能同时识别。

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer API_AVAILABLE(ios(7.0));

函数描述询问代理手势识别器的识别是否需要另一个手势识别器识别失败。该方法在每次识别尝试时被调用一次,因此失败识别可以被延迟地确定,并且可以跨视图层次在识别器之间设置。注意,返回YES保证会设置失败识别;另一方面,返回NO不能保证防止或删除一个失败识别,因为其他手势识别器可能通过使用自己的子类或代理方法使自己成为一个失败识别。

参数 :

gestureRecognizer : 抽象基类UIGestureRecognizer的子类的实例。这是向代理发送消息的对象。

otherGestureRecognizer : 抽象基类UIGestureRecognizer的子类的实例。

返回值 : YES,在前识别的手势识别器和其他手势识别器之间设置动态失败识别。默认实现返回NO,当前识别的手势识别器不需要其他手势识别器失败。

UITapGestureRecognizer -- 点击手势识别器

UIGestureRecognizer的一个具体子类,查找单个或多个点击。要识别手势,指定数量的手指必须按指定次数点击视图。点击手势对于手势识别器的每个状态来说,它们是离散的。因此,当手势开始时发送相关的动作消息,并针对每个中间状态发送,直到(包括)手势的结束状态。因此,处理点击手势的代码应该测试手势的状态。处理此手势的操作方法可以通过调用UIGestureRecognizer的locationInView:方法来获取手势的整体位置,如果有多个点击,则此位置是第一个点击位置;如果有多个触碰,则此位置是所有手指轻击视图的质心。客户端可以通过调用locationOfTouch:inView:方法在点击中获取特定点击的位置,如果允许多次点击,则此位置为第一次点击的位置。

常用属性
@property (nonatomic) NSUInteger  numberOfTapsRequired;

属性描述要识别手势的点击次数。默认值为1。

@property (nonatomic) NSUInteger  numberOfTouchesRequired API_UNAVAILABLE(tvOS); 

属性描述为使手势被识别所需的手指数。默认值为1。

\color{red}{练习的代码片段(视图添加单击与双击):}

@interface GestureRecognizerViewController ()<UIGestureRecognizerDelegate>

@end

@implementation GestureRecognizerViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.navigationItem.title = @"手势识别";
    [self createUI];
}

- (void)createUI{
    //测试视图
    UIView *testView = [[UIView alloc]initWithFrame:CGRectMake(CGRectGetMaxX(self.view.frame) / 2 - 100 / 2, CGRectGetMaxY(self.view.frame) / 2 - 100 / 2, 100, 100)];
    testView.backgroundColor = [UIColor redColor];
    [self.view addSubview:testView];
    
    //初始化点击手势识别器
    UITapGestureRecognizer *singleClick = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(singleClickEvent)];
    //要识别手势的点击次数
    singleClick.numberOfTapsRequired = 1;
    //手势被识别所需的手指数
    singleClick.numberOfTouchesRequired = 1;
    //添加点击手势识别器
    [testView addGestureRecognizer:singleClick];
    
    //初始化点击手势识别器
    UITapGestureRecognizer *doubleClick = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(doubleClickEvent)];
    //要识别手势的点击次数
    doubleClick.numberOfTapsRequired = 2;
    //手势被识别所需的手指数
    doubleClick.numberOfTouchesRequired = 1;
    //添加点击手势识别器
    [testView addGestureRecognizer:doubleClick];
    //双击手势识别失败后识别单击手势,没有识别失败为双击手势
    [singleClick requireGestureRecognizerToFail:doubleClick];

}

- (void)singleClickEvent{
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Alert" message:@"单击响应" preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *yesAction = [UIAlertAction actionWithTitle:@"YES" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action){
        NSLog(@"Top YES Button");
    }];
    [alertController addAction:yesAction];
    [self presentViewController:alertController animated:true completion:nil];
}

- (void)doubleClickEvent{
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Alert" message:@"双击响应" preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *yesAction = [UIAlertAction actionWithTitle:@"YES" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action){
        NSLog(@"Top YES Button");
    }];
    [alertController addAction:yesAction];
    [self presentViewController:alertController animated:true completion:nil];
}

@end

效果如图:

Jietu20200812-090417.gif

UIPinchGestureRecognizer -- 缩放手势识别器

UIGestureRecognizer的一个具体子类,用于查找涉及两次触碰的捏合手势。当用户将两个手指相对移动时,常规意义为缩小;当用户将两个手指移开时,常规意义为放大。

捏合手势是一种持续的姿势。当两次触摸移动到足以被视为捏手势时,手势开始(UIGestureRecognizerStateBegan)。当手指移动时(两个手指保持按下状态),手势会发生变化(UIGestureRecognizerStateChanged)。当两个手指从视图中抬起时,手势结束(UIGestureRecognizerStateEnded)。

常用属性
@property (nonatomic)          CGFloat scale;  

属性描述相对于屏幕坐标中两个触摸点的比例因子。可以设置比例因子,但这样做会重置缩进比例因子的速度。缩放显示时比例因子逐渐接近0,放大显示时,比例因子逐渐大于1,正常显示时为1,但每次当手势被识别时,比例因子都将重置。

@property (nonatomic,readonly) CGFloat velocity;

属性描述每秒缩进比例因子的速度。手指靠近是负值,手指相离是正值。

\color{red}{练习的代码片段:(视图缩放)}

@interface GestureRecognizerViewController ()<UIGestureRecognizerDelegate>

@end

@implementation GestureRecognizerViewController{
    //上一次手势的比例因子
    CGFloat lastRecognizerScale;
    //视图最大缩放比例
    CGFloat viewMaxScale ;
    //视图最小缩放比例
    CGFloat viewMinScale ;

    UIImageView *imageView;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.navigationItem.title = @"手势识别";
    [self initScale];
    [self createUI];
}

- (void)initScale{
    //初始化上一次手势的比例因子,默认为1
    lastRecognizerScale = 1;
    //初始化视图最大缩放比例,默认为2
    viewMaxScale = 2;
    //初始化视图最小缩放比例,默认为1
    viewMinScale = 1;
}

- (void)createUI{
    
    //缩放手势识别器测试图片视图
    imageView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"tian_kong_long"]];
    //设置框架矩形
    imageView.frame = CGRectMake(0, 0, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame));
    //设置内容的显示方式
    imageView.contentMode = UIViewContentModeScaleAspectFit;
    //设置与用户进行交互,UIImageView默认为NO
    imageView.userInteractionEnabled = YES;
    //添加UIImageView到视图
    [self.view addSubview:imageView];
    
    //初始化缩放手势识别器
    UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(pinchEvent:)];
    //添加缩放手势识别器
    [imageView addGestureRecognizer:pinch];
}

///缩放事件
- (void)pinchEvent:(UIPinchGestureRecognizer *)recognizer{
    //判断手势识别器的当前状态
    switch (recognizer.state) {
        case UIGestureRecognizerStateBegan://缩放开始
        case UIGestureRecognizerStateChanged://缩放改变
        {
            //获取当前视图的缩放比例
            CGFloat currentViewScale = [[imageView.layer valueForKeyPath:@"transform.scale"] floatValue];
            //记录两个触摸点的比例因子变化(加1使这个新产生的比例因子大于0),这个新产生的比例因子将作为每次调用次函数时,视图X与Y的缩放值
            CGFloat newRecognizerScale = recognizer.scale - lastRecognizerScale + 1;
            //限制视图的最大缩放值
            newRecognizerScale = MIN(newRecognizerScale, viewMaxScale / currentViewScale);
            //限制视图的最小缩放值
            newRecognizerScale = MAX(newRecognizerScale, viewMinScale / currentViewScale);
            //设置当前视图的缩放
            imageView.transform = CGAffineTransformScale(imageView.transform, newRecognizerScale, newRecognizerScale);
            //记录本次两个触摸点的比例因子的值作为上次两个触摸点的比例因子
            lastRecognizerScale = recognizer.scale;
        }
            break;
        case UIGestureRecognizerStateEnded://缩放结束
        {
            //每次缩放手势结束时,将记录的上一次手势的比例因子置为1
            lastRecognizerScale = 1;
        }
            break;

        default:
            break;
    }
}

@end

效果如图(模拟器使用Option:调出双指放大缩小效果。):

Jietu20200813-215634.gif

UIRotationGestureRecognizer -- 旋转手势识别器

UIGestureRecognizer的一个具体子类,用于查找涉及两次触碰的旋转手势。当用户相对移动手指做圆周运动时,底层视图应该以相应的方向和速度旋转。旋转是一种连续的姿态。当两个触摸移动到足以被认为是旋转时,它就开始了。当两个手指向下移动时,手势会发生变化。当两个手指都抬起时就结束了。在手势的每个阶段,手势识别器都会发送动作消息。

常用属性
@property (nonatomic)          CGFloat rotation;  

属性描述以弧度表示的手势旋转。旋转值可以设置为任意值,但设置旋转会重置速度。旋转值是随时间变化的单个值。随旋转方向有正负之分,两指顺时针转动为正,逆时针转动为负,该值不是上次报告旋转的增量值。每次当手势被识别时,旋转值都将被重置

@property (nonatomic,readonly) CGFloat velocity; 

属性描述旋转手势的速度(以弧度/秒为单位)。

\color{red}{练习代码片段(视图旋转):}

@interface GestureRecognizerViewController ()<UIGestureRecognizerDelegate>

@end

@implementation GestureRecognizerViewController{
    //测试缩放与旋转的图片视图
    UIImageView *imageView;
    //上一次旋转手势的旋转值
    CGFloat lastRotationValue;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.navigationItem.title = @"手势识别";
    [self createUI];
}

- (void)createUI{

    //旋转手势识别器测试图片视图
    imageView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"tian_kong_long"]];
    //设置框架矩形
    imageView.frame = CGRectMake(0, 300, CGRectGetWidth(self.view.frame), 200);
    //设置内容的显示方式
    imageView.contentMode = UIViewContentModeScaleAspectFit;
    //设置与用户进行交互,UIImageView默认为NO
    imageView.userInteractionEnabled = YES;
    //添加UIImageView到视图
    [self.view addSubview:imageView];
    
    //初始化旋转手势识别器
    UIRotationGestureRecognizer *rotation = [[UIRotationGestureRecognizer alloc]initWithTarget:self action:@selector(rotationEvent:)];
    //添加旋转手势识别器到视图
    [imageView addGestureRecognizer:rotation];

}

///旋转事件
- (void)rotationEvent:(UIRotationGestureRecognizer *)rotationGestureRecognizer{
    
    switch (rotationGestureRecognizer.state) {
        case UIGestureRecognizerStateBegan://旋转开始
        case UIGestureRecognizerStateChanged://旋转改变
        {
            NSLog(@"%f",rotationGestureRecognizer.rotation);
            //每次的设置图片的旋转值基于上次结束旋转图片所处位置的旋转值进行计算
            CGFloat newRotationValue = rotationGestureRecognizer.rotation + lastRotationValue;
            //获取附加手势的视图
            UIView *view = rotationGestureRecognizer.view;
            //获取视图的layer
            CALayer *layer = view.layer;
            //3D旋转效果
            CATransform3D rotationAndPerspectiveTransform = CATransform3DIdentity;
            
            rotationAndPerspectiveTransform.m34 = 1.0 / -500;
            
            //围绕向量(x,y,z)按角度弧度旋转t并返回结果。如果向量的长度为零,则行为未定义
            rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform,newRotationValue, 1.0f, 0.0f, 0.0f);
            //设置层内容的转换
            layer.transform = rotationAndPerspectiveTransform;
        }
            break;
        case UIGestureRecognizerStateEnded://旋转结束
        {
            //每次旋转手势结束时,将记录的上一次手势结束时图片所处位置的旋转值
            lastRotationValue += rotationGestureRecognizer.rotation;
        }
            break;
        default:
            break;
    }
}


@end

效果如图:

Jietu20200814-234301.gif

UISwipeGestureRecognizer -- 轻扫(滑动)手势识别器

UIGestureRecognizer的一个具体子类,用于查找在一个或多个方向上的滑动手势。滑动是一个离散的手势,因此每个手势只发送一次相关的动作消息。UISwipeGestureRecognizer在指定的触摸次数(numberOfTouchesRequired)大部分移动到允许的方向(方向)足以被认为是滑动时识别滑动。滑动可以快也可以慢。慢刷需要较高的方向精度,但距离很小;快速滑动需要较低的方向精度,但距离较大。可以通过调用UIGestureRecognizer方法locationInView:和locationOfTouch:inView:来确定滑动开始的位置。前一种方法在手势中涉及多个触摸时给出质心,后者给出了一个特定触摸的位置。

常用属性
@property(nonatomic) NSUInteger numberOfTouchesRequired API_UNAVAILABLE(tvOS);

属性描述要识别滑动手势,必须出现的手指数。默认值为1。

@property(nonatomic) UISwipeGestureRecognizerDirection direction;

属性描述滑动所需的方向,默认是UISwipeGestureRecognizerDirectionRight。如果多个方向将导致相同的行为,则可以指定多个方向(例如,UITableView滑动删除)。

  • UISwipeGestureRecognizerDirection提供的选项:
typedef NS_OPTIONS(NSUInteger, UISwipeGestureRecognizerDirection) {
    //触摸向右滑动。这个方向是默认的
    UISwipeGestureRecognizerDirectionRight = 1 << 0,
    //触摸向左滑动
    UISwipeGestureRecognizerDirectionLeft  = 1 << 1,
    //触摸向上滑动
    UISwipeGestureRecognizerDirectionUp    = 1 << 2,
    //触摸向下滑动
    UISwipeGestureRecognizerDirectionDown  = 1 << 3
};

\color{red}{练习代码片段(视图轻扫):}

@interface GestureRecognizerViewController ()<UIGestureRecognizerDelegate>

//测试缩放与旋转的图片视图
@property(nonatomic, strong) UIImageView *imageView;
//图片数组
@property(nonatomic, copy) NSMutableArray *imageNameArray;
//当前图片的索引
@property(nonatomic, assign) NSInteger currentIndex;

@end

@implementation GestureRecognizerViewController{
    
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.navigationItem.title = @"手势识别";
    [self initScale];
    [self createUI];
}

- (void)initScale{
    //初始化图片名称数组
    self.imageNameArray = [[NSMutableArray alloc]initWithObjects:@"tian_kong_long",@"yi_shen_long",@"ju_shen_bing", nil];
    
}

- (void)createUI{
    
    //手势识别器测试图片视图
    self.imageView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"tian_kong_long"]];
    //设置框架矩形
    self.imageView.frame = CGRectMake(0, CGRectGetMidY(self.view.frame) - 100, CGRectGetWidth(self.view.frame), 200);
    //设置内容的显示方式
    self.imageView.contentMode = UIViewContentModeScaleAspectFit;
    //设置与用户进行交互,UIImageView默认为NO
    self.imageView.userInteractionEnabled = YES;
    //添加UIImageView到视图
    [self.view addSubview:self.imageView];

    //初始化轻扫手势识别器(右侧轻扫,每个方向要单独添加)
    UISwipeGestureRecognizer *rightSwipe = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipeEvent:)];
    //轻扫的方向
    rightSwipe.direction = UISwipeGestureRecognizerDirectionRight;
    //轻扫手势需要的手指数
    rightSwipe.numberOfTouchesRequired = 2;
    //设置代理
    rightSwipe.delegate = self;
    //添加轻扫手势
    [self.imageView addGestureRecognizer:rightSwipe];
    
    //初始化轻扫手势识别器(左侧轻扫,每个方向要单独添加)
    UISwipeGestureRecognizer *leftSwipe = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipeEvent:)];
    //轻扫的方向
    leftSwipe.direction = UISwipeGestureRecognizerDirectionLeft;
    //轻扫手势需要的手指数
    leftSwipe.numberOfTouchesRequired = 2;
    //设置代理
    leftSwipe.delegate = self;
    //添加轻扫手势
    [self.imageView addGestureRecognizer:leftSwipe];

}

///轻扫事件
- (void)swipeEvent:(UISwipeGestureRecognizer *)swipeGestureRecognizer{
    switch (swipeGestureRecognizer.direction) {
        case UISwipeGestureRecognizerDirectionRight:
            //右侧轻扫
            [self transitionAnimation:0];
            break;
        case UISwipeGestureRecognizerDirectionLeft:
            //左侧轻扫
            [self transitionAnimation:1];
            break;
        default:
            break;
    }
}

///旋转动画
- (void)transitionAnimation:(BOOL)isNext{
    
    [UIView animateWithDuration:1.0f animations:^{
        //获取图片视图图层
        CALayer *layer = self.imageView.layer;
        //3D旋转效果
        CATransform3D rotationAndPerspectiveTransform = CATransform3DIdentity;
        //矩阵,值大概在1.0 / -500到1.0 / -1000间效果不错
        rotationAndPerspectiveTransform.m34 = 1.0 / -500;
        //判断轻扫方向
        if(isNext){
            //左侧轻扫
            //围绕向量(x,y,z)按角度弧度旋转t并返回结果。如果向量的长度为零,则行为未定义
            rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform,-M_E, 0.0f, 1.0f, 0.0f);
        }else{
            //右侧轻扫
            //围绕向量(x,y,z)按角度弧度旋转t并返回结果。如果向量的长度为零,则行为未定义
            rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform,M_E, 0.0f, 1.0f, 0.0f);
        }
        //设置层内容的转换
        layer.transform = rotationAndPerspectiveTransform;
        
    }completion:^(BOOL finished) {
        // 调用得到图片的方法
        self.imageView.image = [self getImage:isNext];
        // 重置图片视图层的transform,否则transform改变后会影响左右轻扫
        self.imageView.layer.transform = CATransform3DIdentity;
    }];
}

///获取图片
- (UIImage *)getImage:(BOOL)isNext{
    
    if (isNext)
    {
        // 左侧轻扫,下一张
        self.currentIndex = (self.currentIndex + 1) % self.imageNameArray.count;
    }else
    {
        // 右侧轻塞,上一张
        self.currentIndex = (self.currentIndex - 1 + self.imageNameArray.count) % self.imageNameArray.count;
    }
    //获取数组中的图片名称
    NSString *imageName = self.imageNameArray[self.currentIndex];
    //返回图片
    return [UIImage imageNamed:imageName];
}

@end

效果如图:(模拟器测试轻扫,先按下option键调出2指,之后调整位置后按shitf固定,鼠标滑动)

Jietu20200902-093656.gif

UIPanGestureRecognizer -- 平移手势识别器

UIGestureRecognizer的一个具体子类,用于查找平移(拖动)手势。用户在平移视图时必须按下一个或多个手指。实现该手势识别器动作方法的客户端可以要求手势当前的转换和速度。

平移手势是连续的。用户在平移视图时必须按下一个或多个手指。当用户移动允许的最小手指数量(minimumNumberOfTouches)足够的距离来识别为平移时,手势开始(UIGestureRecognizerStateBegan);当用户同时按下最小数量的手指并移动手指时,它会发生变化(UIGestureRecognizerStateChanged);当用户抬起所有手指时,它就结束了(UIGestureRecognizerStateEnded)。

此类的客户端可以在其操作方法中,查询UIPanGestureRecognizer对象,以获取手势的当前转换(translationview:)和转换速度(velocityview:)。它们可以指定坐标系应用于平移和速度值的视图。客户端还可以将转换重置为所需的值。

常用属性
@property (nonatomic) NSUInteger minimumNumberOfTouches API_UNAVAILABLE(tvOS);

属性描述可接触视图以识别此手势的最少手指数。默认值为1。

@property (nonatomic) NSUInteger maximumNumberOfTouches API_UNAVAILABLE(tvos);   

属性描述可接触视图以识别此手势的最大手指数。默认值为NSUIntegerMax。

常用函数
- (CGPoint)translationInView:(nullable UIView *)view;

函数描述平移手势在指定视图的坐标系统中的转换。x和y值报告随时间的总转换。它们不是上次报告转换时的增量值。 不要在每次调用处理程序时连接该值。

参数 :

view : 应在其坐标系中计算平移手势的视图。如果要调整视图的位置以使其保持在用户的手指下,请在该视图的superview坐标系中请求平移。

返回值 : 在其指定父视图的坐标系统中标识视图新位置的点。

- (void)setTranslation:(CGPoint)translation inView:(nullable UIView *)view;

函数描述在指定视图的坐标系中设置平移值。更改平移值将重置平移的速度。

参数 :

translation : 标识新转换值的点。

view : 在其坐标系中进行平移的视图。

- (CGPoint)velocityInView:(nullable UIView *)view; 

函数描述 :在指定视图的坐标系中平移手势的速度。

参数 :

view : 在其坐标系中计算平移手势速度的视图。

返回值 : 平移手势的速度,以每秒点数表示。速度分为水平分量和垂直分量。

\color{red}{练习的代码片段(控制器侧滑返回):}

@interface GestureRecognizerViewController ()<UIGestureRecognizerDelegate>

@end

@implementation GestureRecognizerViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.navigationItem.title = @"手势识别";
    [self createUI];
    //UINavigationController+FDFullscreenPopGesture设置导航栏显示
    self.fd_prefersNavigationBarHidden = NO;
}

- (void)createUI{
    //测试按钮
    //初始化按钮
    UIButton *testCodeButton = [UIButton buttonWithType:UIButtonTypeCustom];
    //设置按钮框架矩形
    testCodeButton.frame = CGRectMake(CGRectGetMaxX(self.view.frame) / 2 - 75 / 2, CGRectGetMinY(self.view.frame) + HEAD_BAR_HEIGHT, 75, 30);
    //按钮背景颜色
    testCodeButton.backgroundColor = [UIColor blueColor];
    //按钮标题字体大小
    testCodeButton.titleLabel.font = [UIFont systemFontOfSize:15];
    //按钮标题
    [testCodeButton setTitle:@"测试代码" forState:UIControlStateNormal];
    //按钮标题颜色
    [testCodeButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    //按钮添加点击事件
    [testCodeButton addTarget:self action:@selector(testCode) forControlEvents:UIControlEventTouchUpInside];
    //添加按钮到视图
    [self.view addSubview:testCodeButton];
}

///测试按钮点击
- (void)testCode{
    //实现提示
    [self.view makeToast:@"可以开始侧滑返回" duration:1 position:CSToastPositionTop];
    //初始化平移手势识别器对象
    UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] init];
    //可接触视图以识别此手势的最大手指数
    panGestureRecognizer.maximumNumberOfTouches = 1;
    //当前控制器负责从导航堆栈弹出顶视图控制器的手势识别器所附加到视图添加平移手势
    [self.navigationController.interactivePopGestureRecognizer.view addGestureRecognizer:panGestureRecognizer];
    //利用KVC获取手势数组
    NSArray *internalTargets = [self.navigationController.interactivePopGestureRecognizer valueForKey:@"targets"];
    //获取这个手势对象
    id internalTarget = [internalTargets.firstObject valueForKey:@"target"];
    //获取内部handleNavigationTransition:函数编号
    SEL internalAction = NSSelectorFromString(@"handleNavigationTransition:");
    //设置代理
    panGestureRecognizer.delegate = self;
    //添加手势
    [panGestureRecognizer addTarget:internalTarget action:internalAction];
}

//因为使用了FDFullscreenPopGesture,所以这里禁用了pop手势,方便添加自己的测试手势
//UINavigationController+FDFullscreenPopGesture设置交互式pop手势是否被禁用
- (BOOL)fd_interactivePopDisabled {
    
    return YES;
}

///询问代理手势识别器是否应开始解释触摸。
//防止假死
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {

    // 当前控制器为根控制器不允许手势执行
    if (self.navigationController.viewControllers.count <= 1) {
        return NO;
    }
    // 如果这个push、pop动画正在执行(私有属性)不允许手势执行
    if ([[self.navigationController valueForKey:@"_isTransitioning"] boolValue]) {
        return NO;
    }
    return YES;
}

@end

效果如图:

Jietu20200810-084404.gif

UIScreenEdgePanGestureRecognizer -- 边缘侧滑手势识别器

一个手势识别器,用于寻找开始于屏幕边缘附近的平移(拖动)手势。在某些情况下,系统使用屏幕边缘手势来启动视图控制器转换。可以使用这个类为自己的操作复制相同的手势行为。

创建屏幕边缘平移手势识别器后,在将手势识别器附加到视图之前,为edges属性分配一个适当的值。使用此属性可指定手势可以从哪边开始。这个手势识别器会忽略第一次触摸以外的任何触摸。

常用属性
@property (readwrite, nonatomic, assign) UIRectEdge edges;

属性描述手势可接受的起始边。指定的边缘总是相对于应用程序的当前界面方向。这一行为确保了手势总是发生在用户界面的相同位置,而不管设备的当前方向。

  • UIRectEdge提供的值如下(个人测试, 边缘侧滑手势识别器只有左边缘与右边缘是有效的):
typedef NS_OPTIONS(NSUInteger, UIRectEdge) {
    //为定义边缘
    UIRectEdgeNone   = 0,
    //矩形的上边缘
    UIRectEdgeTop    = 1 << 0,
    //矩形的左边缘。
    UIRectEdgeLeft   = 1 << 1,
    //矩形的下边缘
    UIRectEdgeBottom = 1 << 2,
    //矩形的右边缘
    UIRectEdgeRight  = 1 << 3,
    //矩形的所有边
    UIRectEdgeAll    = UIRectEdgeTop | UIRectEdgeLeft | UIRectEdgeBottom | UIRectEdgeRight
} API_AVAILABLE(ios(7.0));

\color{red}{练习的代码片段(边缘侧滑):}

@interface TestCodeController ()<UIGestureRecognizerDelegate>


@end

@implementation TestCodeController



- (void)viewDidLoad {

    [super viewDidLoad];
    
    ///边缘侧滑手势识别器测试图片视图
    UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(CGRectGetMaxX(self.view.frame) - 10, 0, 10, 200)];
    imageView.userInteractionEnabled = YES;
    imageView.image = [UIImage imageNamed:@"宇宙"];
    [self.view addSubview:imageView];
    
    ///初始化边缘侧滑手势识别器
    UIScreenEdgePanGestureRecognizer *screenEdgePanGestureRecognizer = [[UIScreenEdgePanGestureRecognizer alloc]initWithTarget:self action:@selector(screenEdgeEvent:)];
    //设置手势可接受的起始边
    screenEdgePanGestureRecognizer.edges = UIRectEdgeRight;
    //添加边缘侧滑手势识别器
    [imageView addGestureRecognizer:screenEdgePanGestureRecognizer];
    
}

///边缘侧滑事件
- (void)screenEdgeEvent:(UIPanGestureRecognizer *)gestureRecognizer{
    UIImageView *imageView = (UIImageView *)gestureRecognizer.view;
     if(gestureRecognizer.state == UIGestureRecognizerStateBegan || gestureRecognizer.state == UIGestureRecognizerStateChanged ){
         [UIView animateWithDuration:0.5 animations:^{
             imageView.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 200);
         }];
     }
}

@end

效果如图 :

Jietu20200906-133918.gif

UILongPressGestureRecognizer -- 长按手势识别器

常用属性
@property (nonatomic) NSUInteger numberOfTapsRequired;

属性描述识别手势所需的视图上的点击次数。默认的点击次数是0。(例如设置为1,则需要先点击一次鼠标,在点击长按,如果直接识别点击长按,设置为0)。

@property (nonatomic) NSUInteger numberOfTouchesRequired API_UNAVAILABLE(tvOS);

属性描述在视图上必须按下的手指数才能识别手势。默认的手指数是1。

@property (nonatomic) NSTimeInterval minimumPressDuration;

属性描述手指必须按在视图上的最小周期才能识别手势。时间间隔的单位是秒。默认持续时间为0.5秒。

@property (nonatomic) CGFloat allowableMovement;   

属性描述手势失败前手指在视图上的最大移动。允许的距离,以点为单位。默认距离为10点。

\color{red}{练习的代码片段(视图长按):}

@interface TestCodeController ()<UIGestureRecognizerDelegate>


@end

@implementation TestCodeController



- (void)viewDidLoad {

    [super viewDidLoad];
    
    UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(longPressEvent)];
    longPress.minimumPressDuration = 1.0;
    
    UIView *testView = [[UIView alloc]initWithFrame:CGRectMake(CGRectGetMaxX(self.view.frame) / 2 - 100 / 2,CGRectGetMidY(self.view.frame) - 50, 100, 100)];
    testView.backgroundColor = [UIColor redColor];
    testView.userInteractionEnabled = YES;
    [self.view addSubview:testView];
    
    [testView addGestureRecognizer:longPress];
}

- (void)longPressEvent{
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Alert" message:@"长按响应" preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *yesAction = [UIAlertAction actionWithTitle:@"YES" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action){
        NSLog(@"Top YES Button");
    }];
    [alertController addAction:yesAction];
    [self presentViewController:alertController animated:true completion:nil];
}

@end

效果如图:

Jietu20200906-153156.gif
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349