iOS不规则Button响应点击事件
需求
利用图片,实现点击区域是否响应点击事件,有图片内容的则响应,无内容的不响应。
参考:掘金:iOS不规则button点击一
问题描述:
描述:下载了JTShapedButton源码,查看后,存在的问题:
1.如果把设置图片改为背景图片时,改变按钮大小时,界面显示的图片大小与按钮实际大小不一致(为了适配,在不同机型上按钮的实际大小可能会等比缩放,导致实际显示的大小与图片实际大小不一致),点击图片范围事件错误。不该响应的地方也响应,该响应的地方不响应。
2.图片资源只支持1x的图片,改为2x图片,则响应不对。
解决方法:
在获取该点在图片上所在颜色时,实际的点与图片上的点,要等比例计算,得到在原图上该点的实际像素点
//获取在实际原图上该点的值
size_t pixelsWide = CGImageGetWidth(image.CGImage);
size_t pixelsHigh = CGImageGetHeight(image.CGImage);
CGFloat rateWidth = self.bounds.size.width / pixelsWide;
CGFloat rateHeight = self.bounds.size.height / pixelsHigh;
CGPoint realPoint = CGPointMake(point.x / rateWidth, point.y / rateHeight);`
事件传递
- 当用户触摸实际屏幕时,会生成一个Touch Event,将此事件添加到UIApplication管理的事件队列之中。
- UIApplication从事件队列之中按顺序取出事件分发到视图去处理。
- 当事件被发出以后,会从keywindow开始,一次向上传递,包括COntroller以及view,最后找到合适的视图来响应事件。
可以看出:当一个事件发生后,事件会从父控件传给子控件,也就是说由UIApplication->UIWindow->UIView->initial view ,以上就是时间的传递,也就是寻找最合适的view的过程。
涉及到的方法:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
当UIApplication发送时间到keyWindow时,会调用hitTest来找寻最合适的视图处理事件。判断逻辑如下:
- 首先判断自身是否能够响应触摸事件(userInteractionEnabledtrue,hiddentrue,alpha<=0.01不能响应触摸事件),若能响应则下一步,否则返回nil。
- 如果可以响应触摸事件,调用pointInside来判断是否在显示区域内,如果不在其中,pointInside返回false,同时hitTest返回nil。
- 如果 pointInside返回true,表示在当前的视图之中,然后倒序遍历该视图的子视图,重复上述步骤,直到某一视图可以响应,hitTest:返回该视图。
- 如果执行完上述步骤以后,没有符合条件的视图响应事件,则返回视图本身,表示只有当前视图符合条件,能够处理该事件。
Q:为什么倒序遍历?
A:因为在subViews数组中,最后添加的视图,在视图层级中处于最上方。
怎么判断谁来处理当前事件
当知道的上面时间传递机制后,我们就能理清我们的button处理时间逻辑了:
- 自定义Button继承自UIButton。
- 重写- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event 方法,在其中判断是否需要自身处理。
- 判断点是否在自身button.imageView的frame范围内
- 得到点击点在button.imageView中该点的颜色值
- 如果得到的色值中alpha小于阀值,则返回false
自定义按钮代码
//
// LJButton.m
// HexagonDemo
//
// Created by JuneLee on 2019/4/24.
// Copyright © 2019 JuneLee. All rights reserved.
//
#import "LJButton.h"
/** 不响应的alpha最小值 */
static CGFloat minNotResponseAlpha = 0.1;
@interface LJButton ()
/** 记录上一次点击的点 */
@property (nonatomic, assign) CGPoint previousTouchPoint;
/** 记录上一次点击的点的响应值 */
@property (nonatomic, assign) BOOL previousTouchHitTestResponse;
/** 按钮的图片 */
@property (nonatomic, strong) UIImage *contentImage;
/** 按钮的背景图片 */
@property (nonatomic, strong) UIImage *backgroundImage;
@end
@implementation LJButton
/** 初始化默认值 */
- (void)setDefualtParam {
_previousTouchPoint = CGPointMake(-9999, -99);
_contentImage = self.currentImage;
_backgroundImage = self.currentBackgroundImage;
}
#pragma mark - 重写点击方法
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
BOOL superResult = [super pointInside:point withEvent:event];
if (!superResult) {
return superResult;
}
if (point.x == _previousTouchPoint.x && point.y == _previousTouchPoint.y) {
return self.previousTouchHitTestResponse;
}else {
self.previousTouchPoint = point;
}
BOOL response = [self shouldResponseAtPoint:point image:_contentImage] || [self shouldResponseAtPoint:point image:_backgroundImage];
self.previousTouchHitTestResponse = response;
return response;
}
/** 该点是否可响应 */
- (BOOL)shouldResponseAtPoint:(CGPoint)point image:(UIImage *)image {
UIImageView *contentView = self.imageView;
if (!image || !contentView) {
return NO;
}
CGPoint imagePoint = CGPointMake(point.x - contentView.frame.origin.x, point.y - contentView.frame.origin.y);
UIColor *pixeColor = [self imageColorAtPoint:imagePoint image:image];
CGFloat alpla = 0.0;
if (pixeColor) {
[pixeColor getRed:nil green:nil blue:nil alpha:&alpla];
}
return alpla >= minNotResponseAlpha;
}
#pragma mark - 获取该点的颜色值
- (UIColor *)imageColorAtPoint:(CGPoint)point image:(UIImage *)image {
//获取在实际原图上该点的值
size_t pixelsWide = CGImageGetWidth(image.CGImage);
size_t pixelsHigh = CGImageGetHeight(image.CGImage);
CGFloat rateWidth = self.bounds.size.width / pixelsWide;
CGFloat rateHeight = self.bounds.size.height / pixelsHigh;
CGPoint realPoint = CGPointMake(point.x / rateWidth, point.y / rateHeight);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
int bitmapBytesPerRow = (int)(pixelsWide * 4);
UInt32 bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Big;
CGContextRef context = CGBitmapContextCreate(nil, pixelsWide, pixelsHigh, 8, bitmapBytesPerRow, colorSpace, bitmapInfo);
unsigned char* data = CGBitmapContextGetData(context);
if (!context || !data || !image.CGImage) {
return nil;
}
CGRect rect = CGRectMake(0, 0, pixelsWide, pixelsHigh);
CGContextDrawImage(context, rect, image.CGImage);
int offset = 4 * ((pixelsWide * round(realPoint.y)) + round(realPoint.x));
int alpha = data[offset] / 255.0;
int red = data[offset + 1] / 255.0;
int green = data[offset + 2] / 255.0;
int blue = data[offset + 3] / 255.0;
return [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
}
@end