Animating UILabel Font Size Change

前端 未结 9 2032
醉话见心
醉话见心 2020-11-30 21:31

I am currently making an application that uses a custom View Controller container. Multiple views are on the screen at one time and when one is tapped, the selected view con

相关标签:
9条回答
  • 2020-11-30 21:34

    If you want to animate the text size from another anchor point, here is the Swift 5 solution:

    How to apply:

    yourLabel.setAnimatedFont(.systemFont(ofSize: 48), duration: 0.2, anchorPointX: 0, anchorPointY: 1)
    

    Extensions:

    extension UILabel {
      /// Animate font size from a given anchor point of the label.
      /// - Parameters:
      ///   - duration: Animation measured in seconds
      ///   - anchorPointX: 0 = left, 0.5 = center, 1 = right
      ///   - anchorPointY: 0 = top, 0.5 = center, 1 = bottom
      func setAnimatedFont(_ font: UIFont, duration: TimeInterval, anchorPointX: CGFloat, anchorPointY: CGFloat) {
        guard let oldFont = self.font else { return }
    
        setAnchorPoint(CGPoint(x: anchorPointX, y: anchorPointY))
        self.font = font
    
        let scaleFactor = oldFont.pointSize / font.pointSize
        let oldTransform = transform
        transform = transform.scaledBy(x: scaleFactor, y: scaleFactor)
        setNeedsUpdateConstraints()
    
        UIView.animate(withDuration: duration) {
          self.transform = oldTransform
          self.layoutIfNeeded()
        }
      }
    }
    
    extension UIView {
      /// Change the anchor point without moving the view's position.
      /// - Parameters:
      ///   - point: The layer's bounds rectangle.
      func setAnchorPoint(_ point: CGPoint) {
        let oldOrigin = frame.origin
        layer.anchorPoint = point
        let newOrigin = frame.origin
    
        let translation = CGPoint(x: newOrigin.x - oldOrigin.x, y: newOrigin.y - oldOrigin.y)
        translatesAutoresizingMaskIntoConstraints = true
        center = CGPoint(x: center.x - translation.x, y: center.y - translation.y)
      }
    }
    
    0 讨论(0)
  • 2020-11-30 21:35

    For 2017 onwards....

    Swift 3.0, 4.0

    UIView.animate(withDuration: 0.5) {
         label.transform = CGAffineTransform(scaleX: 1.1, y: 1.1) //Scale label area
     }
    

    Critical:

    The critical point to avoid blurring is you must begin with the biggest size, and shrink it. Then expand to "1" when needed.

    For quick "pops" (like a highlight animation) it's OK to expand beyond 1 but if you are transitioning between two sizes, make the larger size the "correct" normal one.

    0 讨论(0)
  • 2020-11-30 21:39

    I've created UILabel extension in Swift.

    import UIKit
    
    extension UILabel {
        func animate(font: UIFont, duration: TimeInterval) {
            // let oldFrame = frame
            let labelScale = self.font.pointSize / font.pointSize
            self.font = font
            let oldTransform = transform
            transform = transform.scaledBy(x: labelScale, y: labelScale)
            // let newOrigin = frame.origin
            // frame.origin = oldFrame.origin // only for left aligned text
            // frame.origin = CGPoint(x: oldFrame.origin.x + oldFrame.width - frame.width, y: oldFrame.origin.y) // only for right aligned text
            setNeedsUpdateConstraints()
            UIView.animate(withDuration: duration) {
                //L self.frame.origin = newOrigin
                self.transform = oldTransform
                self.layoutIfNeeded()
            }
        }
    }
    

    Uncomment lines if the label text is left or right aligned.

    0 讨论(0)
  • 2020-11-30 21:39

    I found each of the suggestions here inadequate for these reasons:

    1. They don't actually change the font size.
    2. They don't play well with frame sizing & auto layout.
    3. Their interface is non-trivial and/or doesn't play nice inside animation blocks.

    In order to retain all of these features & still get a smooth animation transition I've combined the transform approach and the font approach.

    The interface is simple. Just update the fontSize property and you'll update the font's size. Do this inside an animation block and it'll animate.

    @interface UILabel(MPFontSize)
    
    @property(nonatomic) CGFloat fontSize;
    
    @end
    

    As for the implementation, there's the simple way, and there's the better way.

    Simple:

    @implementation UILabel(MPFontSize)
    
    - (void)setFontSize:(CGFloat)fontSize {
    
        CGAffineTransform originalTransform = self.transform;
        UIFont *targetFont = [self.font fontWithSize:fontSize];
    
        [UIView animateWithDuration:0 delay:0 options:0 animations:^{
            self.transform = CGAffineTransformScale( originalTransform,
                    fontSize / self.fontSize, fontSize / self.fontSize );
        }                completion:^(BOOL finished) {
            self.transform = originalTransform;
            if (finished)
                self.font = targetFont;
        }];
    }
    
    - (CGFloat)fontSize {
    
        return self.font.pointSize;
    };
    
    @end
    

    Now, the problem with this is that the layout can stutter upon completion, because the view's frame is sized based on the original font all the way until the animation completion, at which point the frame updates to accommodate the target font without animation.

    Fixing this problem is a little harder because we need to override intrinsicContentSize. You can do this either by subclassing UILabel or by swizzling the method. I personally swizzle the method, because it lets me keep a generic fontSize property available to all UILabels, but that depends on some library code I can't share here. Here is how you would go about this using subclassing.

    Interface:

    @interface AnimatableLabel : UILabel
    
    @property(nonatomic) CGFloat fontSize;
    
    @end
    

    Implementation:

    @interface AnimatableLabel()
    
    @property(nonatomic) UIFont *targetFont;
    @property(nonatomic) UIFont *originalFont;
    
    @end
    
    @implementation AnimatableLabel
    
    - (void)setFontSize:(CGFloat)fontSize {
    
        CGAffineTransform originalTransform = self.transform;
        self.originalFont = self.font;
        self.targetFont = [self.font fontWithSize:fontSize];
        [self invalidateIntrinsicContentSize];
    
        [UIView animateWithDuration:0 delay:0 options:0 animations:^{
            self.transform = CGAffineTransformScale( originalTransform,
                    fontSize / self.fontSize, fontSize / self.fontSize );
        }                completion:^(BOOL finished) {
            self.transform = originalTransform;
    
            if (self.targetFont) {
                if (finished)
                    self.font = self.targetFont;
                self.targetFont = self.originalFont = nil;
                [self invalidateIntrinsicContentSize];
            }
        }];
    }
    
    - (CGFloat)fontSize {
    
        return self.font.pointSize;
    };
    
    - (CGSize)intrinsicContentSize {
    
        @try {
            if (self.targetFont)
                self.font = self.targetFont;
            return self.intrinsicContentSize;
        }
        @finally {
            if (self.originalFont)
                self.font = self.originalFont;
        }
    }
    
    @end
    
    0 讨论(0)
  • 2020-11-30 21:44

    For someone who wants to adjust direction of animation

    I have created an extension for UILabel to animate font size change

    extension UILabel {
      func animate(fontSize: CGFloat, duration: TimeInterval) {
        let startTransform = transform
        let oldFrame = frame
        var newFrame = oldFrame
        let scaleRatio = fontSize / font.pointSize
    
        newFrame.size.width *= scaleRatio
        newFrame.size.height *= scaleRatio
        newFrame.origin.x = oldFrame.origin.x - (newFrame.size.width - oldFrame.size.width) * 0.5
        newFrame.origin.y = oldFrame.origin.y - (newFrame.size.height - oldFrame.size.height) * 0.5
        frame = newFrame
    
        font = font.withSize(fontSize)
    
        transform = CGAffineTransform.init(scaleX: 1 / scaleRatio, y: 1 / scaleRatio);
        layoutIfNeeded()
    
        UIView.animate(withDuration: duration, animations: {
          self.transform = startTransform
          newFrame = self.frame
        }) { (Bool) in
          self.frame = newFrame
        }
      }
    

    If you want to adjust direction of animation, use below method and put a suitable anchor point.

    SWIFT

    struct LabelAnimateAnchorPoint {
      // You can add more suitable archon point for your needs
      static let leadingCenterY         = CGPoint.init(x: 0, y: 0.5)
      static let trailingCenterY        = CGPoint.init(x: 1, y: 0.5)
      static let centerXCenterY         = CGPoint.init(x: 0.5, y: 0.5)
      static let leadingTop             = CGPoint.init(x: 0, y: 0)
    }
    
    extension UILabel {
      func animate(fontSize: CGFloat, duration: TimeInterval, animateAnchorPoint: CGPoint) {
        let startTransform = transform
        let oldFrame = frame
        var newFrame = oldFrame
        let archorPoint = layer.anchorPoint
        let scaleRatio = fontSize / font.pointSize
    
        layer.anchorPoint = animateAnchorPoint
    
        newFrame.size.width *= scaleRatio
        newFrame.size.height *= scaleRatio
        newFrame.origin.x = oldFrame.origin.x - (newFrame.size.width - oldFrame.size.width) * animateAnchorPoint.x
        newFrame.origin.y = oldFrame.origin.y - (newFrame.size.height - oldFrame.size.height) * animateAnchorPoint.y
        frame = newFrame
    
        font = font.withSize(fontSize)
    
        transform = CGAffineTransform.init(scaleX: 1 / scaleRatio, y: 1 / scaleRatio);
        layoutIfNeeded()
    
        UIView.animate(withDuration: duration, animations: {
          self.transform = startTransform
          newFrame = self.frame
        }) { (Bool) in
          self.layer.anchorPoint = archorPoint
          self.frame = newFrame
        }
      }
    }
    

    OBJECTIVE-C

    // You can add more suitable archon point for your needs
    #define kLeadingCenterYAnchorPoint         CGPointMake(0.f, .5f)
    #define kTrailingCenterYAnchorPoint        CGPointMake(1.f, .5f)
    #define kCenterXCenterYAnchorPoint         CGPointMake(.5f, .5f)
    #define kLeadingTopAnchorPoint             CGPointMake(0.f, 0.f)
    
    @implementation UILabel (FontSizeAnimating)
    
    - (void)animateWithFontSize:(CGFloat)fontSize duration:(NSTimeInterval)duration animateAnchorPoint:(CGPoint)animateAnchorPoint {
      CGAffineTransform startTransform = self.transform;
      CGRect oldFrame = self.frame;
      __block CGRect newFrame = oldFrame;
      CGPoint archorPoint = self.layer.anchorPoint;
      CGFloat scaleRatio = fontSize / self.font.pointSize;
    
      self.layer.anchorPoint = animateAnchorPoint;
    
      newFrame.size.width *= scaleRatio;
      newFrame.size.height *= scaleRatio;
      newFrame.origin.x = oldFrame.origin.x - (newFrame.size.width - oldFrame.size.width) * animateAnchorPoint.x;
      newFrame.origin.y = oldFrame.origin.y - (newFrame.size.height - oldFrame.size.height) * animateAnchorPoint.y;
      self.frame = newFrame;
    
      self.font = [self.font fontWithSize:fontSize];
      self.transform = CGAffineTransformScale(self.transform, 1.f / scaleRatio, 1.f / scaleRatio);
      [self layoutIfNeeded];
    
      [UIView animateWithDuration:duration animations:^{
        self.transform = startTransform;
        newFrame = self.frame;
      } completion:^(BOOL finished) {
        self.layer.anchorPoint = archorPoint;
        self.frame = newFrame;
      }];
    }
    
    @end
    

    For example, to animate changing label font size to 30, duration 1s from center and scale bigger. Simply call

    SWIFT

    YOUR_LABEL.animate(fontSize: 30, duration: 1, animateAnchorPoint: LabelAnimateAnchorPoint.centerXCenterY)
    

    OBJECTIVE-C

    [YOUR_LABEL animateWithFontSize:30 
                           duration:1 
                 animateAnchorPoint:kCenterXCenterYAnchorPoint];
    
    0 讨论(0)
  • 2020-11-30 21:47

    Swift 3.0 & Swift 4.0

     UIView.animate(withDuration: 0.5, delay: 0.1, options: .curveLinear, animations: { 
    
        label.transform = label.transform.scaledBy(x:4,y:4) //Change x,y to get your desired effect. 
    
        } ) { (completed) in
    
             //Animation Completed      
    
        }
    
    0 讨论(0)
提交回复
热议问题