如何检测拖动到那里的子视图中的触摸(或获取哪个子视图位于其父视图中的点击下方的最快方法)

Posted

技术标签:

【中文标题】如何检测拖动到那里的子视图中的触摸(或获取哪个子视图位于其父视图中的点击下方的最快方法)【英文标题】:How to detect a touch in a subview that was dragged there (or fastest way to get which subview is underneath a tap in its superview) 【发布时间】:2014-07-02 20:07:46 【问题描述】:

我有一个超级视图,它有一堆子视图。我希望用户能够在超级视图内拖动手指,当他们拖动子视图时,该子视图会发生变化。

我需要它非常高效,因为变化可能发生得非常快。

到目前为止,我已经尝试过,在超级视图中,我可以从 touchesBegan:withEvent: 和 touchesMoved:withEvent: 获取触摸事件:这两个组合为我提供了我需要的所有触摸。在每次调用这些方法时,我必须遍历所有子视图并像这样进行测试

for (UIView *view in self.subviews) 
    if (CGRectContainsPoint(view.frame, touchPoint) 
        return view;
    

然后根据我的意愿更改该视图。问题是,从我的基准测试来看,这个过程只有两个慢。

我的直接想法是让子视图自己检测触摸,然后在点击它们时发布通知或类似的东西。这样我就不必在超级视图中遍历它们每个该死的触摸事件。

我遇到的问题是,我还没有找到一种方法来让子视图本身检测到当它们刚刚被拖动时它们被触摸并且触摸实际上来自不同的 UIView。

我愿意接受任何加快第一个过程或有不同方法来完成此过程的建议。

【问题讨论】:

子视图的嵌套程度如何?意思是,你有一个视图,你有一堆子视图,但是你有多少子视图是子视图的子视图,子视图? 所有子视图都没有任何子视图,它们非常简单。我基本上只是在它们上使用背景颜色属性并进行更改。 您的子视图是如何布局的?它们是基于网格还是更随机? 它是一个网格,想想图像中的像素。 【参考方案1】:

由于您的子视图位于网格上,并且如果它们的大小相同,您应该能够直接计算突出显示的子视图。您只需将它们在创建时的引用存储在 2D 数组中,为了提高性能,我建议使用 c 数组。

int x = touchPoint.x/viewWidth;
int y = touchPoint.y/viewHeight;
return views[x][y];

根据您的布局,应考虑视图之间的一些边框边距或间距。

这样你不需要迭代 subviews 数组,也不需要执行所有这些 CGRectContainsPoint 计算。

【讨论】:

【参考方案2】:

应该很容易:

MyViewController.h

#import <UIKit/UIKit.h>

@interface MyViewController : UIViewController

@end

MyViewController.m

#import "MyViewController.h"

#define SIDE_LENGTH 60
#define NUM_SQUARES 15

@implementation MyViewController

    UIView *_selectedView;


- (void)viewDidLoad

    [super viewDidLoad];
    [self populateView];


- (void)populateView

    for (int i = 0; i < 15; ++i)
    
        CGRect frame = CGRectMake(drand48() * self.view.frame.size.width - SIDE_LENGTH,
                                   drand48() *self.view.frame.size.height - SIDE_LENGTH,
                                   SIDE_LENGTH,
                                   SIDE_LENGTH);
        UIView *view = [[UIView alloc] initWithFrame:frame];
        view.backgroundColor = [UIColor colorWithRed:drand48()
                                               green:drand48()
                                                blue:drand48()
                                               alpha:1.0f];
        view.layer.cornerRadius = SIDE_LENGTH / 2.0f;    // cuz circles r pretty
        [self.view addSubview:view];
    


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

    UITouch *touch = [touches anyObject];
    CGPoint location = [touch locationInView:self.view];
    for (UIView *view in self.view.subviews)
    
        if (CGRectContainsPoint(view.frame, location)) 
            _selectedView = view;
            break;
        
    


- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event 
    UITouch *touch = [touches anyObject];
    CGPoint location = [touch locationInView:self.view];
    _selectedView.center = location;


- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 
    UITouch *touch = [touches anyObject];
    CGPoint location = [touch locationInView:self.view];
    _selectedView.center = location;


@end

当然,对你来说,只需摆脱populateView 方法即可。我只是用它来演示它作为一个临时课程是如何工作的。

如果您正在寻找速度,那么将UIView 子类化并执行以下操作: 我的视图.h

#import <UIKit/UIKit.h>
@class MyView;

@interface MyView : UIView

@end

MyView.m

@implementation MyView

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

    UITouch *touch = [touches anyObject];
    CGPoint location = [touch locationInView:self.superview];
    self.center = location;


- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event 
    UITouch *touch = [touches anyObject];
    CGPoint location = [touch locationInView:self.superview];
    self.center = location;


- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 
    UITouch *touch = [touches anyObject];
    CGPoint location = [touch locationInView:self.superview];
    self.center = location;



@end

然后 MyViewController.m 变得更简单更快:

#import "MyViewController.h"
#import "MyView.h"
#define SIDE_LENGTH 60
#define NUM_SQUARES 15

@implementation MyViewController

    UIView *_selectedView;


- (void)viewDidLoad

    [super viewDidLoad];
    [self populateView];


- (void)populateView

    for (int i = 0; i < 15; ++i)
    
        CGRect frame = CGRectMake(drand48() * self.view.frame.size.width - SIDE_LENGTH,
                                   drand48() *self.view.frame.size.height - SIDE_LENGTH,
                                   SIDE_LENGTH,
                                   SIDE_LENGTH);
        MyView *view = [[MyView alloc] initWithFrame:frame];
        view.backgroundColor = [UIColor colorWithRed:drand48()
                                               green:drand48()
                                                blue:drand48()
                                               alpha:1.0f];
        view.layer.cornerRadius = SIDE_LENGTH / 2.0f;
        [self.view addSubview:view];
    


@end

【讨论】:

如果我错了,请纠正我,但这看起来正是我已经在做的,而且速度不够快,正如我原来的帖子中所解释的那样。 我想你可能误解了这个问题。我不想移动子视图,我只想能够在超级视图上拖动手指,并在任何给定时间知道我在哪个子视图下。 @JoelBell 该死的,伙计。抱歉,我认为没有比您的答案更快的方法了。

以上是关于如何检测拖动到那里的子视图中的触摸(或获取哪个子视图位于其父视图中的点击下方的最快方法)的主要内容,如果未能解决你的问题,请参考以下文章

目标c:从视图/图像/按钮外部开始触摸时检测触摸开始/向下

自定义按钮视图:检测触摸向下/向上,快速,并回退拖动到 UIScrollView

检测用户何时中断 UIScrollView 动画

拖放一个子节点

检测何时触摸另一个视图 - 在本机反应中使用 PanResponder 拖动

使用 MKMapView 和可触摸的子视图创建视图