1.Hit-Test机制
2.Demo--在cell上,添加手势画图的demo
并精确手势识别区域为图中扇形区域;
1.Hit-Test机制
当用户触摸(Touch)屏幕进行交互时,系统会首先找到响应者(Responder)。系统检测到触摸操作时,将Touch以UIEvent的方式加入到UIApplication队列中。UIApplication从事件队列中取出事件传递到UIWindow进行处理,UIWindow会通过hitTest:withEvent:方法寻找触碰点所在的试图,这个过程叫做Hit-Test
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView * view = [super hitTest:point withEvent:event];
return view;
}
hit-test的传递顺序如下
UIApplication -> UIWindow -> Root View -> ··· -> subview
在根视图(rootView)上调用pointInside:withEvent:判断触摸点是否在当前视图内;如果返回NO,那么hitTest:withEvent:返回nil;
如果返回YES,那么它会向当前视图的所有子视图发送hitTest:withEvent:消息,所有子视图的遍历顺序是从最顶层视图一直到到最底层视图,即从subviews数组的末尾向前遍历,直到有子视图返回非空对象或者全部子视图遍历完毕。
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event {
BOOL isInside = [super pointInside:point withEvent:event];
return isInside;
}
在这过程中会判断alpha(0-0.01)、userInteractionEnabled = NO、hidden = Yes时则会忽略此次hit-test。
系统就是通过Hit-Test找到触碰到的视图进行响应
2.Demo--在cell上,添加手势画图的demo
(1)首先自定义一个继承自UITableView的自定义表格视图:WTMotionDistanceTableView重写hitTest方法-(UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
#import "WTMotionDistanceTableView.h"
#import "WTMotionDistanceView.h"
@implementation WTMotionDistanceTableView
-(UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
// 1.判断当前控件能否接收事件
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
// 2. 判断点在不在当前控件
if ([self pointInside:point withEvent:event] == NO) return nil;
// 3.从后往前遍历自己的子控件
NSInteger count = self.subviews.count;
UIView *fitView = nil;
for (NSInteger i = count - 1; i >= 0; i--) {
UIView *childView = self.subviews[i];
// 把当前控件上的坐标系转换成子控件上的坐标系
CGPoint childPoint = [self convertPoint:point toView:childView];
fitView = [childView hitTest:childPoint withEvent:event];
if (fitView && [fitView isKindOfClass:[WTMotionDistanceView class]]) {
// 寻找到最合适的view
self.scrollEnabled = NO;
break;
}
}
if (!fitView || ![fitView isKindOfClass:[WTMotionDistanceView class]]) {
self.scrollEnabled = YES;
}
if (!fitView){
fitView = self;
}
// 循环结束,表示没有比自己更合适的view
return fitView;
}
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
return [super pointInside:point withEvent:event];
}
@end
(2)自定义手势绘图视图
.h文件
#import <UIKit/UIKit.h>
static CGFloat kMinMotionDistanceValue = 1.0;
static CGFloat kMaxMotionDistanceValue = 100.0;
static CGFloat kMaxMotionDistanceRedius = 150.0;
static CGFloat kMinMotionDistanceRedius = 37.5; // kMaxMotionDistanceRedius/4
@protocol WTMotionDistanceViewDelegate <NSObject>
-(void)updateFLLightPirMotionDistanceValue:(CGFloat)distance;
@end
@interface WTMotionDistanceView : UIView
+(CGFloat)viewRadian;
+(CGFloat)viewHeight;
+(CGFloat)viewWidth;
@property(nonatomic, assign) CGFloat redius;
@property (nonatomic, weak) id<BCFLMotionDistanceViewDelegate> delegate;
+(CGFloat)changeDeviceMotionValueToAppDistance:(CGFloat)value;
+(CGFloat)changeAppDistanceToDeviceMotionValue:(CGFloat)distance;
@end
.m文件
#import "WTMotionDistanceView.h"
#import "TJLineModel.h"
@interface WTMotionDistanceView ()
@property (nonatomic, assign) CGPoint beginPoint;
@end
@implementation WTMotionDistanceView
-(instancetype)init {
if (self = [super init]) {
self.backgroundColor = [UIColor whiteColor];
}
return self;
}
#pragma mark public methods
+(CGFloat)viewRadian {
return 40.0;
}
+(CGFloat)viewHeight {
return kMaxMotionDistanceRedius;
}
+(CGFloat)viewWidth {
return cos([self viewRadian]*M_PI/180)*kMaxMotionDistanceRedius*2;
}
+(CGFloat)changeAppDistanceToDeviceMotionValue:(CGFloat)distance {
CGFloat max_redius = dp2po(kMaxMotionDistanceRedius);
CGFloat min_redius = dp2po(kMinMotionDistanceRedius);
CGFloat radio = (kMaxMotionDistanceValue-kMinMotionDistanceValue)/(max_redius-min_redius);
CGFloat motionValue = (distance-kMinMotionDistanceRedius)*radio;
motionValue += kMinMotionDistanceValue;
motionValue = motionValue<kMinMotionDistanceValue?kMinMotionDistanceValue:motionValue;
motionValue = motionValue>kMaxMotionDistanceValue?kMaxMotionDistanceValue:motionValue;
return motionValue;
}
+(CGFloat)changeDeviceMotionValueToAppDistance:(CGFloat)value {
CGFloat max_redius = dp2po(kMaxMotionDistanceRedius);
CGFloat min_redius = dp2po(kMinMotionDistanceRedius);
CGFloat radio = (max_redius-min_redius)/(kMaxMotionDistanceValue-kMinMotionDistanceValue);
CGFloat redius = (value-kMinMotionDistanceValue)*radio;
redius += kMinMotionDistanceRedius;
redius = redius<kMinMotionDistanceRedius?kMinMotionDistanceRedius:redius;
redius = redius>kMaxMotionDistanceRedius?kMaxMotionDistanceRedius:redius;
return redius;
}
#pragma mark private
-(void)drawEndedToUpdateDistance {
if (self.delegate && [self.delegate respondsToSelector:@selector(updateFLLightPirMotionDistanceValue:)]) {
[self.delegate updateFLLightPirMotionDistanceValue:_redius];
}
}
#pragma mark set/get
-(void)setRedius:(CGFloat)redius {
if (_redius != redius) {
_redius = redius;
[self setNeedsDisplay];
}
}
#pragma mark 重写 画图
// Only override drawRect: if you perform custom drawing.
- (void)drawRect:(CGRect)rect {
CGPoint startPoint = CGPointMake(self.bounds.size.width/2, 0);
CGFloat max_redius = dp2po(kMaxMotionDistanceRedius);
CGFloat min_redius = dp2po(kMinMotionDistanceRedius);
CGFloat new_redius = _redius;
if (new_redius<min_redius) {
new_redius = min_redius;
}
if (new_redius>max_redius) {
new_redius = max_redius;
}
_redius = new_redius;
//上下文
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat radian = [BCFLMotionDistanceView viewRadian];
/*画扇形*/
//iGlobalFocusColor=2b92f9
UIColor *defaultColor = iColor(0xeb, 0xf0, 0xf5, 1.0);
CGContextSetFillColorWithColor(context, defaultColor.CGColor);//填充颜色
CGContextSetStrokeColorWithColor(context, defaultColor.CGColor);
CGContextMoveToPoint(context, startPoint.x, startPoint.y);
CGContextAddArc(context, startPoint.x, startPoint.y, max_redius, radian* M_PI / 180, (180-radian) * M_PI / 180, 0);
CGContextClosePath(context);
CGContextDrawPath(context, kCGPathFillStroke); //绘制路径
//ebf0f5
UIColor *focusColor = iColor(0x2b, 0x92, 0xf9, 0.7);
CGContextSetFillColorWithColor(context, focusColor.CGColor);
CGContextSetStrokeColorWithColor(context, focusColor.CGColor);
//以redius为半径围绕圆心画指定角度扇形
CGContextMoveToPoint(context, startPoint.x, startPoint.y);
CGContextAddArc(context, startPoint.x, startPoint.y, new_redius, radian * M_PI / 180, (180-radian) * M_PI / 180, 0);
CGContextClosePath(context);
CGContextDrawPath(context, kCGPathFillStroke); //绘制路径
}
#pragma mark 手势
-(UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
// 1.判断当前控件能否接收事件
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
// 2. 判断点在不在当前控件
if ([self pointInside:point withEvent:event] == NO) return nil;
return self;
}
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
//需要判断当前触摸点是否在扇形区域内;
/*
扇形区域ABC, 圆点A, 弧线BC, 直线AB, 直线AC;
-0---------------A---------------
|
|
B | C
|
*/
//width,height
//1.直线AB, A(viewWeight/2, 0), B(0, redius*sin(40*M_PI/180))
//2.直线AC, A(viewWeight/2, 0), B(2*redius*cos(40*M_PI/180), redius*sin(40*M_PI/180))
//3.弧线BC, 圆方程A(viewWeight/2, 0), R=redius
//4.当前点坐标: (x, y)
CGFloat width = [BCFLMotionDistanceView viewWidth];
CGFloat height = [BCFLMotionDistanceView viewHeight];
CGFloat radian = [BCFLMotionDistanceView viewRadian];
CGFloat cur_x = point.x, cur_y = point.y;
CGPoint pointA= CGPointMake(width/2, 0);
CGPoint pointB = CGPointMake(0, sin(radian*M_PI/180)*height);
CGPoint pointC = CGPointMake(width, sin(radian*M_PI/180)*height);
//判断是否位于直线AB下方
TJLineModel *lineAB = [TJLineModel newLineModelWithPoint1:pointA point2:pointB];
BOOL inLineAB = [lineAB isAboveLineWithPoint:point];
//判断是否位于直线AC下方
TJLineModel *lineAC = [TJLineModel newLineModelWithPoint1:pointA point2:pointC];
BOOL inLineAC = [lineAC isAboveLineWithPoint:point];
//判断是否位于圆内
BOOL inCircle = (powf(point.x-pointA.x, 2.0)+powf(point.y-pointA.y, 2.0)-powf(height, 2.0))<0;
if (inLineAB && inLineAC && inCircle) {
return YES;
}
return NO;//[super pointInside:point withEvent:event];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint p = [touch locationInView:self];
_beginPoint = p;
}
-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint p = [touch locationInView:self];
CGFloat offsetY = p.y - _beginPoint.y;
//更新
self.redius = _redius + offsetY;
_beginPoint = p;
}
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
_beginPoint = CGPointZero;
[self drawEndedToUpdateDistance];
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
_beginPoint = CGPointZero;
[self drawEndedToUpdateDistance];
}
@end
(3)当中涉及的直线模型
.h文件
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
#define WuQiongDa 1.0/0.0
@interface TJLineModel : NSObject
//线段标示直线,线段中点
@property (nonatomic, assign) CGPoint centerPoint;
//直线斜率
@property (nonatomic, assign) CGFloat lineSlope;
//直线-斜截式,y=kx+b
@property (nonatomic, assign) CGFloat lineValueB;
//类方法
+(instancetype)newLineModelWithPoint1:(CGPoint)p1 point2:(CGPoint)p2;
//计算交叉点
-(CGPoint)computeIntersectLine1:(TJLineModel*)l1 line2:(TJLineModel*)l2;
//已知直线上两点,判断某点是否在线段上
-(BOOL)isTouchLineWithPoint:(CGPoint)point;
//已知直线方程,判断某点相对位置 上下(上==左, 下==右)
-(BOOL)isAboveLineWithPoint:(CGPoint)point;
@end
.m文件
#import "TJLineModel.h"
#define kMinTouchDXY 30/2
@interface TJLineModel ()
{
CGPoint _point1;
CGPoint _point2;
CGPoint _centerPoint;
}
//已知直线上两点
@property (nonatomic, assign) CGPoint point1;
@property (nonatomic, assign) CGPoint point2;
@end
@implementation TJLineModel
+(instancetype)newLineModelWithPoint1:(CGPoint)p1 point2:(CGPoint)p2
{
TJLineModel *newLM = [TJLineModel new];
newLM.point1 = p1;
newLM.point2 = p2;
if (newLM.point1.x == newLM.point2.x) {
newLM.lineValueB = 0;
}else if (newLM.point1.y == newLM.point2.y)
{
newLM.lineValueB = WuQiongDa;
}else
{
newLM.lineValueB = (newLM.point1.y-newLM.point1.x*(newLM.point1.y-newLM.point2.y)/(newLM.point1.x-newLM.point2.x));
}
return newLM;
}
//已知直线上两点
-(void)setPoint1:(CGPoint)point1
{
_point1 = point1;
}
-(void)setPoint2:(CGPoint)point2
{
_point2 = point2;
}
//两点之间中点
-(CGPoint)centerPoint
{
_centerPoint = CGPointMake((self.point1.x+self.point2.x)/2, (self.point1.y+self.point2.y)/2);
return _centerPoint;
}
/*
直线平移,斜率不变:y=kb+b
中点:(Cx0,Cy0)-->(Cx=Cx0+dx, Cy=Cy0+dy)
db=dy-dx*(ay-by)/(ax-bx)
*/
-(void)setCenterPoint:(CGPoint)centerPoint
{
//原来的两点之间的中点
CGPoint originCenter = self.centerPoint;
//新的中点
_centerPoint = centerPoint;
//更新直线的参数
/*
分为几种情况:
1.l1为垂直于x轴的直线
2.l1为垂直于y轴的直线
3.l1直线斜率均存在且不为0
*/
//1.为垂直于x轴的直线
if (_point1.x == _point2.x) {
_point1 = CGPointMake(_centerPoint.x, _point1.y);
_point2 = CGPointMake(_centerPoint.x, _point2.y);
}
//2.为垂直于y轴的直线
if (_point1.y == _point2.y) {
_point1 = CGPointMake(_point1.x, _centerPoint.y);
_point2 = CGPointMake(_point2.x, _centerPoint.y);
}
//3.直线斜率均存在且不为0
if (_point1.x!=_point2.x && _point1.y!=_point2.y) {
//位移
CGFloat dx = centerPoint.x-originCenter.x;
CGFloat dy = centerPoint.y-originCenter.y;
CGFloat dValueB = dy+dx*(self.point2.y-self.point1.y)/(self.point1.x-self.point2.x);
self.lineValueB += dValueB;
}
}
//斜率
-(CGFloat)lineSlope
{
_lineSlope = (self.point1.y-self.point2.y)/(self.point1.x-self.point2.x);
if (_lineSlope==0) {
_lineSlope = 0.001;
}
if (_lineSlope==WuQiongDa) {
_lineSlope = tan(M_PI/2-0.0001);
}
return _lineSlope;
}
//斜截式b--y=kx+b
-(CGFloat)lineValueB
{
return _lineValueB;
}
//已知直线上两点,判断某点是否在线段上
-(BOOL)isTouchLineWithPoint:(CGPoint)point {
if (_point1.x == _point2.x) {
CGFloat y_max = _point1.y>_point2.y?_point1.y:_point2.y;
CGFloat y_min = _point1.y<_point2.y?_point1.y:_point2.y;
if (fabs(point.x-_point1.x)<kMinTouchDXY && (point.y>=y_min && point.y<=y_max)) return YES;
} else if (_point1.y == _point2.y) {
CGFloat x_max = _point1.x>_point2.x?_point1.x:_point2.x;
CGFloat x_min = _point1.x<_point2.x?_point1.x:_point2.x;
if (fabs(point.y-_point1.y)<kMinTouchDXY && (point.x>=x_min && point.x<=x_max)) return YES;
} else {
_lineValueB = (_point1.y-_point1.x*(_point1.y-_point2.y)/(_point1.x-_point2.x));
CGFloat value = point.y - _lineSlope*point.x;
if (value>=-kMinTouchDXY/2 && value<=kMinTouchDXY/2) {
CGFloat x_max = _point1.x>_point2.x?_point1.x:_point2.x;
CGFloat x_min = _point1.x<_point2.x?_point1.x:_point2.x;
if (point.x>=x_min && point.x<=x_max) return YES;
}
}
return NO;
}
//已知直线方程,判断某点相对位置 上下(上==左, 下==右)
-(BOOL)isAboveLineWithPoint:(CGPoint)point {
if (_point1.x == _point2.x) {
//垂直于x轴
if (point.x>_point1.x) {
return YES;
}
} else if (_point1.y == _point2.y) {
//垂直于y轴
if (point.y>_point1.y) {
return YES;
}
} else {
_lineValueB = (_point1.y-_point1.x*(_point1.y-_point2.y)/(_point1.x-_point2.x));
CGFloat value = point.y - self.lineSlope*point.x-_lineValueB;
if (value>0) {
return YES;
}
}
return NO;
}
//计算交叉点 已知:直线斜截式公式,求两直线交点
-(CGPoint)computeIntersectLine1:(TJLineModel*)l1 line2:(TJLineModel*)l2 {
/*
分为几种情况:
1.(1)l1为垂直于x轴的直线
(2)l1为垂直于y轴的直线
2.(1)l2为垂直于x轴的直线
(2)l3为垂直于y轴的直线
3. l1、l2直线斜率均存在且不为0
*/
//1.(1)l1垂直于x轴的直线
if (l1.point1.x == l1.point2.x) {
//A
if (l2.point1.x == l2.point2.x) {
//l2垂直于x轴的直线 - 无交点
return CGPointMake(-1, -1);
}
//B
if (l2.point1.y == l2.point2.y) {
//l2垂直于y轴的直线
return CGPointMake(l1.point1.x, l2.point1.y);
}
return CGPointMake(l1.point1.x, l2.lineSlope*l1.point1.x+l2.lineValueB);
}
//1.(2)l1垂直于y轴的直线
if (l1.point1.y == l1.point2.y) {
//A
if (l2.point1.x == l2.point2.x) {
//l2垂直于x轴的直线
return CGPointMake(l2.point1.x, l1.point1.y);
}
//B
if (l2.point1.y == l2.point2.y) {
//l2垂直于y轴的直线 - 无交点
return CGPointMake(-1, -1);
}
return CGPointMake((l1.point1.y-l2.lineValueB)/l2.lineSlope, l1.point1.y);
}
//2.(1)//l2垂直于x轴的直线
if (l2.point1.x == l2.point2.x) {
return CGPointMake(l2.point1.x, l1.lineSlope*l2.point1.x+l1.lineValueB);
}
//2.(2)//l2垂直于y轴的直线
if (l2.point1.y == l2.point2.y) {
return CGPointMake((l2.point1.y-l1.lineValueB)/l1.lineSlope, l2.point1.y);
}
//3.l1、l2直线斜率均存在且不为0
CGFloat x = (l1.lineValueB-l2.lineValueB)/(l2.lineSlope-l1.lineSlope);
CGFloat y = l1.lineValueB+l1.lineSlope*(l1.lineValueB-l2.lineValueB)/(l2.lineSlope-l1.lineSlope);
return CGPointMake(x, y);
}
@end