3

I have two UIViews, one of which is rotated every .01 second using the following code:

    self.rectView.transform = CGAffineTransformRotate(self.rectView.transform, .05);

Now, I want to be able to tell if another UIView, called view, intersects rectView. I used this code:

if(CGRectIntersectsRect(self.rectView.frame, view.frame)) {
    //Intersection
}

There is a problem with this, however, as you probably know. Here is a screenshot: enter image description here

In this case, a collision is detected, even though obviously they are not touching. I have looked around, but I cannot seem to find real code to detect this collision. How can this be done? Working code for detecting the collision in this case would be greatly appreciated! Or maybe would there be a better class to be using other than UIView?

John Farkerson
  • 2,543
  • 2
  • 23
  • 33
  • I don't know the answer but the cause is probably down to the fact that a view's frame is not affected by any transforms applied to a view. – Jasarien Feb 13 '14 at 15:25
  • Ok @Jasarien, do you know of a better class I could be using other than UIView? – John Farkerson Feb 13 '14 at 15:26
  • I think you should use some OpenGL framework if you plan to do a lot of things like it. If it's casual, you can still do some arithmetics. But this kind are already done in most of game framework. – Tancrede Chazallet Feb 13 '14 at 15:37
  • If your application is for iOS 7 and more, check the `UICollisionBehavior` class. – Emmanuel Feb 13 '14 at 15:55
  • If you're intending to make a game, I would recommend using a game framework, like Sprite Kit or Cocos 2d. While it's still possible make games with UIKit, you'll find it a lot easier to do with a game framework that will handle things like collision for you, built into the framework. – Jasarien Feb 13 '14 at 16:00
  • @AncAinu - I would love to see the arithmetic you mentioned to detect the collision. – John Farkerson Feb 13 '14 at 18:24
  • This can be done much more simply and in an optimized manner using apple's built in CoreGraphics functions. There is no need to bring cross-products and vector algebra into this... I urge you to update your selected answer for the sake of future readers: https://stackoverflow.com/a/66096992/2057171 – Albert Renshaw Feb 08 '21 at 06:48

2 Answers2

2

when you rotate a view, its bounds won't change but its frame changes.

So, for my view with backgroundColor blue,
the initial frame i set to was

frame = (30, 150, 150, 35);
bounds={{0, 0}, {150, 35}};

but after rotating by 45 degree, the frame changed to

frame = (39.5926 102.093; 130.815 130.815);
bounds={{0, 0}, {150, 35}};

screenshot of running app showing frame of blue view with black border

Because the frame always return the smallest enclosing rectangle of that view.

So, in your case, even-though it looks both views are not intersecting,their frames intersect.

To solve it you can use, separating axis test. If you want learn on it, link here

I tried to solve it and finally got the solution. If you like to check, below is the code. Copy paste the below code into an empty project to check it out.

In .m file

@implementation ViewController{
    UIView *nonRotatedView;
    UIView *rotatedView;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    nonRotatedView =[[UIView alloc] initWithFrame:CGRectMake(120, 80, 150, 40)];
    nonRotatedView.backgroundColor =[UIColor blackColor];
    [self.view addSubview:nonRotatedView];

    rotatedView =[[UIView alloc] initWithFrame:CGRectMake(30, 150, 150, 35)];
    rotatedView.backgroundColor =[UIColor blueColor];
    [self.view addSubview:rotatedView];
    CGAffineTransform t=CGAffineTransformMakeRotation(M_PI_4);
    rotatedView.transform=t;

    CAShapeLayer *layer =[CAShapeLayer layer];
    [layer setFrame:rotatedView.frame];
    [self.view.layer addSublayer:layer];
    [layer setBorderColor:[UIColor blackColor].CGColor];
    [layer setBorderWidth:1];

    CGPoint p=CGPointMake(rotatedView.bounds.size.width/2, rotatedView.bounds.size.height/2);

    p.x = -p.x;p.y=-p.y;
    CGPoint tL =CGPointApplyAffineTransform(p, t);
    tL.x +=rotatedView.center.x;
    tL.y +=rotatedView.center.y;

    p.x = -p.x;
    CGPoint tR =CGPointApplyAffineTransform(p, t);
    tR.x +=rotatedView.center.x;
    tR.y +=rotatedView.center.y;

    p.y=-p.y;
    CGPoint bR =CGPointApplyAffineTransform(p, t);
    bR.x +=rotatedView.center.x;
    bR.y +=rotatedView.center.y;

    p.x = -p.x;
    CGPoint bL =CGPointApplyAffineTransform(p, t);
    bL.x +=rotatedView.center.x;
    bL.y +=rotatedView.center.y;


    //check for edges of nonRotated Rect's edges
    BOOL contains=YES;
    CGFloat value=nonRotatedView.frame.origin.x;
    if(tL.x<value && tR.x<value && bR.x<value && bL.x<value)
        contains=NO;
    value=nonRotatedView.frame.origin.y;
    if(tL.y<value && tR.y<value && bR.y<value && bL.y<value)
        contains=NO;
    value=nonRotatedView.frame.origin.x+nonRotatedView.frame.size.width;
    if(tL.x>value && tR.x>value && bR.x>value && bL.x>value)
        contains=NO;
    value=nonRotatedView.frame.origin.y+nonRotatedView.frame.size.height;
    if(tL.y>value && tR.y>value && bR.y>value && bL.y>value)
        contains=NO;

    if(contains==NO){
        NSLog(@"no intersection 1");
        return;
    }
    //check for roatedView's edges
    CGPoint rotatedVertexArray[]={tL,tR,bR,bL,tL,tR};

    CGPoint nonRotatedVertexArray[4];
    nonRotatedVertexArray[0]=CGPointMake(nonRotatedView.frame.origin.x,nonRotatedView.frame.origin.y);
    nonRotatedVertexArray[1]=CGPointMake(nonRotatedView.frame.origin.x+nonRotatedView.frame.size.width,nonRotatedView.frame.origin.y);
    nonRotatedVertexArray[2]=CGPointMake(nonRotatedView.frame.origin.x+nonRotatedView.frame.size.width,nonRotatedView.frame.origin.y+nonRotatedView.frame.size.height);
    nonRotatedVertexArray[3]=CGPointMake(nonRotatedView.frame.origin.x,nonRotatedView.frame.origin.y+nonRotatedView.frame.size.height);

    NSInteger i,j;
    for (i=0; i<4; i++) {
        CGPoint first=rotatedVertexArray[i];
        CGPoint second=rotatedVertexArray[i+1];
        CGPoint third=rotatedVertexArray[i+2];
        CGPoint mainVector =CGPointMake(second.x-first.x, second.y-first.y);
        CGPoint selfVector =CGPointMake(third.x-first.x, third.y-first.y);
        BOOL sign;
        sign=[self crossProductOf:mainVector withPoint:selfVector];
        for (j=0; j<4; j++) {
            CGPoint otherPoint=nonRotatedVertexArray[j];
            CGPoint otherVector = CGPointMake(otherPoint.x-first.x, otherPoint.y-first.y);
            BOOL checkSign=[self crossProductOf:mainVector withPoint:otherVector];
            if(checkSign==sign)
                break;
            else if (j==3)
                contains=NO;
        }
        if(contains==NO){
            NSLog(@"no intersection 2");
            return;
        }
    }
    NSLog(@"intersection");
}


-(BOOL)crossProductOf:(CGPoint)point1 withPoint:(CGPoint)point2{
    if((point1.x*point2.y-point1.y*point2.x)>=0)
         return YES;
    else
        return NO;
}

Hope this helps.

Community
  • 1
  • 1
santhu
  • 4,796
  • 1
  • 21
  • 29
0

This can be done much more efficiently and easily than what has been suggested... and both the black and blue views can be rotated if need be.

Just convert the 4 corners of the rotated blueView to their location in the superview and then convert those points to their location in the rotated blackView then check if those points are within the blackView's bounds.

UIView *superview = blueView.superview;//Assuming blueView.superview and blackView.superview are the same...

CGPoint blueView_topLeft_inSuperview = [blueView convertPoint:CGPointMake(0, 0) toView:superview];
CGPoint blueView_topRight_inSuperview = [blueView convertPoint:CGPointMake(blueView.bounds.size.width, 0) toView:superview];
CGPoint blueView_bottomLeft_inSuperview = [blueView convertPoint:CGPointMake(0, blueView.bounds.size.height) toView:superview];
CGPoint blueView_bottomRight_inSuperview = [blueView convertPoint:CGPointMake(blueView.bounds.size.width, blueView.bounds.size.height) toView:superview];

CGPoint blueView_topLeft_inBlackView = [superview convertPoint:blueView_topLeft_inSuperview toView:blackView];
CGPoint blueView_topRight_inBlackView = [superview convertPoint:blueView_topRight_inSuperview toView:blackView];
CGPoint blueView_bottomLeft_inBlackView = [superview convertPoint:blueView_bottomLeft_inSuperview toView:blackView];
CGPoint blueView_bottomRight_inBlackView = [superview convertPoint:blueView_bottomRight_inSuperview toView:blackView];

BOOL collision = (CGRectContainsPoint(blackView.bounds, blueView_topLeft_inBlackView) ||
                  CGRectContainsPoint(blackView.bounds, blueView_topRight_inBlackView) ||
                  CGRectContainsPoint(blackView.bounds, blueView_bottomLeft_inBlackView) ||
                  CGRectContainsPoint(blackView.bounds, blueView_bottomRight_inBlackView));
Albert Renshaw
  • 17,282
  • 18
  • 107
  • 195
  • 1
    Hmm, isn't there a possibility of there being a collision, but none of the corners of the blue rectangle are within the black rectangle? Like if the blue rectangle is at a 45 degree angle and the corner of the black rectangle is touching the side. I guess we might have to do the same process with the corners of the black rectangle, checking if they're within the blue rectangle as well? – John Farkerson Feb 25 '21 at 08:09
  • @SamuelNoyes The above code should work for all corners of both rects automatically, regardless of eithers' rotation. – Albert Renshaw Feb 25 '21 at 19:27