手势之间的传递
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);
}