22

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

Meet Doshi
  • 4,241
  • 10
  • 40
  • 81
Vad
  • 3,658
  • 8
  • 46
  • 81
  • I think what you are looking for is the layer's [anchorPoint](https://developer.apple.com/library/mac/documentation/GraphicsImaging/Reference/CALayer_class/Introduction/Introduction.html#jumpTo_4) property. – nrj Nov 19 '13 at 22:26

3 Answers3

10

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?

Segev
  • 19,035
  • 12
  • 80
  • 152
  • 1
    This is not what I am looking for. I'll come down to exactly what I need: find cathetus A and B knowing hypotenuse and one point (x,y) for right angle. – Vad Dec 16 '13 at 18:22
  • 1
    I don't understand. You promoted your project but did not answer my question and half a bounty was given to you. Is this a common practice on SO? – Vad Dec 18 '13 at 02:56
  • 7
    I don't understand. I spent my own time writing a detailed answer and you and you are acting like I owe you something with that ungrateful comment. Is that common practice on earth? YES – Segev Dec 18 '13 at 07:12
  • 1
    Sha: sorry for my unpleasant response. Yes, thank you for your time and interesting solution. It deserves to be here. I can re-post my question I guess if I need additional answers. – Vad Dec 18 '13 at 14:49
1

Instead of trying to change the frame, adjust the scale property of your view according to your gestures. This should properly resize your view without interfering with rotation.

Patrick Goley
  • 5,397
  • 22
  • 42
  • 3
    Thanks but I am not changing the frame at all. I change the bounds off the center because frame does not work for transformed views. Also, scaling would affect subviews which I do not want. All I want is to calculate WIDTH and HEIGHT based on how far user dragged. Knowing that I could change the bounds correctly and modify the center. – Vad Dec 09 '13 at 19:06
  • I see your problem. What does frame print out as when the view is rotated? Is there any reason you can't reassign the same frame with new dimensions? Does this reset the rotation? – Patrick Goley Dec 09 '13 at 19:18
  • 2
    After a CGAffineTransform, the property frame of a UIView is simply no longer defined. The docs say "Warning: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored." – Vad Dec 09 '13 at 19:20
  • 1
    ok. Then try in your gesture method: undo the rotation, do the resize, and reapply the rotation. – Patrick Goley Dec 09 '13 at 19:24
  • 2
    While that makes sense I got stuck with correct calculation for new bounds. I know center point, finger touch location and initial bounds but how to derive new bounds using these known values is my problem because the view is now rotated and I do not know by how much to increase/decrease width/height. – Vad Dec 09 '13 at 19:33
  • 1
    makes sense, try this: put the gesture recognizer in the super view of the resizing view. in the gesture method, perform a hit test to see if the gesture is touching a resizing view. get that resizing view and do the resizing calculations based on the gesture's movement in the super view, as opposed to the resizing one. – Patrick Goley Dec 09 '13 at 19:48
  • 1
    I think I am looking for solution which comes down to finding correct formula to calculate new values for width and height based on known values (see code above). I am only lacking enough knowledge of how to do it when view is rotated. I wouldn't like to change architecture of views to accomplish this because there are other corners which successfully rotate/drag/resize proportionally my view. – Vad Dec 09 '13 at 20:20
  • Actually you don't have to calculate that, Check my answer above. – Segev Dec 16 '13 at 10:00
1

Embed your view into a container view. Then transform the container, leaving the draggable view intact. Use the same logic you've used for the unrotated view.

Bartosz Ciechanowski
  • 10,293
  • 5
  • 45
  • 60
  • 1
    Thank you but my question is how to calculate new bounds of a rotated view knowing a new touch point. Sorry if it is not very clear. – Vad Dec 15 '13 at 21:32