事件传递机制案例

手势之间的传递

    EOCViewLightGray *viewGray = [[EOCViewLightGray alloc] initWithFrame:CGRectMake(50.f, 100.f, 300.f, 300.f)];
    viewGray.backgroundColor = [UIColor lightGrayColor];
    EOCViewRed *viewRed = [[EOCViewRed alloc] initWithFrame:CGRectMake(30.f, 30.f, 200.f, 200.f)];
    viewRed.backgroundColor = [UIColor redColor];
    //添加手势
    EOCPanGestureLightGray *panGestureGray = [[EOCPanGestureLightGray alloc] initWithTarget:self action:@selector(panActionLightGray)];
    EOCPanGestureRed *panGestureRed = [[EOCPanGestureRed alloc] initWithTarget:self action:@selector(panActionRed)];
    [viewGray addGestureRecognizer:panGestureGray];
    [viewRed addGestureRecognizer:panGestureRed];
    [self.view addSubview:viewGray];
    [viewGray addSubview:viewRed];

如上,我们创建两个view,并分别为它们添加手势,红色在灰色view上面,当我们点击红色view的时候:



我们可以看出响应顺序是:灰色手势->红色手势->红色touch->灰色touch

如果我们想要点击红色View的时候,灰色view的手势不响应

解法一:实现一个手势的代理方法

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    return NO;
}

一句话总结就是此方法返回YES时,手势事件会一直往下传递,不论当前层次是否对该事件进行响应。所以当返回NO的时候,也就是说手势不共存。此方法写在灰色view的手势或者红色view的手势中均可实现,打印如下:


如上图,当红色手势识别的时候,就不会再存在灰色手势了,之后只有红色手势响应。

解法二:添加一个手势的方法[panGestureGray requireGestureRecognizerToFail:panGestureRed];,也能实现同样的效果。这个方法就是在作为参数的Gesture recognizer失败以后接受者才发生,否则从不会发生。也即是说只有当红色手势识别失败的时候灰色手势才会识别,而红色手势不会识别失败,所以灰色手势就不会识别。

注意:这里不能用属性delaysContentTouches和canCancelContentTouches来进行判断,因为这两个属性是对其subView的touch事件进行操作。详情请见事件层次分析(二)——运用案例

两个手势方法的不同

// called once per attempt to recognize, so failure requirements can be determined lazily and may be set up between recognizers across view hierarchies
// return YES to set up a dynamic failure requirement between gestureRecognizer and otherGestureRecognizer
//
// note: returning YES is guaranteed to set up the failure requirement. returning NO does not guarantee that there will not be a failure requirement as the other gesture's counterpart delegate or subclass methods may return YES
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer NS_AVAILABLE_IOS(7_0);
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer NS_AVAILABLE_IOS(7_0);

官方注释即是说:当一个手势被识别,就会在view的层级的手势中创建一个延后执行的失败手势请求,当return YES的时候,可以保证至少有一个手势识别,return NO的时候,不能保证会有手势识别。第一个方法是指otherGestureRecognizer生效,第二个方法是指gestureRecognizer生效。

自定义一个简单的ScrollView

自定义一个ScrollView,要继承自UIView,因为要实现scrollview的滚动,所以必须也要添加手势,其次再来分析scrollview的原理。



ScrollView的滚动其实就是本身bounds的变化,虽然给我们看的感觉是里面的内容在滚动,其实只是它的origin的X值和Y值在变化,也即是上面的黄色框在向右移动。(因为只是改变的它的bounds,并没有改变它的frame,所以它在它父控件中的位置没变,还在屏幕上)。

#import <UIKit/UIKit.h>
#import "EOCPanGestureRecognizer.h"
@interface EOCCustomScrollView : UIView<UIGestureRecognizerDelegate>
@property(nonatomic, assign)CGSize contentSize;
@property(nonatomic, strong)EOCPanGestureRecognizer *panGesture;
@end
#import "EOCCustomScrollView.h"

@implementation EOCCustomScrollView

- (instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        
        //新建手势
        _panGesture = [[EOCPanGestureRecognizer alloc] initWithTarget:self action:@selector(panAction:)];
        [self addGestureRecognizer:_panGesture];
    }
    return self;
}

- (void)panAction:(UIPanGestureRecognizer *)gesture
{
    CGPoint translation = [gesture translationInView:self]; //获取手势的相对偏移量
    CGRect bounds = self.bounds;
    CGFloat newBoundsOriginX = bounds.origin.x - translation.x; //新的bounds的x值等于旧值减去偏移的值,向左滑偏移量为负,但是新的bounds的x值应该增加
    CGFloat minBoundsOriginX = 0.0;
    CGFloat maxBoundsOriginX = self.contentSize.width - bounds.size.width; //最大的bounds的x值,即最大的滑动偏移量,为contentSize的宽度减去bounds的宽度
    
    bounds.origin.x = fmax(minBoundsOriginX, fmin(newBoundsOriginX, maxBoundsOriginX)); //新的bounds的x值不应该大于最大的bounds的x值,所以取fmin(newBoundsOriginX, maxBoundsOriginX)这两个中最小的一个,也不应该小于最小的bounds的x值minBoundsOriginX,所以取两个中间最大的一个
    
    CGFloat newBoundsOriginY = bounds.origin.y - translation.y;
    CGFloat minBoundsOriginY = 0.0;
    CGFloat maxBoundsOriginY = self.contentSize.height - bounds.size.height;
    bounds.origin.y = fmax(minBoundsOriginY, fmin(newBoundsOriginY, maxBoundsOriginY));
    self.bounds = bounds;
    [gesture setTranslation:CGPointZero inView:self]; //每次要将手势的便宜量置零,不然会每次在前面的基础上叠加
    
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];
    CGPoint touchPoint = [touch locationInView:self];
    CGPoint previousPoint = [touch previousLocationInView:self];
    
    if (touchPoint.x < previousPoint.x && self.bounds.origin.x == self.contentSize.width - self.bounds.size.width) { // 当滑动到最边缘,最大的bounds的x值时,还向右边滑就滑不动了,禁止这个手势,从而可以响应其他的手势
        self.panGesture.enabled = NO;
    } else {
        self.panGesture.enabled = YES;
    }
    NSLog(@"touchPoint.x %f, previousPoint.x %f", touchPoint.x, previousPoint.x);
}

上面是实现代码,里面注释有详细的解释

使用: 写两个控件,继承自刚刚自定义的ScrollView

#import "EOCCustomScrollView.h"
@interface EOCCustomScrollViewOne : EOCCustomScrollView<UIGestureRecognizerDelegate>
@end
#import "EOCCustomScrollViewOne.h"

@implementation EOCCustomScrollViewOne

- (instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        
        self.panGesture.delegate = self;
    }
    return self;
}

设置手势代理为本身

- (void)createCustomScrollView
{
    EOCCustomScrollViewOne *scrollView = [[EOCCustomScrollViewOne alloc] initWithFrame:CGRectMake(0.f, 100.f, self.view.frame.size.width, self.view.frame.size.width)];
    scrollView.contentSize = CGSizeMake(self.view.frame.size.width*4, self.view.frame.size.width);
    [self.view addSubview:scrollView];
    
    for (int i=0; i<4; i++) {
        
        if (!i) {
            NSArray *colorArr = @[[UIColor redColor], [UIColor blueColor], [UIColor yellowColor]];
            
            EOCCustomScrollViewTwo *subScrollView = [[EOCCustomScrollViewTwo alloc] initWithFrame:CGRectMake(0.f, 0.f, self.view.frame.size.width, self.view.frame.size.width)];
            subScrollView.backgroundColor = [UIColor lightGrayColor];
            subScrollView.contentSize = CGSizeMake(self.view.frame.size.width*2, self.view.frame.size.width);
            
            for (int i=0; i<3; i++) {
                UIView *view = [[UIView alloc] initWithFrame:CGRectMake(i*(self.view.frame.size.width/2+50.f), 0.f, self.view.frame.size.width/2, self.view.frame.size.width)];
                view.backgroundColor = colorArr[i];
                [subScrollView addSubview:view];
            }
            [scrollView addSubview:subScrollView];
            
        } else {
            
            UIView *view = [[UIView alloc] initWithFrame:CGRectMake(i*self.view.frame.size.width, 0.f, self.view.frame.size.width, self.view.frame.size.width)];
            view.backgroundColor = [UIColor colorWithRed:arc4random_uniform(256.f)/255.f green:arc4random_uniform(256.f)/255.f blue:arc4random_uniform(256.f)/255.f alpha:1.f];
            [scrollView addSubview:view];
        }
    }
}

创建两个scrollView,在第一个scrollView的第一个view上面再添加一个scrollView,实现效果如图:

但是如果不实现上面的这段代码的话,就会第一个scrollView的一个view中的scrollView滑动完了过后,就滑动不过去了


而用系统的scrollView就可以实现,所以系统的scrollView内部应该也实现了类似的代码

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];
    CGPoint touchPoint = [touch locationInView:self];
    CGPoint previousPoint = [touch previousLocationInView:self];
    
    if (touchPoint.x < previousPoint.x && self.bounds.origin.x == self.contentSize.width - self.bounds.size.width) { // 当滑动到最边缘,最大的bounds的x值时,还向右边滑就滑不动了,禁止这个手势,从而可以响应其他的手势
        self.panGesture.enabled = NO;
    } else {
        self.panGesture.enabled = YES;
    }
    NSLog(@"touchPoint.x %f, previousPoint.x %f", touchPoint.x, previousPoint.x);
}

参考文章
IOS开发UI篇—gesture详解(一)
iOS7下滑动返回与ScrollView共存二三事

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,246评论 4 61
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 173,436评论 25 708
  • 昔时黄鹤已离楼,诗在楼空水自流。 唐韵一去不复返,题诗千载空悠悠。 晴川极目年年绿,芳草横天日日洲。 夫子曾在江上...
    衣吹风阅读 551评论 1 1
  • XDF TMC 时间:每周六晚19:00-21:00 地点:济南市历下区历山路142号凯旋商务中心A座3层新东方3...
    XDFTMC阅读 356评论 0 3
  • 今年的生日是最特别的,首先我婉拒了六个一起要过生日的邀约,在自己的沉思里度过了这一夜晚。 第二,我在和马博士沟通时...
    布衣华筝阅读 292评论 1 1