Scale UIView with the top center as the anchor poi

2019-02-01 01:38发布

问题:

I'm scaling a UIView with CGAffineTransformMakeScale but I want to keep it anchored to it's current top center point as it's scaled.

I've looked into setting view.layer.anchorPoint but I believe I need to use this in conjunction with setting view.layer.position to account for the change in the anchor point.

If anyone could point me in the right direction I'd be very grateful!

回答1:

Take a look at my answer here to understand why your view is moving when you change the anchor point: https://stackoverflow.com/a/12208587/77567

So, start with your view's transform set to identity. When the view is untransformed, find the center of its top edge:

CGRect frame = view.frame;
CGPoint topCenter = CGPointMake(CGRectGetMidX(frame), CGRectGetMinY(frame));

Then the anchor point to the middle of the top edge:

view.layer.anchorPoint = CGPointMake(0.5, 0);

Now the layer's position (or the view's center) is the position of the center of the layer's top edge. If you stop here, the layer will move so that the center of its top edge is where its true center was before changing the anchor point. So change the layer's position to the center of its top edge from a moment ago:

view.layer.position = topCenter;

Now the layer stays in the same place on screen, but its anchor point is the center of its top edge, so scales and rotations will leave that point fixed.



回答2:

Swift 4.2

Scaling an UIView with a specific anchor point while avoiding misplacement:

extension UIView {
    func applyTransform(withScale scale: CGFloat, anchorPoint: CGPoint) {
        layer.anchorPoint = anchorPoint
        let scale = scale != 0 ? scale : CGFloat.leastNonzeroMagnitude
        let xPadding = 1/scale * (anchorPoint.x - 0.5)*bounds.width
        let yPadding = 1/scale * (anchorPoint.y - 0.5)*bounds.height
        transform = CGAffineTransform(scaleX: scale, y: scale).translatedBy(x: xPadding, y: yPadding)
    }
}

So to shrink a view to half of its size with its top center as anchor point:

view.applyTransform(withScale: 0.5, anchorPoint: CGPoint(x: 0.5, y: 0))


回答3:

It's an old question, but I faced the same problem and took me a lot of time to get to the desired behavior, expanding a little bit the answer from @Rob Mayoff (I found the solution thanks to his explanation). I had to show a floating-view scaling from a point touched by the user. Depending on screen coords, It could scale down, right, left, up or from the center.

The first thing to do is to set the center of our floating view in the touch point (It has to be the center, 'cause as Rob explanation):

-(void)onTouchInView:(CGPoint)theTouchPosition
{
self.floatingView.center = theTouchPosition;
....
}

The next issue is to setup the anchor point of the layer, depending on what we want to do (I measured the point relative to the screen frame, and determined where to scale it):

switch (desiredScallingDirection) {
    case kScaleFromCenter:
    {
        //SCALE FROM THE CENTER, SO
        //SET ANCHOR POINT IN THE VIEW'S CENTER
        self.floatingView.layer.anchorPoint=CGPointMake(.5, .5);
    }
        break;
    case kScaleFromLeft:
        //SCALE FROM THE LEFT, SO
        //SET ANCHOR POINT IN THE VIEW'S LEFT-CENTER
        self.floatingView.layer.anchorPoint=CGPointMake(0, .5);
        break;
    case kScaleFromBottom:
        //SCALE FROM BOTTOM, SO
        //SET ANCHOR POINT IN THE VIEW'S CENTER-BOTTOM
        self.floatingView.layer.anchorPoint=CGPointMake(.5, 1);
        break;
    case kScaleFromRight:
        //SCALE FROM THE RIGHT, SO
        //SET ANCHOR POINT IN THE VIEW'S RIGHT-CENTER
        self.floatingView.layer.anchorPoint=CGPointMake(1, .5);
        break;
    case kScallingFromTop:
        //SCALE FROM TOP, SO
        //SET ANCHOR POINT IN THE VIEW'S CENTER-TOP
        self.floatingView.layer.anchorPoint=CGPointMake(.5, 0);
        break;
    default:
        break;

}

Now, all it's easy. Depending on the effect you want to give to the animation (The idea was a bouncing scaling from 0 to 100%):

//SETUP INITIAL SCALING TO (ALMOST) 0%
CGAffineTransform t=CGAffineTransformIdentity;
t=CGAffineTransformScale(t, .001, .001);
self.floatingView.transform=t;
[UIView animateWithDuration:.2
                      delay:0
                    options:UIViewAnimationOptionCurveEaseInOut
                 animations:^{
                     //BOUNCE A LITLE UP!
                     CGAffineTransform t = CGAffineTransformMakeScale(1.1,1.1);
                     self.floatingView.transform=t;
                 } completion:^(BOOL finished) {
                     [UIView animateWithDuration:.1
                                      animations:^{
                                          //JUST A LITTLE DOWN
                                          CGAffineTransform t = CGAffineTransformMakeScale(.9,.9);
                                          self.floatingView.transform = t;
                                      } completion:^(BOOL finished) {
                                          [UIView animateWithDuration:.05
                                                           animations:^{
                                                               //AND ALL SET
                                                               self.floatingView.transform  = CGAffineTransformIdentity;
                                                           }
                                           completion:^(BOOL finished) {
                                               //ALL SET
                                           }];
                     }];
                 }];

That's it. As you can see, the main thing is to setup correctly the view's position and anchor point. Then, it's all piece of cake! Regards, and let the code be with you!