How to use CGRectContainsPoint() with rotated UIView

I have a UIView, and the user can tap the UIView to ‘select’ or highlight the in-app ‘thing’ that it represents. I use CGRectContainsPoint(thing.frame,tapPoint) to achieve this, where thing.frame is the frame of the UIView, and tapPoint is the tapped point from a UITapGestureRecognizer. This works perfect.

..except when UIView is rotated by setting the transform property (with a CGAffineTransform value). When the UIView is rotated like this, the frame becomes a flat square that encapsulates the rotated view.

  • Interface Builder: What are the UIView's Layout iOS 6/7 Deltas for?
  • Why doesn't UIView.exclusiveTouch work?
  • IOS: How to put some view on top of presented modal view controller?
  • SpringWithDamping for CALayer animations?
  • How do I create a UIView with a transparent circle inside (in swift)?
  • On an iOS single view app, when is the earliest time that self.view.bounds is set correctly?
  • Here is an illustration of the problem (frame property is labeled A, and the visual UIView bounds are labeled B):

    When NOT Rotated

    +------------------+
    |      A == B      |
    +------------------+
    

    When Rotated

    +-----------------+
    |  A        .     |
    |         .   .   |
    |       .       . |
    |     .       .   |
    |   .    B  .     |
    | .       .       |
    |   .   .         |
    |     .           |
    +-----------------+
    

    I want to capture taps that are within the bounds of rect B (the true bounds of UIView, rotated), but NOT when they’re only within rect A (the value of the frame property of UIView) and not B.

    How might I calculate whether a given tap point is within the true bounds/frame/borders of the rotated UIView? Is there a convenience method for this? Or would I need to calculate the coordinates and dimensions of B using my own geometry?

    (If the latter, please include a suggestion so we can make the answer as complete as possible. Thanks!)

    3 Solutions Collect From Internet About “How to use CGRectContainsPoint() with rotated UIView”

    You’re discovering the one fundamental stumbling block that everybody has when they first work for frame and bounds.

    Frame is the smallest possible (non-rotated) rectangle in which a view fits in, taking into account transformation. Meaning, if you were to test for touches, you could log in the available space around the view so long as it was within that smallest possible rectangle.

    For the visual, imagine the blue square is a transformed UIView. The blue border around the view represents it’s frame. Notice how, even though the view is transformed, that it’s frame remains un-transformed and in standard position. The green area represents the areas that are touchable if frame is passed instead of bounds:

    frame

    Bounds, on the other hand, represents the reciever’s rectangle with respect to itself, taking into account transformations, and so testing for points in the view by passing bounds (after a -convertPoint:toView: call) will correctly return whether or not a given touch (point) intersects the view.

    here is code, viewB is target view, viewA is source view that containing the point.

    if(CGRectContainsPoint(viewB.bounds, [viewA convertPoint:point toView:viewB])){
        //...
    }
    

    I came up with this answer as I wanted a full code response explained. If anyone needs the code, for the sake of completeness, this is how I ended up calculating if a view (containerView) is fully contained in another view (view):

    -(BOOL)viewIsFullyContained:(UIView*)view{
        // get the inner rectangle corners in the view coordinate system
        CGPoint upperLeft   = [self.containerView convertPoint:self.containerView.bounds.origin toView:view];
        CGPoint upperRight  = [self.containerView convertPoint:CGPointMake(self.containerView.bounds.origin.x + self.containerView.bounds.size.width,
                                                                       self.containerView.bounds.origin.y)
                                                    toView:view];
        CGPoint lowerLeft   = [self.containerView convertPoint:CGPointMake(self.containerView.bounds.origin.x,
                                                                       self.containerView.bounds.origin.y + self.containerView.bounds.size.height)
                                                    toView:view];
        CGPoint lowerRight  = [self.containerView convertPoint:CGPointMake(self.containerView.bounds.origin.x + self.containerView.bounds.size.width,
                                                                       self.containerView.bounds.origin.y + self.containerView.bounds.size.height)
                                                    toView:view];
        // Check whether all of the corners are fully contained in the view.
        BOOL upperLeftIsContained   = CGRectContainsPoint(view.bounds, upperLeft);
        BOOL upperRightIsContained  = CGRectContainsPoint(view.bounds, upperRight);
        BOOL lowerLeftIsContained   = CGRectContainsPoint(view.bounds, lowerLeft);
        BOOL lowerRightIsContained  = CGRectContainsPoint(view.bounds, lowerRight);
        NSLog(@"Checking for (%i/%i/%i/%i)",upperLeftIsContained,upperRightIsContained,lowerLeftIsContained,lowerRightIsContained);
        return (upperRightIsContained && upperRightIsContained && lowerRightIsContained && lowerLeftIsContained);
    }