Search code examples
uiviewcgaffinetransformbounds

Detecting if a CGAffineTransformed view is out of bounds of a screen/UIView


I have a several views that I can drag around, rotate, scale. I want to make it so they can't be drug, rotated or scaled off the screen.

Dragging seems to not be an Issue as I'm not using a Transform to generate the new position and see if that new position would put the view off the screen.

When I rotate or scale I use a CGAffineTransform (CGAffineTransformedRotate or CGAffineTransformScale) and I cant seem to get what the new frame would be without actually applying it to my view.

CGRect  newElementBounds = CGRectApplyAffineTransform(element.bounds, CGAffineTransformScale([[gestureRecognizer view] transform], [gestureRecognizer scale], [gestureRecognizer scale]));


CGRect elementBoundsInSuperView = [element convertRect:newElementBounds toView:[element superview]];

elementBoundsInSuperView is not the Rect that I would Expect it to be, Its way off.

I've also Tried to get the bounds in the SuperView first and then apply the transform to it, and that's not right either.

CGRect elementBoundsInSuperView = [element convertRect:element.bounds toView:[element superview]];

CGRect  newElementBounds = CGRectApplyAffineTransform(newElementBounds, CGAffineTransformScale([[gestureRecognizer view] transform], [gestureRecognizer scale], [gestureRecognizer scale]));

the [gestureRecognizer view] should be the same as element.


Solution

  • I came up with some gesture handlers that work so the view you are manipulatoing does not go off the area you specified. My View pallet was defined by kscreenEditorSpace, 2048.

    The Pan gesture just calls the calcCenterFromXposition:yPosition:fromBoundsInSuperView: method to set its center, if the center falls out of bounds it just adjusts and keeps the element in bounds

    //--------------------------------------------------------------------------------------------------------
    //    handlePanGesture
    //    Description: Called when scrollView got a DoubleFinger DoubleTap Gesture
    //                 We want to Zoom out one ZOOM_STEP.               
    //
    //--------------------------------------------------------------------------------------------------------
    - (void)handlePanGesture:(UIPanGestureRecognizer *)gestureRecognizer {
    
        UIView *element = [gestureRecognizer view];
    
    
        if ([gestureRecognizer state] == UIGestureRecognizerStateBegan ) {
                [[self superview] bringSubviewToFront:self]; 
        }
    
        if ([gestureRecognizer state] == UIGestureRecognizerStateBegan || [gestureRecognizer state] == UIGestureRecognizerStateChanged) {
                //Front and Center Mr Element! 
    
                // Find out where we are going
            CGPoint translation = [gestureRecognizer translationInView:[element superview]];
            CGRect elementBoundsInSuperView = [element convertRect:element.bounds toView:[element superview]];
            CGFloat xPosition = CGRectGetMidX(elementBoundsInSuperView) + translation.x;
            CGFloat yPosition = CGRectGetMidY(elementBoundsInSuperView) + translation.y;
    
            CGPoint newCenter = [self calcCenterFromXposition:xPosition yPosition:yPosition fromBoundsInSuperView:elementBoundsInSuperView];
    
                //Re position ourselves
            [element setCenter:newCenter];
    
                //set the translation back to 0 point
            [gestureRecognizer setTranslation:CGPointZero inView:[element superview]];
            [self setNeedsDisplay];
        }
    
        if ([gestureRecognizer state] == UIGestureRecognizerStateEnded ) {
        }
    
    }
    

    So the handle Pinch and Rotation are pretty Similar. instead of calling the calc Center Directly, we call another method to help determine if we are in bounds.

    //--------------------------------------------------------------------------------------------------------
    //    handlePinchGesture
    //    Description: Called when scrollView got a DoubleFinger DoubleTap Gesture
    //                 We want to Zoom out one ZOOM_STEP.               
    //
    //--------------------------------------------------------------------------------------------------------
    
    - (void)handlePinchGesture:(UIPinchGestureRecognizer *)gestureRecognizer {
        if ([gestureRecognizer state] == UIGestureRecognizerStateBegan ) {
    
            [[self superview] bringSubviewToFront:self]; 
        }
    
        if ([gestureRecognizer state] == UIGestureRecognizerStateBegan || [gestureRecognizer state] == UIGestureRecognizerStateChanged) {
    
    
    
            BOOL aSelectedElementOffscreen = FALSE;
                if ([[gestureRecognizer view] pinchOffScreen:[gestureRecognizer scale]]) {
                    aSelectedElementOffscreen = TRUE;
                }
    
            if (!aSelectedElementOffscreen) {
    
                [gestureRecognizer view].transform = CGAffineTransformScale([[gestureRecognizer view] transform], [gestureRecognizer scale], [gestureRecognizer scale]);
    
                    //Update ourself
                [self contentSizeChanged];  
         }
                 [gestureRecognizer setScale:1];
        }
    
        if ([gestureRecognizer state] == UIGestureRecognizerStateEnded) {
    
            if (![self becomeFirstResponder]) {
                NSLog(@" %@ - %@ - couldn't become first responder", INTERFACENAME, NSStringFromSelector(_cmd) );
                return;
            }
        }
    }
    }
    

    Pinch Off Screen Method

     //--------------------------------------------------------------------------------------------------------
    //    pinchOffScreen
    //    Description: Called to see if the Pinch Gesture will cause element to go off screen Gesture
    //
    //--------------------------------------------------------------------------------------------------------
    
    - (BOOL)pinchOffScreen:(CGFloat)scale {
    
        // Save Our Current Transform incase we go off Screen
    CGAffineTransform elementOrigTransform = [self transform];
        //Apply our Transform
    self.transform = CGAffineTransformScale([self transform],  scale,  scale);
        // Get our new Bounds in the SuperView
    CGRect newElementBoundsInSuperView = [self convertRect:self.bounds toView:[self superview]];
    
        //Find out where we are in the SuperView 
    CGFloat xPosition = CGRectGetMidX( newElementBoundsInSuperView);
    CGFloat yPosition = CGRectGetMidY( newElementBoundsInSuperView);
    
        //See if we are off the Screen
    BOOL offScreen = [self calcOffEditorFromXposition:xPosition yPosition:yPosition fromBoundsInSuperView: newElementBoundsInSuperView];
    
        // We just wanted to Check. Revert to where we were
    self.transform = elementOrigTransform;
    return offScreen;
    
    }
    

    The Handle Rotation is Similar to Pinch, we have a helper method to see if we rotated off screen.

    //--------------------------------------------------------------------------------------------------------
    //    handleRotationGesture
    //    Description: Called when we get a rotation gesture
    //                 toggle the scroll/zoom lock
    //
    //--------------------------------------------------------------------------------------------------------
    
    - (void) handleRotationGesture:(UIRotationGestureRecognizer *)gestureRecognizer{
        UIView *element = [gestureRecognizer view];
    
        if ([gestureRecognizer state] == UIGestureRecognizerStateBegan ) {
                    [[self superview] bringSubviewToFront:self]; 
        }
    
        if ([gestureRecognizer state] == UIGestureRecognizerStateBegan || [gestureRecognizer state] == UIGestureRecognizerStateChanged) {
    
    
            BOOL aSelectedElementOffscreen = FALSE;
                if ([element rotateOffScreen:[gestureRecognizer rotation]]) {
                    aSelectedElementOffscreen = TRUE;
                }
    
            if (!aSelectedElementOffscreen) {
    
                [gestureRecognizer view].transform = CGAffineTransformRotate([element transform], [gestureRecognizer rotation]);
    
    
                    //Update ourself
                [self contentSizeChanged];  
    
            }
            [gestureRecognizer setRotation:0];
        }
        if ([gestureRecognizer state] == UIGestureRecognizerStateEnded) {
        }
    }
    }
    

    Rotate Off Screen method

    //--------------------------------------------------------------------------------------------------------
    //    rotateOffScreen
    //    Description: Called to see if the Rotation Gesture will cause element to go off screen Gesture
    //
    //--------------------------------------------------------------------------------------------------------
    
    - (BOOL)rotateOffScreen:(CGFloat)rotation {
    
        // Save Our Current Transform incase we go off Screen
    CGAffineTransform elementOrigTransform = [self transform];
        //Apply our Transform
    self.transform = CGAffineTransformRotate([self transform], rotation);
        // Get our new Bounds in the SuperView
    CGRect newElementBoundsInSuperView = [self convertRect:self.bounds toView:[self superview]];
    
        //Find out where we are in the SuperVire 
    CGFloat xPosition = CGRectGetMidX( newElementBoundsInSuperView);
    CGFloat yPosition = CGRectGetMidY( newElementBoundsInSuperView);
    
        //See if we are off the Screen
    BOOL offScreen = [self calcOffEditorFromXposition:xPosition yPosition:yPosition fromBoundsInSuperView: newElementBoundsInSuperView];
    
        // We just wanted to Check. Revert to where we were
    self.transform = elementOrigTransform;
    
    return offScreen;
    
    }
    

    Calc Screen Positioning Helper Methods

    #pragma mark -
    #pragma mark === Calc Screen Positioning  ===
    #pragma mark
    //--------------------------------------------------------------------------------------------------------
    //    calcCenterFromXposition: yPosition: fromBoundsInSuperView:
    //    Description: calculate the center point in the element's super view from x, y
    //
    //--------------------------------------------------------------------------------------------------------
    -(CGPoint) calcCenterFromXposition: (CGFloat) xPosition yPosition:(CGFloat) yPosition fromBoundsInSuperView:(CGRect) elementBoundsInSuperView{
    
    
        // Ge the Height/width based on SuperView Bounds
    CGFloat elementWidth = CGRectGetWidth(elementBoundsInSuperView); 
    CGFloat elementHeight = CGRectGetHeight(elementBoundsInSuperView);
    
        //Determine our center.x from the new x
    if (xPosition < elementWidth/2) {
        xPosition = elementWidth/2;
    } else if (xPosition + elementWidth/2  > kscreenEditorSpace) {
        xPosition = kscreenEditorSpace - elementWidth/2;
    }   
        //Determine our center.y from the new y
    if (yPosition < elementHeight/2) {
        yPosition = elementHeight/2;
    } else if (yPosition + elementHeight/2  > kscreenEditorSpace) {
        yPosition = kscreenEditorSpace - elementHeight/2;
    } 
    return (CGPointMake(xPosition, yPosition));
    }
    
    //--------------------------------------------------------------------------------------------------------
    //    calcOffEditorFromXposition: yPosition: fromBoundsInSuperView:
    //    Description: Determine if moving the element to x, y will it be off the editor screen
    //
    //--------------------------------------------------------------------------------------------------------
    -(BOOL) calcOffEditorFromXposition: (CGFloat) xPosition yPosition:(CGFloat) yPosition fromBoundsInSuperView:(CGRect) elementBoundsInSuperView{
    
    BOOL offScreen = NO;
    
        // Ge the Height/width based on SuperView Bounds
    CGFloat elementWidth = CGRectGetWidth(elementBoundsInSuperView); 
    CGFloat elementHeight = CGRectGetHeight(elementBoundsInSuperView);
    
        // Off Screen on Left
    if (xPosition < elementWidth/2) {
        offScreen = YES;
    } 
        //Off Screen Right
    if (xPosition + elementWidth/2  > kscreenEditorSpace) {
        offScreen = YES;;
    }
    
        // Off Screen Top
    if (yPosition < elementHeight/2) {
        offScreen = YES;
    } 
    
        //Off Screen Bottom
    if (yPosition + elementHeight/2  > kscreenEditorSpace) {
        offScreen = YES;
    }
    
    return (offScreen);
    }