3

I want to have a movable/scalable/rotable view inside another view. The inner view can go outside the outer view's frame but I want to keep part of it inside the outer one so the inner view isn't lost.

I simplified the problem in this xcode project https://github.com/nextorlg/Intersection.

If the inner view was only movable and scalable the problem would be already solved but when the inner view rotates this solution it is not good because the frame contains the view but it is not the view itself.

Every transformation performed to the inner view I use this function to validate the new view position, if it is not valid I revert the last transformation ( this is the movable view code https://github.com/nextorlg/Intersection/blob/master/intersec/MovableView.m )

-(BOOL) validInset {
    CGRect outerLimit = CGRectMake(0, 0, self.superview.frame.size.width, self.superview.frame.size.height);
    CGRect intersectionRect = CGRectIntersection(self.frame, outerLimit);
    NSLog(@"self.frame:%f,%f,%f,%f", self.frame.origin.x, self.frame.origin.y, self.frame.size.width, self.frame.size.height);
    NSLog(@"outer.frame:%f,%f,%f,%f", outerLimit.origin.x, outerLimit.origin.y, outerLimit.size.width, outerLimit.size.height);
    NSLog(@"intersec.frame:%f,%f,%f,%f", intersectionRect.origin.x, intersectionRect.origin.y, intersectionRect.size.width, intersectionRect.size.height);
    NSLog(@"========================");
    if ( CGRectIsNull(intersectionRect) ||
         intersectionRect.size.width < INSET ||
         intersectionRect.size.height < INSET ) {
         return NO;
    }
    else {
        return YES;
    }
}

The question is, how can I be sure the inner view it is not lost behind the outer view when the first is rotated some (45 for example) degrees and dragged to a corner?

One comment, I want to keep just some pixels inside the outer view because the inner view can be bigger (scaled) than the outer one.

I recommend you to download and run the project to understan better the problem, it is difficult to understand it just reading this.

Thank you!

Nextorlg
  • 794
  • 1
  • 8
  • 16

1 Answers1

1

As you stated, your method will only work fine when the inner view is not rotated.

The easiest method I am aware of for solving your problem is simply to test if the center or a vertex of one of the views is inside the other one.

-(BOOL) validInset {
    // Get the vertices of A (outerLimit)
    CGRect outerLimit = self.superview.bounds;
    CGPoint pointA[] = {
        CGPointMake(outerLimit.origin.x,   outerLimit.origin.y    ),
        CGPointMake(outerLimit.size.width, outerLimit.origin.y    ),
        CGPointMake(outerLimit.size.width, outerLimit.size.height ),
        CGPointMake(outerLimit.origin.x,   outerLimit.size.height )};
    // Adjust outerLimit's borders
    pointA[0].x += INSET, pointA[0].y += INSET;
    pointA[1].x -= INSET, pointA[1].y += INSET;
    pointA[2].x -= INSET, pointA[2].y -= INSET;
    pointA[3].x += INSET, pointA[3].y -= INSET;
    // Get the vertices of B (innerView)
    CGRect innerView = self.bounds;
    CGPoint pointB[] = {
        CGPointMake(innerView.origin.x,   innerView.origin.y    ),
        CGPointMake(innerView.size.width, innerView.origin.y    ),
        CGPointMake(innerView.size.width, innerView.size.height ),
        CGPointMake(innerView.origin.x,   innerView.size.height )};
    // Test if the center of B is inside A or vice versa
    CGPoint center, converted;
    center = CGPointMake(pointB[0].x + (pointB[1].x - pointB[0].x)/2, pointB[0].y + (pointB[2].y - pointB[0].y)/2);
    if( converted = [self convertPoint:center toView:self.superview],
        converted.x >= pointA[0].x && 
        converted.x <= pointA[1].x &&
        converted.y >= pointA[0].y &&
        converted.y <= pointA[2].y ) return YES;
    center = CGPointMake(pointA[0].x + (pointA[1].x - pointA[0].x)/2, pointA[0].y + (pointA[2].y - pointA[0].y)/2);
    if( converted = [self convertPoint:center toView:self.superview],
        converted.x >= pointA[0].x && 
        converted.x <= pointA[1].x &&
        converted.y >= pointA[0].y &&
        converted.y <= pointA[2].y ) return YES;
    // Test if vertices of B are inside A or vice versa
    for (int i = 0; i < 4; i++) {
        if( converted = [self convertPoint:pointB[i] toView:self.superview],
            converted.x >= pointA[0].x && 
            converted.x <= pointA[1].x &&
            converted.y >= pointA[0].y &&
            converted.y <= pointA[2].y ) return YES;
        if( converted = [self.superview convertPoint:pointA[i] toView:self],
            converted.x >= pointB[0].x && 
            converted.x <= pointB[1].x &&
            converted.y >= pointB[0].y &&
            converted.y <= pointB[2].y ) return YES;
    }
    return NO;
}

This code will do the job for dealing with the transformations that you use in your example, but it is not a general purpose answer.

A non iphone-related answer to this question is found here.

And you can also read some background about rectangle and point intersection here.

edit:

For the transformations that can be applied to an UIView, to test the vertices and the center will be enough for most cases.

The two well-known exceptions are:

  • when h1 < h2/2 and w1/2 > w2 (or h2 < h1/2 and w2/2 > w1),
  • when 2w2 < h2/2 and r1 > w2 (or 2w1 < h1/2 and r2 > w1);

where:

  • r = the distance between the center of the UIView and a vertex;
  • w = the length of the shortest side of the UIView;
  • h = the length of the longest side of the UIView.

If you face one of these cases, you should use another method, like the one in the first link.

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
hectr
  • 481
  • 7
  • 15