73

我目前正在制作一个使用自定义 View Controller 容器的应用程序。一次在屏幕上显示多个视图,当点击一个时,选定的视图控制器将动画到全屏。这样做时,选定的视图控制器子视图也会缩放(框架、字体大小等)。虽然,UILabel 的字体属性不可动画化,从而导致问题。我尝试了多种解决方案,但都非常糟糕。

我尝试过的解决方案是:

  1. 截取大视图并为更改设置动画(类似于 Flipboard 的做法)
  2. 使用 transform 属性制作动画
  3. 缩小 UIScrollView 并在全屏时将其放大。
  4. 将 adjustsFontSizeToFitWidth 设置为 YES 并在动画之前设置 fontSize

到目前为止,一个是最好的解决方案,但我对此并不满意。

如果有人有任何使用 [UIView animate..] 平滑动画的 UILabel 替代品,我正在寻找其他建议。

这是一个很好的例子,类似于我希望我的 UILabel 做的事情:http: //www.cocoawithlove.com/2010/09/zoomingviewcontroller-to-animate-uiview.html

编辑:此代码有效

// Load View

self.label = [[UILabel alloc] init];
self.label.text = @"TEXT";
self.label.font = [UIFont boldSystemFontOfSize:20.0];
self.label.backgroundColor = [UIColor clearColor];

[self.label sizeToFit];

[self.view addSubview:self.label];

// Animation

self.label.font = [UIFont boldSystemFontOfSize:80.0];
self.label.transform = CGAffineTransformScale(self.label.transform, .25, .25);
[self.label sizeToFit];

[UIView animateWithDuration:1.0 animations:^{
    self.label.transform = CGAffineTransformScale(self.label.transform, 4.0, 4.0);
    self.label.center = self.view.center;
} completion:^(BOOL finished) {

    self.label.font = [UIFont boldSystemFontOfSize:80.0]; 
    self.label.transform = CGAffineTransformScale(self.label.transform, 1.0, 1.0);

    [self.label sizeToFit];

}];
4

9 回答 9

65

你可以改变你UILabel的动画的大小和字体,如下所示..这里我只是举了一个例子,说明如何UILabel用变换动画改变字体..

    yourLabel.font = [UIFont boldSystemFontOfSize:35]; // set font size which you want instead of 35
    yourLabel.transform = CGAffineTransformScale(yourLabel.transform, 0.35, 0.35); 
    [UIView animateWithDuration:1.0 animations:^{
        yourLabel.transform = CGAffineTransformScale(yourLabel.transform, 5, 5);
    }];
于 2013-01-24T05:32:28.860 回答
46

从 2017 年开始......

斯威夫特 3.0、4.0

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

危急:

避免模​​糊的关键点是你必须从最大的尺寸开始,然后缩小它。然后在需要时扩展为“1”。

对于快速“弹出”(如高亮动画),可以扩展到 1 以上,但如果您在两种尺寸之间转换,请将较大的尺寸设为“正确”的正常尺寸

于 2017-05-30T10:04:26.353 回答
27

UILabel在 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()
        }
    }
}

如果标签文本左对齐或右对齐,请取消注释行。

于 2016-09-09T15:35:07.897 回答
19

您还可以使用具有 fontSize 作为动画属性的 CATextLayer。

let startFontSize: CGFloat = 20
let endFontSize: CGFloat = 80

let textLayer = CATextLayer()
textLayer.string = "yourText"
textLayer.font = yourLabel.font.fontName as CFTypeRef?
textLayer.fontSize = startFontSize
textLayer.foregroundColor = UIColor.black.cgColor
textLayer.contentsScale = UIScreen.main.scale //for some reason CATextLayer by default only works for 1x screen resolution and needs this line to work properly on 2x, 3x, etc. ...
textLayer.frame = parentView.bounds
parentView.layer.addSublayer(textLayer)

//animation:
let duration: TimeInterval = 1
textLayer.fontSize = endFontSize //because upon completion of the animation CABasicAnimation resets the animated CALayer to its original state (as opposed to changing its properties to the end state of the animation), setting fontSize to endFontSize right BEFORE the animation starts ensures the fontSize doesn't jump back right after the animation.
let fontSizeAnimation = CABasicAnimation(keyPath: "fontSize")
fontSizeAnimation.fromValue = startFontSize
fontSizeAnimation.toValue = endFontSize
fontSizeAnimation.duration = duration
fontSizeAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
textLayer.add(fontSizeAnimation, forKey: nil)

我在我的项目中使用它:https ://github.com/yinanq/AngelListJobs

此动画保持字体左上对齐(与 CGAffineTransformScale 从中心缩放标签不同),赞成或反对取决于您的需要。CATextLayer 的一个缺点是 CALayers 不适用于自动布局约束动画(我碰巧需要并通过制作一个仅包含 CATextLayer 并为其约束设置动画的 UIView 来解决它)。

于 2017-02-05T01:53:52.933 回答
10

对于那些不寻找转换但实际值变化的人:

UIView.transition(with: label, duration: 0.25, options: .transitionCrossDissolve, animations: {
    self.label.font = UIFont.systemFont(ofSize: 15)
}) { isFinished in }

在此处输入图像描述

于 2019-03-09T10:17:25.250 回答
7

对于想要调整动画方向的人

我创建了一个扩展UILabel来动画字体大小变化

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
    }
  }

如果要调整动画的方向,请使用以下方法并放置合适的锚点。

迅速

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
    }
  }
}

目标-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

例如,要将标签字体大小更改为 30 的动画,从中心持续 1 秒并缩放更大。只需调用

迅速

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

目标-C

[YOUR_LABEL animateWithFontSize:30 
                       duration:1 
             animateAnchorPoint:kCenterXCenterYAnchorPoint];
于 2017-11-22T14:07:34.897 回答
6

斯威夫特 3.0 和斯威夫特 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      

    }
于 2017-05-26T07:06:02.290 回答
1

由于以下原因,我发现这里的每个建议都不合适:

  1. 他们实际上并没有改变字体大小。
  2. 他们不能很好地调整框架大小和自动布局。
  3. 他们的界面很重要和/或在动画块中播放效果不佳。

为了保留所有这些功能并仍然获得平滑的动画过渡,我结合了变换方法和字体方法。

界面很简单。只需更新fontSize属性,您就会更新字体的大小。在动画块内执行此操作,它会动画。

@interface UILabel(MPFontSize)

@property(nonatomic) CGFloat fontSize;

@end

至于实现,有简单的方法,也有更好的方法。

简单的:

@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

现在,这样做的问题是布局在完成时可能会卡顿,因为视图的框架一直根据原始字体调整大小,直到动画完成,此时框架会更新以适应没有动画的目标字体。

解决这个问题有点困难,因为我们需要覆盖intrinsicContentSize. 您可以通过子类UILabel化或调配方法来做到这一点。我个人 swizzle 这个方法,因为它让我fontSize可以为所有UILabels 保留一个通用属性,但这取决于我不能在这里分享的一些库代码。以下是使用子类化的方法。

界面:

@interface AnimatableLabel : UILabel

@property(nonatomic) CGFloat fontSize;

@end

执行:

@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
于 2018-05-18T16:08:14.833 回答
0

如果你想从另一个锚点动画文本大小,这里是Swift 5解决方案:

如何申请:

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

扩展:

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)
  }
}
于 2020-07-22T18:35:44.267 回答