判断某个 point 是否在 View 内部是开发中非常常见的场景,比如最典型的 tap 手势判断 touch point 是否在某个 view 范围内:

图 1 - App 常见页面:欢迎弹窗
在实际的开发中,我遇到了 view 旋转后需要判断点是否在范围内的问题。在这篇文章中我会从简单情况到我真实遇到的问题一步步推导,阐明我是如何解决该问题的
View 未发生旋转
假设 View 没有发生旋转,我们仅需要使用 CGRect 提供的 CGRectContainsPoint 函数就可以进行判断。例如前言中提及的这个欢迎弹窗的 tap 手势判断:
|  |  | 
View 发生旋转
当视图进行了旋转后,出现的一个问题就是 view.frame 不再能准确描述 view 的范围。我们查看 CGRect 的定义:
|  |  | 
CGRect 是一个仅由矩形原点以及矩形长宽组成的结构体,这样子的描述方式是无法描述一个旋转过后的矩形的。事实上,旋转后再打印 view.frame 会得到一个面积更大的矩形(前提是旋转角度不是 90 的倍数)。因此不能再使用 self.contentView.frame 进行判断
使用 CGRectApplyAffineTransform 函数可以将参数中的 CGRect 进行变换,返回新的 CGRect,查阅函数的注释:
|  |  | 
可知变换后无法保留矩形,实际上是将原矩形的四个角点进行变换,返回能容纳新的四个角点的最小矩形
Frame 与 Bounds
Reference: 图层几何学

图 2 - 变换前的 frame 与 bounds

图 3 - 旋转后的 frame 与 bounds
两张图很好地说明了 frame 与 bounds 的区别:
- frame: 视图的外部坐标(相对坐标),原点与父图层保持一致
- bounds: 视图的内部坐标(绝对坐标),原点位于视图的左上角
当发生旋转后,frame 相当于调用了 CGRectApplyAffineTransform 函数,转换为能容纳旋转后视图的最小矩形,因此 origin, size 都会发生改变
而 bounds 由于是绝对坐标,相当于整个坐标系都一起发生了旋转,因此不会有变化:

图 4 - bounds 坐标系旋转
convertPoint:toView:
convertPoint:toView: 方法是一个坐标系转化方法,能够将给定的 CGPoint 转化到目标 View 的坐标系中
因此我们先将 point 转化到 contentView 中,得到在 contentView.bounds 这个坐标系里点的坐标,再判断转换后的点是否在 contentView.bounds 中,就可以得到正确的结果
|  |  | 
View 发生旋转 && frame only
最后一种情况也是开发中我实际遇到的是无法获取对应的 View,仅能获取对应的 frame 以及视图的旋转角度,要判断 touch point 是否在视图范围内
由于无法获取对应的 view,也就没有办法得到 view.bounds,无法将 touchPoint 进行坐标系转化
重新分析我们的问题,假设我们的 touchPoint 在旋转后的矩形内:

图 5 - touchPoint 位于旋转后的矩形
视图发生旋转,本质上是矩形内所有点围绕中心点发生了旋转,因此我们的 touchPoint 如果逆角度进行旋转可以转回原来的矩形(也就是我们的 frame):

图 6 - touchPoint 转化
我们需要对 touchPoint 进行旋转,将 point 绕矩形的中心点反方向旋转对应的角度,再判断新的 point 是否在 frame 内
CGPoint 中没有提供绕点旋转的方法,需要我们自己进行计算
Reference: 计算几何之向量旋转
推广结论:对于任意两个不同点A和B,A绕B旋转θ角度后的坐标为:
(Δxcosθ- Δy * sinθ+ xB, Δycosθ + Δx * sinθ+ yB )
注:xB、yB为B点坐标。
|  |  | 
在使用该方法时会发现旋转 90° 得到的点坐标有很小的误差,是因为我们使用的圆周率并不是真实值,查看 math.h 文件可以看到 M_PI 的定义:
|  |  | 
但这样的误差实际上非常小,可以忽略不计
Summary
最开始的时候,我考虑的做法是计算旋转过后矩形的四个角的坐标,再计算新的每条边的方程式,用类似高中数学里线性规划的方式去计算 point 是否在范围内:
screenshot: 线性规划常见题型和解法

图 7 - 线性规划例题
之后想到矩形旋转比较复杂,可以尝试反过来考虑把点进行旋转。伟大的哲学家拉大缸“先生”教导我们:只有回归,才能揭露秘密
screenshot: 疾风华莱士

图 8 - 「只有回归,才能揭露秘密」
让我们伸出双手,摆出这个 pose,愉快地结束这篇博客~~
 
             
            