同一个view上添加两个相同的手势

本文探讨了解决iOS应用中一个view同时注册两个相同手势的问题,通过在view上添加一层透明layer并注册手势,实现类似功能。详细介绍了手势响应方法的实现,包括代理方法的必要性及如何避免冲突,以及使用UIResponder类的方法自动捕获手势的策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    因为使用第三方库,第三方库里已经实现添加了点击事件,然后没有相应的对外接口,没办法添加手势响应事件,需要另外再注册一个手势。但是很遗憾,ios不支持一个view里同时注册两个相同的手势,只会响应后添加的那个手势。除非强制重写没有对外开放的那个接口,但这肯定不是解决问题的办法。

    想到一个办法可以尝试解决这个问题。再添加一层透明layer,然后在layer上注册手势,并添加响应时间来实现类似于一个view上添加两个相同的手势的功能。我没有添加一层蒙板,因为之前的 mapView 上添加手势,而mapView又是添加在self.view上的。我直接在self.view上注册手势。


@property (nonatomic, retain) UITapGestureRecognizer  *tapMapGestureRecognizer;


self.tapMapGestureRecognizer =        

                   [[UITapGestureRecognize alloc initWithTarget:sel action:@selector(tapMap:)];

self.tapMapGestureRecognizer.delegate = self;

[self.view addGestureRecognizer:self.tapMapGestureRecognizer];


手势响应的方法为:


-(void)tapMap:(UITapGestureRecognizer *)gestureRecognizer

{

   

}


然后实现手势的代理方法为:


- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer

{

    return YES;

}


- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch

{

    //如果点击的是annotationView,则不响应地图选点事件(返回选点界面有点慢,查看什么问题)

    if (gestureRecognizer == self.tapMapGestureRecognizer && [touch.view isKindOfClass:[CMPoiLabelBallonView class]])

    {

        return NO;

    }

    return YES;

}


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

{

    return YES;

}


    特别要注意的地方就是最后一个代理方法的实现,在模拟器上调试,总是不响应事件,找了很久的原因才发现原来是这个代理方法没有实现。因为这些代理方法都是optional可选的,不是强制性的,需要特别注意下。

    然后就是上面的第二个方法里的处理,在识别手势时,点击地图,会弹出一个气泡,然后下次再点击气泡,当然不能响应self.view的点击事件了,因此需要这么处理下。


    另外还有一种处理方法,没有细究,姑且也放在这里。UIViewUIResponder的子类,在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;


    这几个实例方法会自动捕获view上的任何手势,当然既然能捕获所有手势,很自然就需要费一番力气来分别处理这些手势了。可以参看下面的例子来操作。


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

    if (touches.count > 0) {

        UITouch *touch = [touches anyObject];

        CGPoint point = [touch locationInView:self];

    }

}



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

    if (touches.count > 0) {

        UITouch *touch = [touches anyObject];

        CGPoint point = [touch locationInView:self];

        CLLocationCoordinate2D coordinate = [self convertPoint:point toCoordinateFromView:self];

//                [self.tapDelegate mapView:self.mapView onMapClick:coordinate];

    }

}


<think>我们有两个UIScrollView需要同步滚动,即当其中一个滚动时,另一个也跟随滚动相同的偏移量。基本思路是:设置两个UIScrollView的代理(delegate),在滚动事件中(如scrollViewDidScroll:)根据当前滚动的scrollView去设置另一个scrollView的contentOffset。但是需要注意避免循环调用:因为当我们设置另一个scrollView的contentOffset时,也会触发它的scrollViewDidScroll:,所以需要设置一个标志位来区分是用户滚动还是我们程序触发的滚动。步骤:1.创建两个UIScrollView,并设置它们的delegate(例如当前视图控制器)。2.在代理方法中,判断当前滚动的scrollView,然后设置另一个scrollView的contentOffset。3.为了避免循环调用,我们用一个变量(例如isSyncing)来标记当前是否正在同步滚动。具体实现:定义两个UIScrollView属性,以及一个同步标志:@property(nonatomic, strong) UIScrollView*scrollView1; @property (nonatomic,strong)UIScrollView *scrollView2;@property(nonatomic, assign) BOOL isSyncing;在viewDidLoad中设置它们的delegate为self。实现UIScrollViewDelegate的scrollViewDidScroll:方法: -(void)scrollViewDidScroll:(UIScrollView *)scrollView{//如果正在同步,则直接返回,避免循环if (self.isSyncing) {return;}//设置同步标志,防止循环调用self.isSyncing= YES;if (scrollView== self.scrollView1) {//将scrollView1的偏移量同步给scrollView2self.scrollView2.contentOffset =scrollView.contentOffset;} elseif (scrollView== self.scrollView2) {//将scrollView2的偏移量同步给scrollView1self.scrollView1.contentOffset= scrollView.contentOffset;}//同步完成,重置标志self.isSyncing= NO; }但是,上面的方法有一个问题:在快速滚动时,由于设置contentOffset会触发另一个scrollView的scrollViewDidScroll:,而我们的标志位只能防止立即循环,但在快速滚动时可能因为异步等原因导致不同步。因此,我们可以使用更精确的方法:在设置另一个scrollView的contentOffset之前,先移除它的代理,设置完后再重新设置代理,这样就不会触发代理方法。另一种方法:在设置另一个scrollView的contentOffset时,暂时将其代理置为nil,设置完后再恢复代理。改进后的代码: -(void)scrollViewDidScroll:(UIScrollView *)scrollView{if (self.isSyncing){return;}self.isSyncing =YES;if(scrollView ==self.scrollView1){//先移除scrollView2的代理,避免触发其scrollViewDidScroll:self.scrollView2.delegate= nil;self.scrollView2.contentOffset =scrollView.contentOffset;self.scrollView2.delegate =self;}else if(scrollView ==self.scrollView2){//先移除scrollView1的代理self.scrollView1.delegate =nil;self.scrollView1.contentOffset= scrollView.contentOffset;self.scrollView1.delegate= self;}self.isSyncing= NO; }但是,这种方法也有缺点:在设置代理为nil的短暂时间内,如果用户正好在滚动,可能会丢失一些代理事件。不过由于我们是在同一个runloop循环中立即恢复,所以通常问题不大。另外,我们还需要考虑两个UIScrollView的contentSize可能不同,但这里假设它们有相同的滚动区域(即相同的contentSize),或者我们只希望它们在共同的范围内同步(比如高度相同,宽度不同,那么只同步垂直方向)。如果只同步垂直方向,我们可以只设置y值。例如,如果我们只同步垂直方向:if (scrollView== self.scrollView1) {CGPoint offset= self.scrollView2.contentOffset;offset.y= scrollView.contentOffset.y;self.scrollView2.delegate =nil;self.scrollView2.contentOffset= offset;self.scrollView2.delegate =self;}同理,水平方向类似。另外,还需要注意两个scrollView的缩放、减速等处理,但这里只要求同步滚动,所以只处理contentOffset即可。如果两个scrollView的contentSize不同,我们可能需要根据比例来同步,但问题中未提及,所以按相同偏移量处理。下面给出完整的示例代码(假设在UIViewController中):</think>在 iOS 中实现两个 UIScrollView 的同步滚动,可以通过手势识别和代理方法结合实现。以下是 Objective-C 的实现方案: ### 实现步骤 1. **设置代理和手势识别** 为两个 UIScrollView 设置相同的代理对象,并添加平移手势识别器: ```objectivec // ViewController.m @interface ViewController () <UIScrollViewDelegate> @property (nonatomic, strong) UIScrollView *scrollView1; @property (nonatomic, strong) UIScrollView *scrollView2; @end - (void)viewDidLoad { [super viewDidLoad]; // 初始化两个 ScrollView self.scrollView1 = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 200, 400)]; self.scrollView2 = [[UIScrollView alloc] initWithFrame:CGRectMake(200, 0, 200, 400)]; // 设置代理和手势 self.scrollView1.delegate = self; self.scrollView2.delegate = self; UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)]; [self.view addGestureRecognizer:panGesture]; } ``` 2. **手势同步逻辑** 通过手势识别器捕获滚动事件并同步两个 ScrollView: ```objectivec - (void)handlePanGesture:(UIPanGestureRecognizer *)gesture { CGPoint translation = [gesture translationInView:self.view]; // 同步滚动偏移量 CGPoint offset1 = self.scrollView1.contentOffset; CGPoint offset2 = self.scrollView2.contentOffset; offset1.y -= translation.y; // 根据手势方向调整 offset2.y -= translation.y; // 设置新偏移量(不触发代理回调) [self.scrollView1 setContentOffset:offset1 animated:NO]; [self.scrollView2 setContentOffset:offset2 animated:NO]; [gesture setTranslation:CGPointZero inView:self.view]; } ``` 3. **代理方法同步** 在滚动回调中实时同步位置: ```objectivec - (void)scrollViewDidScroll:(UIScrollView *)scrollView { if (scrollView == self.scrollView1) { [self.scrollView2 setContentOffset:scrollView.contentOffset animated:NO]; } else { [self.scrollView1 setContentOffset:scrollView.contentOffset animated:NO]; } } ``` 4. **关键优化** 避免循环调用和冲突: ```objectivec // 在同步操作时临时禁用代理 - (void)safeSetOffset:(CGPoint)offset forScrollView:(UIScrollView *)scrollView { scrollView.delegate = nil; [scrollView setContentOffset:offset animated:NO]; scrollView.delegate = self; } ``` ### 注意事项 1. **内容尺寸匹配** 确保两个 ScrollView 的 `contentSize` 高度一致: ```objectivec self.scrollView1.contentSize = CGSizeMake(200, 1000); self.scrollView2.contentSize = CGSizeMake(200, 1000); // 高度必须相同 ``` 2. **手势冲突处理** 在 ScrollView 代理中协调手势优先级: ```objectivec - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)other { return YES; // 允许同时识别 } ``` 3. **边界情况处理** 在滚动结束时检查边界: ```objectivec - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { if (!decelerate) { [self adjustScrollViewBounds:scrollView]; } } ``` ### 完整示例 [下载示例代码](https://github.com/ScrollViewSyncDemo)(模拟器运行后拖动任意区域即可看到同步效果)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值