Odrakir
Odrakir

Reputation: 4254

Same CGAffineTransform different anchor

I have 1 view with 2 subviews. One of them being 10 times bigger than the other one. I have a gesture recognizer for the big one (which is on top).

I want to be able to scale the big one with the pinch gesture from an anchor point between the fingers. And I want the little one to make that same transform from the same global position anchor point but without changing its own anchor point.

Hope I explain myself. Here is the code:

- (void)twoFingerPinch:(UIPinchGestureRecognizer *)gestureRecognizer
{    
    //this changes the anchor point of "big" without moving it
    [self adjustAnchorPointForGestureRecognizer:gestureRecognizer];

    if ([gestureRecognizer state] == UIGestureRecognizerStateBegan || [gestureRecognizer state] == UIGestureRecognizerStateChanged) {    

        CGAffineTransform transform = CGAffineTransformScale([[gestureRecognizer view] transform], [gestureRecognizer scale], [gestureRecognizer scale]);
        float scale = sqrt(transform.a*transform.a+transform.c*transform.c);

        //this transforms "big"
        [gestureRecognizer view].transform = transform;

        //anchor point location in little view
        CGPoint pivote = [gestureRecognizer locationInView:little];

        CGAffineTransform transform_t = CGAffineTransformConcat(CGAffineTransformMakeTranslation(-pivote.x, -pivote.y), transform);
        transform_t = CGAffineTransformConcat(transform_t, CGAffineTransformMakeTranslation(pivote.x, pivote.y));

        little.transform = transform_t;
    }
    [gestureRecognizer setScale:1];
}

But this is not working, the little view keeps jumping around and goes crazy.

EDIT: More info.

Ok, this is the diagram: diagram

The red square is the big view, the dark one is the little one. The dotted square is the main view.

The line: [self adjustAnchorPointForGestureRecognizer:gestureRecognizer]; changes the big views anchor point to the center of the pinch gesture. That works.

As I scale the big view, the small view should scale the same amount and move so it's centered in the big view as it is now. That is, it should scale with the same anchor point as the big view.

I would like to keep those transforms to the little view in a CGAffineTransform, if possible.

Upvotes: 2

Views: 2469

Answers (2)

Odrakir
Odrakir

Reputation: 4254

Ok, I finally found it. I don't know if it's the better solution, but it works.

- (void)twoFingerPinch:(UIPinchGestureRecognizer *)gestureRecognizer
{    
    [self adjustAnchorPointForGestureRecognizer:gestureRecognizer];

    if ([gestureRecognizer state] == UIGestureRecognizerStateBegan || [gestureRecognizer state] == UIGestureRecognizerStateChanged) {    

        CGAffineTransform transform = CGAffineTransformScale([[gestureRecognizer view] transform], [gestureRecognizer scale], [gestureRecognizer scale]);
        float scale = sqrt(transform.a*transform.a+transform.c*transform.c);
        if((scale>0.1)&&(scale<20)) {

            [gestureRecognizer view].transform = transform;

            CGPoint anchor = [gestureRecognizer locationInView:little];
            anchor = CGPointMake(anchor.x - little.bounds.size.width/2, anchor.y-little.bounds.size.height/2);

            CGAffineTransform affineMatrix = little.transform;
            affineMatrix = CGAffineTransformTranslate(affineMatrix, anchor.x, anchor.y);
            affineMatrix = CGAffineTransformScale(affineMatrix, [gestureRecognizer scale], [gestureRecognizer scale]);
            affineMatrix = CGAffineTransformTranslate(affineMatrix, -anchor.x, -anchor.y);        
            little.transform = affineMatrix;    

            [eagleView setTransform:little.transform];    
            [gestureRecognizer setScale:1];
        }

    }
}

That eaglView line, is the real reason why I needed a CGAffineTransform and I couldn't change the anchor. I'm sending it to OpenGL to change the model view transform matrix.

Now it works perfectly with 3 transforms (rotate, scale, translate) at the same time through user feedback.

EDIT

Just a little note: It seems that when I move the view too fast, the eaglView and the UIView get out of sync. So I don't apply the transforms to the UIViews directly, I apply them with the info out of the eaglView. Like this:

- (void)twoFingerPinch:(UIPinchGestureRecognizer *)gestureRecognizer
{    
    [self adjustAnchorPointForGestureRecognizer:gestureRecognizer];

    if ([gestureRecognizer state] == UIGestureRecognizerStateBegan || [gestureRecognizer state] == UIGestureRecognizerStateChanged) {    

        CGAffineTransform transform = CGAffineTransformScale([[gestureRecognizer view] transform], [gestureRecognizer scale], [gestureRecognizer scale]);
        float scale = sqrt(transform.a*transform.a+transform.c*transform.c);
        if((scale>0.1)&&(scale<20)) {

            //[gestureRecognizer view].transform = transform;

            CGPoint anchor = [gestureRecognizer locationInView:little];
            anchor = CGPointMake(anchor.x - little.bounds.size.width/2, anchor.y-little.bounds.size.height/2);

            CGAffineTransform affineMatrix = little.transform;
            affineMatrix = CGAffineTransformTranslate(affineMatrix, anchor.x, anchor.y);
            affineMatrix = CGAffineTransformScale(affineMatrix, [gestureRecognizer scale], [gestureRecognizer scale]);
            affineMatrix = CGAffineTransformTranslate(affineMatrix, -anchor.x, -anchor.y);        
            //little.transform = affineMatrix;    

            [eagleView setTransform:affineMatrix];    
            [gestureRecognizer setScale:1];

            CGAffineTransform transform = CGAffineTransformMakeRotation(eaglView.myRotation*M_PI/180);
            transform = CGAffineTransformConcat(CGAffineTransformMakeScale(eaglView.myScale, eaglView.myScale), transform);
            transform = CGAffineTransformConcat(transform, CGAffineTransformMakeTranslation(eaglView.myTranslate.x, -eaglView.myTranslate.y));    
            little.transform = transform;
            big.transform = transform;
        }

    }
}

Upvotes: 2

tarmes
tarmes

Reputation: 15442

To scale the smaller view using the center of the pinch as the anchor point then you'll need to calculate the new position by hand:

CGRect frame = little.frame; // Returns the frame based on the current transform
frame.origin.x = (frame.origin.x - pivot.x) * gestureRecognizer.scale;
frame.origin.y = (frame.origin.y - pivot.y) * gestureRecognizer.scale;
frame.width = frame.width * gestureRecognizer.scale;
frame.height = frame.height * gestureRecognizer.scale;

Then, update the transform. Personally I would do this based on the view's real position rather than transforming the current transform - I find it easier to think about. So for example:

little.transform = CGAffineTransformIndentity; // Remove the current transform
CGRect orgFrame = little.frame
CGFloat scale = frame.width / orgFrame.size.width;
CGAffineTransform t = CGAffineTransformMakeScale(scale, scale);
t = CGAffineTransformConcat(t, CGAffineTransformMakeTranslation(newFrame.origin.x - frame.origin.x, newFrame.origin.y - frame.origin.y));
little.transform = t;

Note that I've just typed in the code off the top of my head to give you and idea. It'll need testing and debugging!

Also, some of that code can be removed if you use the scale value based on the original pinch rather than resetting it each time and then transforming the transforms.

Tim

Upvotes: 0

Related Questions