web-dev-qa-db-ja.com

UILabelフォントサイズ変更のアニメーション化

現在、カスタムView Controllerコンテナを使用するアプリケーションを作成しています。一度に複数のビューが画面に表示され、1つをタップすると、選択したView Controllerが全画面にアニメーション化します。そうすることで、選択されたView Controllerサブビューも同様に拡大縮小します(フレーム、フォントサイズなど)。しかし、UILabelのフォントプロパティはアニメーション化できず、問題につながります。私は複数のソリューションを試しましたが、すべてがひどいものです。

私が試した解決策は次のとおりです。

  1. 大きなビューのスクリーンショットを撮り、変更をアニメーション化します(Flipboardの動作と同様)
  2. 変換プロパティを使用してアニメーション化する
  3. UIScrollViewをズームアウトし、フルスクリーンにしたときにズームインします。
  4. AdjustsFontSizeToFitWidthをYESに設定し、アニメーションの前にfontSizeを設定します

これまでのところ、1つが最善の解決策でしたが、満足していません。

[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];

}];
62
morcutt

以下のようなアニメーションでUILabelのサイズとフォントを変更できます。ここに、transform Animationで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);
    }];

これがお役に立てば幸いです。

55
Paras Joshi

2017年以降...

Swift 3.0、4.

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

SwiftでUILabel拡張機能を作成しました。

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

ラベルテキストが左揃えまたは右揃えの場合、行のコメントを解除します。

21
mixel

また、アニメーション可能なプロパティとして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を作成し、その制約をアニメーション化することで必要に応じて解決しました)。

13
Yinan Qiu

アニメーションの方向を調整したい人向け

フォントサイズの変更をアニメートするために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];
8
trungduc

Swift 3.0&Swift 4.

 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      

    }
4
Sam Bing

これらの理由から、ここでの提案はそれぞれ不十分であることがわかりました。

  1. 実際には、フォントサイズは変更されません。
  2. フレームのサイズ設定と自動レイアウトではうまく機能しません。
  3. それらのインターフェースは重要なものであり、アニメーションブロック内でNiceを再生しません。

これらのすべての機能を保持し、アニメーションをスムーズに移行させるために、変換アプローチとフォントアプローチを組み合わせました。

インターフェースはシンプルです。 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をサブクラス化するか、メソッドをスウィズルします。汎用の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
1
lhunath

変換を求めていないが、実際の値が変化する場合:

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

テキストがある場合、これを行います:

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

(Gifは異なるフォントを表示します)

enter image description here

1
SirRupertIII