Animieren der Änderung der UILabel-Schriftgröße

70

Ich erstelle derzeit eine Anwendung, die einen benutzerdefinierten View Controller-Container verwendet. Es werden mehrere Ansichten gleichzeitig auf dem Bildschirm angezeigt. Wenn Sie auf eine tippen, wird der ausgewählte Ansichts-Controller im Vollbildmodus animiert. Dabei werden auch die Unteransichten der ausgewählten Ansichtssteuerungen skaliert (Rahmen, Schriftgröße usw.). Die Schrifteigenschaft von UILabel kann jedoch nicht animiert werden, was zu Problemen führt. Ich habe mehrere Lösungen ausprobiert, aber alle saugen.

Die Lösungen, die ich versucht habe, sind:

  1. Machen Sie einen Screenshot der größeren Ansicht und animieren Sie die Änderung (ähnlich wie bei Flipboard).
  2. Animieren Sie mithilfe der Transformationseigenschaft
  3. Verkleinern einer UIScrollView und Vergrößern, wenn sie auf Vollbild gebracht wird.
  4. Durch Einstellen von adjustsFontSizeToFitWidth auf YES und Festlegen der Schriftgröße vor der Animation

Eine war bisher die beste Lösung, aber ich bin damit nicht zufrieden.

Ich suche nach anderen Vorschlägen, wenn jemand einen oder einen UILabel-Ersatz hat, der mit [UIView animate ..] reibungslos animiert.

Hier ist ein gutes Beispiel, das dem ähnelt, was mein UILabel tun soll: http://www.cocoawithlove.com/2010/09/zoomingviewcontroller-to-animate-uiview.html

EDIT: Dieser Code funktioniert

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

}];
Morcutt
quelle

Antworten:

64

Sie können die Größe und Schriftart Ihrer UILabelAnimation wie unten ändern . Hier habe ich nur ein Beispiel dafür, wie Sie die Schriftart UILabelmit transform Animation ändern können .

    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);
    }];

Ich hoffe das ist hilfreich für dich ..

Paras Joshi
quelle
Ich habe es versucht. Es scheint, als ob die Transformation vom Mittelpunkt aus erfolgt. Wenn ich den Scale-Up-Effekt animiere, scheint er mit einer anderen Geschwindigkeit als die Bildänderung angezeigt zu werden. Ich mache wahrscheinlich nicht viel Sinn, aber die Animation wird nicht auf die gleiche Weise repliziert wie die Skalierung von UIImageViews.
Morcutt
Ich mache das gleiche mit dem obigen Code, aber ich ändere nur die Größe von Lable und UIImageView ... was genau willst du? und was ist das Problem, wenn Sie den obigen Code verwenden?
Paras Joshi
@morcutt auch, wenn Sie die Schriftgröße mit Ihrer Etikettengröße ändern möchten. bedeutet, dass Sie mit Ihrer Etikettengröße anpassen und dann die Eigenschaft adjustsFontSizeToFitWidth von uilable verwenden. Weitere Informationen finden Sie unter diesem Link stackoverflow.com/questions/10475513/uilabel-font-size
Paras Joshi
Ich habe auch versucht, adjustsFontSizeToFitWidth anzupassen. Die Animation wird ausgeführt, aber die Position ist während der Animation immer ausgeschaltet.
Morcutt
4
Das einzige Problem bei dieser Methode ist, dass CGAffineTransformdie Schriftartverfolgung nicht berücksichtigt wird. Wenn man also versucht, eine UILabelSchriftart mit der Schriftart San Francisco zu skalieren, hat das skalierte Etikett falsche Tracking-Werte.
Schieberegler
42

Ab 2017 ....

Swift 3.0, 4.0

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

Kritisch:

Der kritische Punkt, um Unschärfe zu vermeiden, ist, dass Sie mit der größten Größe beginnen und sie verkleinern müssen. Erweitern Sie dann bei Bedarf auf "1".

Für schnelle "Pops" (wie eine Hervorhebungsanimation) ist es in Ordnung, über 1 hinaus zu expandieren. Wenn Sie jedoch zwischen zwei Größen wechseln, machen Sie die größere Größe zur "richtigen" normalen .

Chathuranga Silva
quelle
der einzige Weg zu gehen!
Fattie
1
Leider wird die Schrift dadurch unscharf, wenn Sie sie um mehr als einen winzigen Betrag vergrößern müssen.
Alex Scanlan
@AlexScanlan, aber Sie können die Schriftgröße ändern, nachdem die Animation beendet ist, so dass es nicht verschwommen ist :)
Freeubi
2
Ah, @AlexScanlan, es ist wichtig, mit den größeren Größen , der größten Größe, die Sie benötigen, zu beginnen und sie auf die kleinere Größe zu verkleinern.
Fattie
27

Ich habe eine UILabelErweiterung in Swift erstellt.

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

Kommentieren Sie Zeilen aus, wenn der Beschriftungstext links oder rechts ausgerichtet ist.

mischen
quelle
Was ist, wenn das Etikett richtig ausgerichtet ist? Das Problem, auf das ich stoße, ist, dass ich ein rechtsbündiges Etikett habe. Wenn ich diesen Code verwende, wird das Etikett nach rechts verschoben und ist nicht mehr ausgerichtet.
JideO
@JideO Antwort aktualisiert.
Mixel
Funktioniert perfekt. Vielen Dank!
Baran Emre
Dies ist das einzige, was bei mir wirklich funktioniert hat. Vielen Dank!
Victor Camargo
15

Sie können auch CATextLayer verwenden, der fontSize als animierbare Eigenschaft hat.

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)

Ich habe es in meinem Projekt verwendet: https://github.com/yinanq/AngelListJobs

Bei dieser Animation wird die Schriftart oben links ausgerichtet (im Gegensatz zu CGAffineTransformScale, bei der das Etikett von der Mitte aus skaliert wird), je nach Bedarf für oder wider. Ein Nachteil von CATextLayer ist, dass CALayers nicht mit Autolayout-Einschränkungsanimationen arbeiten (was ich zufällig brauchte und löste, indem ich eine UIView erstellte, die nur den CATextLayer enthielt, und dessen Einschränkungen animierte).

Yinan Qiu
quelle
1
@ShubhamNaik Ich vermute, Sie haben die zweite Zeile unter "// animation:" in meiner obigen Antwort nicht implementiert? 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. Das Zurücksetzen auf fromValue ist das Verhalten für jede CABasicAnimation. Glücklicherweise ist diese Lösung auch für jede CABasicAnimation universell
Yinan Qiu
Um die Animation in ihrem endgültigen Zustand zu halten, können Sie auch: fontSizeAnimation.isRemovedOnCompletion = false fontSizeAnimation.fillMode = CAMediaTimingFillMode.forwards
Mask Ota
8

Für jemanden, der die Animationsrichtung anpassen möchte

Ich habe eine Erweiterung erstellt UILabel, um die Änderung der Schriftgröße zu animieren

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

Wenn Sie die Animationsrichtung anpassen möchten, verwenden Sie die folgende Methode und setzen Sie einen geeigneten Ankerpunkt.

SCHNELL

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

ZIEL 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

Um beispielsweise die Änderung der Schriftgröße des Etiketts auf 30 zu animieren, dauert die Dauer 1 Sekunde von der Mitte und die Skalierung größer. Einfach anrufen

SCHNELL

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

ZIEL C

[YOUR_LABEL animateWithFontSize:30 
                       duration:1 
             animateAnchorPoint:kCenterXCenterYAnchorPoint];
trungduc
quelle
7

Für diejenigen, die keine Transformation, sondern eine tatsächliche Wertänderung suchen:

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

Geben Sie hier die Bildbeschreibung ein

Sir RupertIII
quelle
5

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      

    }
Sam Bing
quelle
1

Ich fand jeden der Vorschläge hier aus folgenden Gründen unzureichend:

  1. Sie ändern die Schriftgröße nicht wirklich.
  2. Sie spielen nicht gut mit Frame-Größe und automatischem Layout.
  3. Ihre Benutzeroberfläche ist nicht trivial und / oder spielt sich in Animationsblöcken nicht gut ab.

Um all diese Funktionen beizubehalten und dennoch einen reibungslosen Animationsübergang zu erzielen, habe ich den Transformationsansatz und den Schriftansatz kombiniert.

Die Schnittstelle ist einfach. Aktualisieren fontSizeSie einfach die Eigenschaft und Sie aktualisieren die Schriftgröße. Wenn Sie dies in einem Animationsblock tun, wird es animiert.

@interface UILabel(MPFontSize)

@property(nonatomic) CGFloat fontSize;

@end

Was die Implementierung betrifft, gibt es den einfachen und den besseren Weg.

Einfach:

@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

Das Problem dabei ist nun, dass das Layout nach Abschluss stottern kann, da der Rahmen der Ansicht bis zur Fertigstellung der Animation auf der Grundlage der Originalschrift angepasst wird. Zu diesem Zeitpunkt wird der Rahmen aktualisiert, um die Zielschrift ohne Animation aufzunehmen.

Die Behebung dieses Problems ist etwas schwieriger, da wir es überschreiben müssen intrinsicContentSize. Sie können dies entweder durch Unterklassen UILabeloder durch Swizzeln der Methode tun . Ich persönlich habe die Methode geändert, weil ich damit eine generische fontSizeEigenschaft für alle verfügbar halten kannUILabel s , aber das hängt von einem Bibliothekscode ab, den ich hier nicht kann. Hier erfahren Sie, wie Sie dies mithilfe von Unterklassen tun.

Schnittstelle:

@interface AnimatableLabel : UILabel

@property(nonatomic) CGFloat fontSize;

@end

Implementierung:

@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
lhunath
quelle
3
Danke für deine Antwort. Denken Sie daran, es ist 2018, könnten Sie eine Swift- Version Ihrer Antwort bereitstellen ? Vielen Dank!
Gabriel
1

Wenn Sie die Textgröße von einem anderen Ankerpunkt aus animieren möchten, finden Sie hier den Swift 5 Lösung:

So bewerben Sie sich:

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

Erweiterungen:

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)
  }
}
appsunited
quelle