iphone - Animando el cambio de tamaño de fuente UILabel
ios animation (7)
Actualmente estoy haciendo una aplicación que usa un contenedor de controlador de vista personalizado. Múltiples vistas están en la pantalla a la vez y cuando se toca una, el controlador de vista seleccionado se anima a pantalla completa. Al hacerlo, los controles de vista seleccionados suben también la escala (fotograma, tamaño de fuente, etc.). Sin embargo, la propiedad de fuente de UILabel no es animable, lo que genera problemas. He intentado con múltiples soluciones, pero todo es una mierda.
Las soluciones que he probado son:
- Toma una captura de pantalla de la vista más grande y anima el cambio (similar a como lo hace Flipboard)
- Animar mediante el uso de la propiedad transformar
- Alejar un UIScrollView y acercarlo cuando aparece en pantalla completa.
- El ajuste ajustaFontSizeToFitWidth a YES y configura fontSize antes de la animación
Una ha sido la mejor solución hasta ahora, pero no estoy satisfecho con ella.
Estoy buscando otras sugerencias si alguien tiene alguno o un sustituto UILabel que se anima sin problemas utilizando [UIView animate ..].
Aquí hay un buen ejemplo que es similar a lo que me gustaría que hiciera mi UILabel: http://www.cocoawithlove.com/2010/09/zoomingviewcontroller-to-animate-uiview.html
EDITAR: Este código funciona
// 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];
}];
Para 2017 en adelante ...
Swift 3.0, 4.0
UIView.animate(withDuration: 0.5) {
label.transform = CGAffineTransform(scaleX: 1.1, y: 1.1) //Scale label area
}
Para alguien que quiere ajustar la dirección de la animación
He creado una extensión para UILabel
para animar el cambio de tamaño de fuente
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
}
}
Si desea ajustar la dirección de la animación, utilice el método a continuación y coloque un punto de anclaje adecuado.
RÁPIDO
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 OBJETIVO
// 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
Por ejemplo, para animar cambiando el tamaño de la fuente de la etiqueta a 30, la duración 1 desde el centro y la escala más grande. Simplemente llame
RÁPIDO
YOUR_LABEL.animate(fontSize: 30, duration: 1, animateAnchorPoint: LabelAnimateAnchorPoint.centerXCenterY)
C OBJETIVO
[YOUR_LABEL animateWithFontSize:30
duration:1
animateAnchorPoint:kCenterXCenterYAnchorPoint];
Encontré cada una de las sugerencias aquí inadecuadas por estas razones:
- En realidad, no cambian el tamaño de la fuente.
- No juegan bien con el tamaño del marco y el diseño automático.
- Su interfaz no es trivial y / o no juega bien dentro de los bloques de animación.
Para conservar todas estas características y obtener una transición de animación uniforme, he combinado el enfoque de transformación y el enfoque de fuente.
La interfaz es simple. Simplemente actualice la propiedad fontSize
y actualizará el tamaño de la fuente. Haga esto dentro de un bloque de animación y animará.
@interface UILabel(MPFontSize)
@property(nonatomic) CGFloat fontSize;
@end
En cuanto a la implementación, está la manera simple, y está la mejor manera.
Sencillo:
@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
Ahora, el problema con esto es que el diseño puede tartamudear al completarse, porque el marco de la vista se dimensiona en función de la fuente original hasta la finalización de la animación, en cuyo momento el marco se actualiza para acomodar la fuente objetivo sin animación.
Reparar este problema es un poco más difícil porque necesitamos anular intrinsicContentSize
. Puedes hacerlo subclasificando UILabel
o modificando el método. Personalmente agito el método, porque me permite mantener una propiedad fontSize
genérica disponible para todos los UILabel
, pero eso depende de algún código de biblioteca que no puedo compartir aquí. Aquí es cómo harías con subclases.
Interfaz:
@interface AnimatableLabel : UILabel
@property(nonatomic) CGFloat fontSize;
@end
Implementación:
@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
Puedes cambiar el tamaño y la fuente de tu UILabel
con animación como la siguiente. Aquí solo pongo el ejemplo de cómo cambiar la fuente de UILabel
con la animación de transformación.
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);
}];
Espero que esto te sea útil ..
También podría usar CATextLayer que tiene fontSize como una propiedad animable.
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)
Lo utilicé en mi proyecto: https://github.com/yinanq/AngelListJobs
Esta animación mantiene la fuente en la parte superior izquierda alineada (a diferencia de CGAffineTransformScale escalando la etiqueta desde el centro), pro o contra dependiendo de sus necesidades. Una desventaja de CATextLayer es que los CALayers no funcionan con la animación de restricción de autolayout (que casualmente necesitaba) y la resolvió haciendo una UIView que contenía solo CATextLayer y animaba sus restricciones).
UILabel
extensión UILabel
en 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()
}
}
}
Descomente las líneas si el texto de la etiqueta está alineado a la izquierda o a la derecha.
Swift 3.0 y 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
}