You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过Hit Test确定圆形UIView中触摸对应的16个CGPoint点位?

解决方案:试剂瓶轮播组件的点击判断

嘿,针对你把LMGaugeView改造成16个试剂瓶轮播组件的需求,其实不需要依赖额外的Hit Test方法——你当前的hitTest:实现是为了检测子视图的点击,但现在你没有对应每个试剂瓶的子视图,所以得换个思路来处理点击判断:


核心思路

既然没有子视图对应每个试剂瓶,我们可以预先计算出16个均匀分布在圆形边缘的点位,然后在点击事件里,判断用户的点击点距离哪个预计算点位最近,且在有效点击范围内,以此确定点击的是哪个试剂瓶。


步骤1:预计算16个均匀分布的点位

首先在视图的layoutSubviews(或者初始化方法里)计算并保存这16个点的坐标:

// 定义一个属性保存点位数组
@property (nonatomic, strong) NSArray<NSValue *> *reagentPoints;

- (void)layoutSubviews {
    [super layoutSubviews];
    
    NSMutableArray *points = [NSMutableArray arrayWithCapacity:16];
    CGPoint center = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2);
    // 半径:取视图最小边长的一半,减去试剂瓶的偏移量(根据你的UI调整)
    CGFloat radius = MIN(self.bounds.size.width, self.bounds.size.height)/2 - 10;
    // 每个点位的角度间隔:2π/16
    CGFloat angleStep = 2 * M_PI / 16;
    
    for (NSInteger i = 0; i < 16; i++) {
        // 减去M_PI/2是让第一个点从顶部开始,可根据你的UI需求调整角度起始值
        CGFloat angle = angleStep * i - M_PI/2;
        CGFloat x = center.x + radius * cos(angle);
        CGFloat y = center.y + radius * sin(angle);
        [points addObject:[NSValue valueWithCGPoint:CGPointMake(x, y)]];
    }
    
    self.reagentPoints = points;
}

步骤2:处理点击事件,匹配对应试剂瓶

你可以用系统的touchesBegan:withEvent:方法,或者添加UITapGestureRecognizer来处理点击,这里以touchesBegan为例:

// 可以定义一个block回调给外部处理点击事件
@property (nonatomic, copy) void(^didTapReagentBottleBlock)(NSInteger index);

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    UITouch *touch = touches.anyObject;
    CGPoint touchPoint = [touch locationInView:self];
    CGPoint center = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2);
    
    // 先过滤掉圆形范围外的无效点击(对应你之前hitTest里的14pt扩展范围)
    CGFloat maxValidRadius = MIN(self.bounds.size.width, self.bounds.size.height)/2 + 14;
    CGFloat distanceToCenter = hypot(touchPoint.x - center.x, touchPoint.y - center.y);
    if (distanceToCenter > maxValidRadius) {
        return;
    }
    
    // 遍历预计算的点位,找到距离最近且在有效点击阈值内的那个
    NSInteger closestIndex = -1;
    CGFloat minDistance = CGFLOAT_MAX;
    // 点击阈值:根据试剂瓶的大小调整,比如20pt
    CGFloat clickThreshold = 20;
    
    for (NSInteger i = 0; i < self.reagentPoints.count; i++) {
        CGPoint point = [self.reagentPoints[i] CGPointValue];
        CGFloat distance = hypot(touchPoint.x - point.x, touchPoint.y - point.y);
        
        if (distance < minDistance && distance < clickThreshold) {
            minDistance = distance;
            closestIndex = i;
        }
    }
    
    // 如果找到有效点击的试剂瓶,触发回调
    if (closestIndex != -1 && self.didTapReagentBottleBlock) {
        self.didTapReagentBottleBlock(closestIndex);
    }
}

步骤3:优化你的hitTest:方法

原来的hitTest:是用矩形来判断点击范围,我们可以改成圆形范围判断,避免点击视图矩形内但圆形外的区域触发事件:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) {
        return nil;
    }
    
    CGPoint center = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2);
    CGFloat distanceToCenter = hypot(point.x - center.x, point.y - center.y);
    // 允许14pt的扩展点击范围,和你之前的逻辑一致
    CGFloat validRadius = MIN(self.bounds.size.width, self.bounds.size.height)/2 + 14;
    
    if (distanceToCenter <= validRadius) {
        return self;
    }
    
    return nil;
}

为什么不需要额外的Hit Test方法?

Hit Test系列方法的核心是查找响应点击的子视图,但你的场景里没有为每个试剂瓶单独创建子视图,所以用Hit Test没法直接定位到具体的试剂瓶。而通过预计算点位+点击距离匹配的方式,更适合这种无对应子视图的点击判断场景。

内容的提问来源于stack exchange,提问作者Anthony_b

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 06:16:04