iOS不规则控件的点击事件(转载)

我们有时候会碰到这样的需求,比如我们某个菜单是圆形的,或者某个菜单是环形的,由于一些情况普通用户很少能感知到,可能导致我们对非矩形控件的事件处理都按照矩形的区域来处理了,虽然这样的实现也没有问题,但是如果有一些极端的不规则控件出现的时候,可能矩形的处理区域就无法满足需求,我们就需要一种更加精确的处理方式,来决定我们的事件到底分发给哪个subview。

差不多2年前的时候,国内很多TabBar的设计中间都有一个凸起的部分,当时还是很流行,如下图所示

------------^---------------

这种UI实现起来也比较简单,我们只需要一张背景图片加上几个按钮的图片就可以了,至于中间的凸起部分,我们一般有两种处理方式:

1、设置TabBar的frame为图二所示的区域,然后中间的按钮的坐标设置成负数(iOS中,如果设置clipsToBounds=NO的情况,是可以显示出超过View区域的subview)。

2、设置TabBar的frame为图三所示的区域,然后剩下的正常设置

这两种方式都可以实现这种设计,但是都存在问题。图二中,由于中间的按钮有一部分不在TabBar的区域内,不再TabBar的区域不能响应事件,所以绿色阴影部分点击了之后无法达到预期的效果;图三中,TabBar透明区域遮住了后面的TableView的一部分,所以在图三绿色阴影区域内,无法响应TableView的滚动事件。相比之下,图三的处理方式对用户的操作影响会少一些,所以大部分都选择了图三所示的方式,当然这也看情况而视,主要是根据你凸起的大小来决定。

我们有没有一种方法来解决这种问题呢?即我们既希望用户点击凸起的地方能够响应TabBar事件,又希望在图三绿色阴影内能让TableView进行滚动。

答案显然是有的。

之前写过几篇文章,介绍iOS的事件分发(hitTest),我们在这里就需要用到这些知识了。我们需要在确定hitTestView的时候告诉UIKit,如果触摸区域在这些地方,就把事件分发给TabBar,在那些地方,你就不要分发给TabBar,你直接忽略TabBar,当作TabBar不存在就行了。

我们来实现一下如上所示的需求,我们首先选择图三所示的方法自定义TabBar。也就是定义TabBar的frame比较大,然后往里面添加组件,添加组件的代码比较零碎,就不在这里粘贴了。

补充一点知识,我们如何确定点击区域在如下图所示的区域内呢? ---------------^---------------- 这就是一点简单的数学知识。以TabBar左上角作为原点iOS视图坐标系中,假设凸起部分半径为40,超出的高度是20,圆心坐标为P(160, 40),则y>20的区域必须是TabBar的区域,而且距离中间凸起圆形区域的半径<40的区域也是TabBar的点击区域。也就是最终的这个不规则图形是由一个矩形和一个原叠和而成的。

根据我们的到的触摸点P1(x,y), 则如果 y > 20 || distance(P1,P) < 40,则返回响应的hitTestView为TabBar,否则,则返回nil(返回nil则认为当前TabBar不响应事件)。(两点间的距离的计算方式在这里就不说了)

上代码了

[STTabBar]
1
2
3
4
5
6
7
8
9
10
11
12
13
CGPoint publishCenterPoint = CGPointMake(CGRectGetMidX(kPublishTabItemFrame), CGRectGetMidY(kPublishTabItemFrame));
self.tabBar.hitTestBlock = ^(CGPoint point, UIEvent *event, BOOL *returnSuper) {
    /// 按钮的点击区域距离圆心的距离
    CGFloat distance = STDistanceBetweenPoints(point, publishCenterPoint);
    /// 如果不在圆形内,则不响应点击事件,让下一层去响应
    if (distance < CGRectGetHeight(kPublishTabItemFrame) / 2) {
        *returnSuper = YES;
    }
    if (point.y >= 20) {
        *returnSuper = YES;
    }
    return (UIView *)nil;
};

代码很简单的就实现了我们的需求,点击上图所示红色区域内的,TabBar去响应事件,反之,则交给TabBar下面的View去响应事件(hitTestBlock的这一部分可以直接参考之前提过的博客)。

关于TabBar的就说这么多,大家可以通过我的GitHub来下载Demo。Demo中的如果大家想更精确的测试,建议用模拟器+鼠标来操作。

我们根据如上的方法,就能得到一个解决方案。对于不规则的点击区域,我们只需要重写当前View的hitTest方法,然后根据一些数学公式来检测,如果满足条件,则返回当前View,否则,返回nil就可以了,当然前提是我们View的大小要覆盖我们期望的的点击区域,不能出现一些超出View区域的subview。

PS.有时候我们出现一些点击无法响应的情况,我们可以通过设置clipsToBounds=YES来验证是否是因为超出了父View的区域而导致的。

根据如上的思路,我来实现一个圆形的按钮,要求只有点击圆形内才响应事件,否则则不响应按钮事件

[SKRoundButton.h]
1
2
3
4
5
6
7
8
9
10
11
12
13
//
//  SKRoundButton.h
//  STSecret
//
//  Created by SunJiangting on 14-3-27.
//  Copyright (c) 2014年 Attackers. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface SKRoundButton : UIButton

@end
[SKRoundButton.m]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//
//  SKRoundButton.m
//  STSecret
//
//  Created by SunJiangting on 14-3-27.
//  Copyright (c) 2014年 Attackers. All rights reserved.
//

#import "SKRoundButton.h"

@implementation SKRoundButton


- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.layer.masksToBounds = YES;
        
        __weak SKRoundButton *weakSelf = self;
        self.hitTestBlock = ^(CGPoint point, UIEvent *event, BOOL *returnSuper) {
            CGPoint center = CGPointMake(CGRectGetWidth(weakSelf.bounds) / 2, CGRectGetHeight(weakSelf.bounds) / 2);
            if (STDistanceBetweenPoints(point, center) <= weakSelf.size.width / 2) {
                *returnSuper = YES;
            }
            return (UIView *)nil;
        };
    }
    return self;
}

- (void)setFrame:(CGRect)frame {
    if (frame.size.width != frame.size.height) {
        frame.size.width = frame.size.height;
    }
    [super setFrame:frame];
    self.layer.cornerRadius = CGRectGetWidth(frame) / 2;
}

@end

这个Demo的测试大家可以通过如上所示的git中下载,直接点击第二个标签测试就OK了~~

HitTestDemo地址

大家可以通过修改SKRoundButton,自行实现环形的按钮,无非就是数学公式而已,没有其他复杂的地方。

说明:转载自:技术哥

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值