Objective-C如何检查旋转UIView的子视图是否相交?

10

我不知道从哪里开始。 显然,CGRectIntersectsRect在这种情况下无法使用,您将看到原因。

我有一个UIView的子类,其中包含一个UIImageView,该UIImageView放置在UIView的正中心:

旋转前的UIView

然后我旋转自定义UIView以保持内部UIImageView的框架,同时仍能执行CGAffineRotation。 结果的框架看起来像这样:

旋转后的UIView

我需要防止用户使这些UIImageView相交,但是我不知道如何检查两个UIImageView之间的交点,因为它们的框架不仅不适用于父UIView,而且还会在不影响其框架的情况下旋转。

我的尝试只有失败的结果。

有任何想法吗?

1个回答

10

以下算法可用于检查两个(旋转或其他转换的)视图是否重叠:

  • 使用 [view convertPoint:point toView:nil] 将两个视图的 4 个边界点转换为一个公共坐标系(窗口坐标)。
  • 转换后的点形成两个凸四边形。
  • 使用SAT(分离轴定理)检查四边形是否相交。

这里有另一个描述该算法并包含伪代码的链接:http://www.geometrictools.com/Documentation/MethodOfSeparatingAxes.pdf。通过搜索“分离轴定理”可以找到更多相关信息。


更新:我尝试创建一个 Objective-C 方法来实现“分离轴定理”,以下是我的成果。目前为止,我只进行了一些测试,所以希望没有太多错误。

- (BOOL)convexPolygon:(CGPoint *)poly1 count:(int)count1 intersectsWith:(CGPoint *)poly2 count:(int)count2;

测试两个凸多边形是否相交。两个多边形都作为顶点的 CGPoint 数组给出。

- (BOOL)view:(UIView *)view1 intersectsWith:(UIView *)view2

测试(如上所述)两个任意视图是否相交。

实现:

- (void)projectionOfPolygon:(CGPoint *)poly count:(int)count onto:(CGPoint)perp min:(CGFloat *)minp max:(CGFloat *)maxp
{
    CGFloat minproj = MAXFLOAT;
    CGFloat maxproj = -MAXFLOAT;
    for (int j = 0; j < count; j++) {
        CGFloat proj = poly[j].x * perp.x + poly[j].y * perp.y;
        if (proj > maxproj)
            maxproj = proj;
        if (proj < minproj)
            minproj = proj;
    }
    *minp = minproj;
    *maxp = maxproj;
}

-(BOOL)convexPolygon:(CGPoint *)poly1 count:(int)count1 intersectsWith:(CGPoint *)poly2 count:(int)count2
{
    for (int i = 0; i < count1; i++) {
        // Perpendicular vector for one edge of poly1:
        CGPoint p1 = poly1[i];
        CGPoint p2 = poly1[(i+1) % count1];
        CGPoint perp = CGPointMake(- (p2.y - p1.y), p2.x - p1.x);

        // Projection intervals of poly1, poly2 onto perpendicular vector:
        CGFloat minp1, maxp1, minp2, maxp2;
        [self projectionOfPolygon:poly1 count:count1 onto:perp min:&minp1 max:&maxp1];
        [self projectionOfPolygon:poly2 count:count1 onto:perp min:&minp2 max:&maxp2];

        // If projections do not overlap then we have a "separating axis"
        // which means that the polygons do not intersect:
        if (maxp1 < minp2 || maxp2 < minp1)
            return NO;
    }

    // And now the other way around with edges from poly2:
    for (int i = 0; i < count2; i++) {
        CGPoint p1 = poly2[i];
        CGPoint p2 = poly2[(i+1) % count2];
        CGPoint perp = CGPointMake(- (p2.y - p1.y), p2.x - p1.x);

        CGFloat minp1, maxp1, minp2, maxp2;
        [self projectionOfPolygon:poly1 count:count1 onto:perp min:&minp1 max:&maxp1];
        [self projectionOfPolygon:poly2 count:count1 onto:perp min:&minp2 max:&maxp2];

        if (maxp1 < minp2 || maxp2 < minp1)
            return NO;
    }

    // No separating axis found, then the polygons must intersect:
    return YES;
}

- (BOOL)view:(UIView *)view1 intersectsWith:(UIView *)view2
{
    CGPoint poly1[4];
    CGRect bounds1 = view1.bounds;
    poly1[0] = [view1 convertPoint:bounds1.origin toView:nil];
    poly1[1] = [view1 convertPoint:CGPointMake(bounds1.origin.x + bounds1.size.width, bounds1.origin.y) toView:nil];
    poly1[2] = [view1 convertPoint:CGPointMake(bounds1.origin.x + bounds1.size.width, bounds1.origin.y + bounds1.size.height) toView:nil];
    poly1[3] = [view1 convertPoint:CGPointMake(bounds1.origin.x, bounds1.origin.y + bounds1.size.height) toView:nil];

    CGPoint poly2[4];
    CGRect bounds2 = view2.bounds;
    poly2[0] = [view2 convertPoint:bounds2.origin toView:nil];
    poly2[1] = [view2 convertPoint:CGPointMake(bounds2.origin.x + bounds2.size.width, bounds2.origin.y) toView:nil];
    poly2[2] = [view2 convertPoint:CGPointMake(bounds2.origin.x + bounds2.size.width, bounds2.origin.y + bounds2.size.height) toView:nil];
    poly2[3] = [view2 convertPoint:CGPointMake(bounds2.origin.x, bounds2.origin.y + bounds2.size.height) toView:nil];

    return [self convexPolygon:poly1 count:4 intersectsWith:poly2 count:4];
}

Swift版本。(通过扩展在UIView中添加了此行为)

extension UIView {

func projection(of polygon: [CGPoint], perpendicularVector: CGPoint) -> (CGFloat, CGFloat) {
    var minproj = CGFloat.greatestFiniteMagnitude
    var maxproj = -CGFloat.greatestFiniteMagnitude
    
    for j in 0..<polygon.count {
        let proj = polygon[j].x * perpendicularVector.x + polygon[j].y * perpendicularVector.y
        if proj > maxproj {
            maxproj = proj
        }
        
        if proj < minproj {
            minproj = proj
        }
    }
    
    return (minproj, maxproj)
}

func convex(polygon: [CGPoint], intersectsWith polygon2: [CGPoint]) -> Bool {
    //
    let count1 = polygon.count
    for i in 0..<count1 {
        let p1 = polygon[i]
        let p2 = polygon[(i+1) % count1]
        let perpendicularVector = CGPoint(x: -(p2.y - p1.y), y: p2.x - p1.x)
        
        let m1 = projection(of: polygon, perpendicularVector: perpendicularVector)
        let minp1 = m1.0
        let maxp1 = m1.1

        let m2 = projection(of: polygon2, perpendicularVector: perpendicularVector)
        let minp2 = m2.0
        let maxp2 = m2.1
        
        if maxp1 < minp2 || maxp2 < minp1 {
            return false
        }
    }
    //
    let count2 = polygon2.count
    for i in 0..<count2 {
        let p1 = polygon2[i]
        let p2 = polygon2[(i+1) % count2]
        let perpendicularVector = CGPoint(x: -(p2.y - p1.y), y: p2.x - p1.x)
        
        let m1 = projection(of: polygon, perpendicularVector: perpendicularVector)
        let minp1 = m1.0
        let maxp1 = m1.1

        let m2 = projection(of: polygon2, perpendicularVector: perpendicularVector)
        let minp2 = m2.0
        let maxp2 = m1.0
        
        if maxp1 < minp2 || maxp2 < minp1 {
            return false
        }
    }
    //
    return true
}

func intersects(with someView: UIView) -> Bool {
    //
    var points1 = [CGPoint]()
    let bounds1 = bounds
    let p11 = convert(bounds1.origin, to: nil)
    let p21 = convert(CGPoint(x: bounds1.origin.x + bounds1.size.width, y: bounds1.origin.y), to: nil)
    let p31 = convert(CGPoint(x: bounds1.origin.x + bounds1.size.width, y: bounds1.origin.y + bounds1.size.height) , to: nil)
    let p41 = convert(CGPoint(x: bounds1.origin.x, y: bounds1.origin.y + bounds1.size.height), to: nil)
    points1.append(p11)
    points1.append(p21)
    points1.append(p31)
    points1.append(p41)
    //
    var points2 = [CGPoint]()
    let bounds2 = someView.bounds
    let p12 = someView.convert(bounds2.origin, to: nil)
    let p22 = someView.convert(CGPoint(x: bounds2.origin.x + bounds2.size.width, y: bounds2.origin.y), to: nil)
    let p32 = someView.convert(CGPoint(x: bounds2.origin.x + bounds2.size.width, y: bounds2.origin.y + bounds2.size.height) , to: nil)
    let p42 = someView.convert(CGPoint(x: bounds2.origin.x, y: bounds2.origin.y + bounds2.size.height), to: nil)
    points2.append(p12)
    points2.append(p22)
    points2.append(p32)
    points2.append(p42)
    //
    return convex(polygon: points1, intersectsWith: points2)
}

@MartinR 我在这里发了一个新问题(https://dev59.com/t4Xca4cB1Zd3GeqPCAK2),基于这个问题/答案。想知道你是否可以看一眼? - brandonscript
@MartinR 解释得非常清楚。我刚刚实现了这段代码,它完美地工作了。非常感谢!接下来有个问题。您如何计算旋转的UIView与未旋转的UIView重叠的百分比? - Holger Sindbaek
对不起,我没有答案。但我认为没有一个“简单”的公式。 - Martin R

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接