Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Resize UIView While It Is Rotated with Transform

Tags:

When my UIView is rotated with transform property (CGAffineTransformMakeRotation), I need to drag one of its corners, say bottom right, to resize it. During this process when user drags the corner, the view's corner must follow user's finger and resize the box by increasing 2 of the sides (right and bottom size for bottom right corner dragging).

It is not so difficult to resize UIView when you can use frame and touch locations when box is not transformed. For example, I would remember initial frame and touch in UIPanGestureRecognizer handler attached to this resizable view on StateBegan, and on StateChanged I would calculate the touch point X, Y difference from initial touch and add those values to initial frame width and height.

However, frame is be reliable for rotated UIView when transform is applied. Thus I can only rely on bounds and center. I created this code but it almost works: I can only enlarge the view proportionally in all 4 directions, not one.

- (void)handleResizeGesture:(UIPanGestureRecognizer *)recognizer {
    CGPoint touchLocation = [recognizer locationInView:self.superview];
    CGPoint center = self.center;

    switch (recognizer.state) {
        case UIGestureRecognizerStateBegan: {
            deltaAngle = atan2f(touchLocation.y - center.y, touchLocation.x - center.x) - CGAffineTransformGetAngle(self.transform);
            initialBounds = self.bounds;
            initialDistance = CGPointGetDistance(center, touchLocation);
            initialLocation = touchLocation;
            if ([self.delegate respondsToSelector:@selector(stickerViewDidBeginRotating:)]) {
                [self.delegate stickerViewDidBeginRotating:self];
            }
            break;
        }

        case UIGestureRecognizerStateChanged: {

            CGFloat scale = CGPointGetDistance(center, touchLocation)/initialDistance;
            CGFloat minimumScale = self.minimumSize/MIN(initialBounds.size.width, initialBounds.size.height);
            scale = MAX(scale, minimumScale);
            CGRect scaledBounds = CGRectScale(initialBounds, scale, scale);
            self.bounds = scaledBounds;

            [self setNeedsDisplay];

            if ([self.delegate respondsToSelector:@selector(stickerViewDidChangeRotating:)]) {
                [self.delegate stickerViewDidChangeRotating:self];
            }
            break;
        }

        case UIGestureRecognizerStateEnded:
            if ([self.delegate respondsToSelector:@selector(stickerViewDidEndRotating:)]) {
                [self.delegate stickerViewDidEndRotating:self];
            }
            break;

        default:
            break;
    }
}

Image below shows the rotated view with known values (A, B, x, y, N, M, N-M distance):

enter image description here

like image 478
Vad Avatar asked Nov 19 '13 22:11

Vad


1 Answers

Nice question. I've just created class that uses something similar. In my class you can change the scale of UIViewA and it will change the scale of UIViewB while keeping the same ratio.

Check the small white square on top left:

enter image description here

The class can be found here: https://github.com/sSegev/SSUIViewMiniMe

What you are asking is a bit different. I'm changing the total size of the square while you want to change only the size that is relative to one of the corners based on how far the user dragged it.

Because you can't tap on a button while it's animating (well, you can but the animation is just eye candy and the view is already in it's final state) I used CABasicAnimation which made it easier.

- (void)viewDidAppear:(BOOL)animated
{
        [super viewDidAppear:animated];
        //Rotate the UIView            
    if ([_myRedView.layer animationForKey:@"SpinAnimation"] == nil)
        {
            CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
            animation.fromValue = [NSNumber numberWithFloat:0.0f];
            animation.toValue = [NSNumber numberWithFloat: 2*M_PI];
            animation.duration = 50.0f;
            animation.repeatCount = INFINITY;
            [self.myRedView.layer addAnimation:animation forKey:@"SpinAnimation"];
        }
}

 // That's the main chunk of code:

- (void)dragBegan:(UIControl *)c withEvent:ev
{
    UITouch *touch = [[ev allTouches] anyObject];
    CGPoint touchPoint = [touch locationInView:_myRedView];
    NSLog(@"Touch x : %f y : %f", touchPoint.x, touchPoint.y);
    if(_myRedView.width/2<touchPoint.x && _myRedView.height/2<touchPoint.y) //Checks for right bottom corner
        {
            _myRedView.width =touchPoint.x;
            _myRedView.height = touchPoint.y;
            dragButton.frame = CGRectMake(0, 0, _myRedView.width, _myRedView.height);
        }
}

I'm sure that some tweaking is needed but right now it looks like this:

enter image description here *

Pretty cool for just 6 lines of code ha?

like image 182
Segev Avatar answered Oct 15 '22 20:10

Segev