2021-03-24 iOS手势问题

2021-03-24  本文已影响0人  我是小胡胡分胡

View布局-subViews 层级布局
1、touch 触摸手势 事件响应链-》hitTestView, 确定第一响应者
2、在不同View上(父子View,平级View),确定第一响应者
3、在同一个View上 ,自定义的UIControl/UIControl/UIControl系统子类(button,slider,segment等)target-action+ Gesture共存
4、在同一个View上,多个Gesture共存
5、UIView(touchesBegin)+UIControl+ Gesture 响应优先级
6、多个Gesture在不同View上:
父ScrollView+子View 手势
父ScrollView+子ScrollView 手势
父NavigationController View(侧滑手势)+子View手势

UIResponder->UIView->UIControl

1, UIResponder

- (void)touchesXXXXX:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;

2, UIGestureRecognizer

@interface UIView : UIResponder 
@interface UIView (UIViewGestureRecognizers)

@property(nullable, nonatomic,copy) NSArray<__kindof UIGestureRecognizer *> *gestureRecognizers NS_AVAILABLE_IOS(3_2);

优先级最高响应,并忽略UIControl方法响应;

直接通过UIApplication直接向其派发消息,并且不再向响应者链派发消息。
[UIWindow sendEvent:] ---> [UIGestureEnvironment _updateGesturesForEvent:window:]--->

@interface UIGestureRecognizer (UIGestureRecognizerProtected)

// mirror of the touch-delivery methods on UIResponder
// UIGestureRecognizers aren't in the responder chain, but observe touches hit-tested to their view and their view's subviews
// UIGestureRecognizers receive touches before the view to which the touch was hit-tested
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
- (void)touchesEstimatedPropertiesUpdated:(NSSet<UITouch *> *)touches API_AVAILABLE(ios(9.1));

state:possible/began/changed/ended/cancelled/failed
enable
cancelsTouchesInView
delaysTouchesBegan
delaysTouchesEnd
allowedTouchTypes
allowedPressTypes
requiresExclusiveTouchType

  1. UIControl
    @interface UIControl : UIView

[UIWindow sendEvent:] ---》 [UIWindow _sendTouchesForEvent:]

会重写父类(UIResponder)的touchesXXXXX方法,可能会直接取消touchesCancelled/

highlighted
tracking
touchInside
selected
enabled
state:normal/highlighted/disabled/selected/focused/application/reserved

touchesBegan-> beginTrackingWithTouch
touchesEnded-> endTrackingWithTouch->target/action

touchesBegan-> beginTrackingWithTouch
touchesMoved-> continueTrackingWithTouch
touchesMoved-> continueTrackingWithTouch
touchesMoved-> continueTrackingWithTouch->pointinside
touchesEnded->pointinside-> endTrackingWithTouch

1, 寻找第一响应者

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    //3种状态无法响应事件
     if (self.userInteractionEnabled == NO || self.hidden == YES ||  self.alpha <= 0.01) return nil; 
    //触摸点若不在当前视图上则无法响应事件
    if ([self pointInside:point withEvent:event] == NO) return nil; 
    //从后往前遍历子视图数组 
    int count = (int)self.subviews.count; 
    for (int i = count - 1; i >= 0; i--) 
    { 
        // 获取子视图
        UIView *childView = self.subviews[i]; 
        // 坐标系的转换,把触摸点在当前视图上坐标转换为在子视图上的坐标
        CGPoint childP = [self convertPoint:point toView:childView]; 
        //询问子视图层级中的最佳响应视图
        UIView *fitView = [childView hitTest:childP withEvent:event]; 
        if (fitView) 
        {
            //如果子视图中有更合适的就返回
            return fitView; 
        }
    } 
    //没有在子视图中找到更合适的响应视图,那么自身就是最合适的
    return self;
}

3, 事件是怎么传递的

事件 是怎么接 收的??

UIReponder 通过 TouchesXXXX方法接收 事件
<UIKit/UIGestureRecognizerSubclass.h>

UIGesture 也是 通过 TouchesXXXX方法接收 事件

UIControl的 Tracking 系列方法是在 touch 系列方法内部调用的。比如 beginTrackingWithTouch 是在 touchesBegan 方法内部调用的

Window怎么知道要把事件传递给哪些手势识别器?

Application怎么知道要把event传递给哪个Window,以及Window怎么知道要把event传递给哪个hit-tested view的问题,
手势识别器也是一样的,event绑定的touch对象上维护了一个手势识别器数组,里面的手势识别器毫无疑问是在hit-testing的过程中收集的

UIControl会阻止父视图上的手势识别器行为,也就是UIControl处理事件的优先级比UIGestureRecognizer高,
但前提是相比于父视图上的手势识别器。UIControl比其父视图上的手势识别器具有更高的事件响应优先级。

如果UIControl本身添加了手势呢?测试结果:UIGesture执行。uicontroler的target-action不执行。对自定义UIControl,UIControl的系统子类(button,slider等)一样。

准确地说只适用于系统提供的有默认action操作的UIControl,
例如UIbutton、UISwitch等的单击,而对于自定义的UIControl,经验证,响应优先级比手势识别器低(父子view上,UIGesture的优先关系)

Window先将事件传递给这些手势识别器,再传给hit-tested view。
一旦有手势识别器成功识别了手势,Application就会取消hit-tested view对事件的响应。

UIResponder响应触摸事件

UIResponder、UIGestureRecognizer、UIControl,笼统地讲,事件响应优先级依次递增。这句话不对啊, 如果是父子view,uigesture最高。如果同一个view上,uibutton的target-action最高。自定义uicontrol低与uigesture

如果多个UIGesture呢(同一个单View上的多个Gesture。父子View的各自的Gesutre)
最后添加的有效

问题1 , 父子view

首先确定 hit- test View

UIGestureRecognizer是默认往super view传递的
1、 这个view没有添加gesture, 看他的父view有没有添加手势, 如果添加了, 父视图的手势方法响应
2、 如果view添加了gesture, 父视图也添加了gesture, 子 view的优先响应, view的不满足条件激活条件的话, 父视图的响应

UIResponder的, 也会往父视图传
1、 view的touch执行了,
2、 super view的touch执行了
3、如果view重写toucheXXXX方法, 不调用super的toucheXXXX, 就在这个View终止了, 事件吸收了

问题2 , 父子view/平级view/同一个view,即添加了UIGesture,又add了target-action,哪个方法执行?

父子view,
父view上有UIGesture,
子view,如果是uibutton,uibutton的点击执行。
子view,如果是UIControl或者自定义UIControl,父view的UIGesture方法执行。如果想要修改,百度一下UIGesture的cancelsTouchesInView是干嘛的,这里懒得bb了,反正我也不懂

同一个view
view上即添加UIGesture又add了target-action,
无论view是UIButton/UIControl还是自定义UIControl子类,view上的UIGesture方法执行。

平级view
一个view在前面一个view在后面,但是他们是平级的。遵循响应链机制,hittest-view第一响应者view上的事件执行

https://www.jianshu.com/p/df86508e2811

测试代码:


- (void)viewDidLoad {
    [super viewDidLoad];

    //[self testSameView];
    [self testSubview];
    //[self testneighborView];
}

- (void)testSameView {
    double x = 20;
    double y = 20;
    double width = 100;
    double height = 50;

    {
        UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
        [btn addTarget:self action:@selector(btnAction) forControlEvents:UIControlEventTouchUpInside];
        btn.frame = CGRectMake(x, y, width, height);
        btn.backgroundColor = [UIColor redColor];
        [btn setTitle:@"btn" forState:UIControlStateNormal];
        [self.view addSubview:btn];
        y += 70;

        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapAction)];
        [btn addGestureRecognizer:tap];
    }

    {
        UIControl *btn = [UIControl new];
        [btn addTarget:self action:@selector(btnAction) forControlEvents:UIControlEventTouchUpInside];
        btn.frame = CGRectMake(x, y, width, height);
        btn.backgroundColor = [UIColor redColor];
        [self.view addSubview:btn];
        y += 70;

        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapAction)];
        [btn addGestureRecognizer:tap];
    }

    {
        CustomControl *btn = [CustomControl new];
        [btn addTarget:self action:@selector(btnAction) forControlEvents:UIControlEventTouchUpInside];
        btn.frame = CGRectMake(x, y, width, height);
        btn.backgroundColor = [UIColor redColor];
        [self.view addSubview:btn];
        y += 70;

        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapAction)];
        [btn addGestureRecognizer:tap];
    }
}

- (void)btnAction {
    NSLog(@"target-action");
}

- (void)tapAction {
    NSLog(@"tap called");
}

- (void)testSubview {
    double x = 20;
    double y = 20;
    double width = 100;
    double height = 50;

    {
        UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
        [btn addTarget:self action:@selector(btnAction) forControlEvents:UIControlEventTouchUpInside];
        btn.frame = CGRectMake(0, 0, width, height);
        btn.backgroundColor = [UIColor redColor];
        [btn setTitle:@"btn" forState:UIControlStateNormal];

        UIView *view = [UIView new];
        view.frame = CGRectMake(x, y, width + 50, height + 50);
        view.backgroundColor = [UIColor grayColor];
        view.userInteractionEnabled = YES;
        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapAction)];
        [view addGestureRecognizer:tap];

        [view addSubview:btn];
        [self.view addSubview:view];

        y += 120;
    }

    {
        UIControl *btn = [UIControl new];
        [btn addTarget:self action:@selector(btnAction) forControlEvents:UIControlEventTouchUpInside];
        btn.frame = CGRectMake(0, 0, width, height);
        btn.backgroundColor = [UIColor redColor];
        [self.view addSubview:btn];

        UIView *view = [UIView new];
        view.frame = CGRectMake(x, y, width + 50, height + 50);
        view.backgroundColor = [UIColor grayColor];
        view.userInteractionEnabled = YES;
        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapAction)];
        [view addGestureRecognizer:tap];

        [view addSubview:btn];
        [self.view addSubview:view];

        y += 120;
    }

    {
        CustomControl *btn = [CustomControl new];
        [btn addTarget:self action:@selector(btnAction) forControlEvents:UIControlEventTouchUpInside];
        btn.frame = CGRectMake(0, 0, width, height);
        btn.backgroundColor = [UIColor redColor];
        [self.view addSubview:btn];

        UIView *view = [UIView new];
        view.frame = CGRectMake(x, y, width + 50, height + 50);
        view.backgroundColor = [UIColor grayColor];
        view.userInteractionEnabled = YES;
        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapAction)];
        [view addGestureRecognizer:tap];

        [view addSubview:btn];
        [self.view addSubview:view];

        y += 120;
    }
}

- (void)testneighborView {
    double x = 20;
    double y = 20;
    double width = 100;
    double height = 50;

    {
        UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
        [btn addTarget:self action:@selector(btnAction) forControlEvents:UIControlEventTouchUpInside];
        btn.frame = CGRectMake(x, y, width, height);
        btn.backgroundColor = [UIColor redColor];
        [btn setTitle:@"btn" forState:UIControlStateNormal];

        UIView *view = [UIView new];
        view.frame = CGRectMake(x, y, width + 50, height + 50);
        view.backgroundColor = [UIColor grayColor];
        view.userInteractionEnabled = YES;
        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapAction)];
        [view addGestureRecognizer:tap];

        [self.view addSubview:view];
        [self.view addSubview:btn];
        y += 120;
    }

    {
        UIControl *btn = [UIControl new];
        [btn addTarget:self action:@selector(btnAction) forControlEvents:UIControlEventTouchUpInside];
        btn.frame = CGRectMake(x, y, width, height);
        btn.backgroundColor = [UIColor redColor];
        [self.view addSubview:btn];

        UIView *view = [UIView new];
        view.frame = CGRectMake(x, y, width + 50, height + 50);
        view.backgroundColor = [UIColor grayColor];
        view.userInteractionEnabled = YES;
        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapAction)];
        [view addGestureRecognizer:tap];

        [self.view addSubview:view];
        [self.view addSubview:btn];

        y += 120;
    }

    {
        CustomControl *btn = [CustomControl new];
        [btn addTarget:self action:@selector(btnAction) forControlEvents:UIControlEventTouchUpInside];
        btn.frame = CGRectMake(x, y, width, height);
        btn.backgroundColor = [UIColor redColor];
        [self.view addSubview:btn];

        UIView *view = [UIView new];
        view.frame = CGRectMake(x, y, width + 50, height + 50);
        view.backgroundColor = [UIColor grayColor];
        view.userInteractionEnabled = YES;
        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapAction)];
        [view addGestureRecognizer:tap];

        [self.view addSubview:view];
        [self.view addSubview:btn];
        y += 120;
    }
}
上一篇下一篇

猜你喜欢

热点阅读