我目前正在制作一个使用自定义 View Controller 容器的应用程序。一次在屏幕上显示多个视图,当点击一个时,选定的视图控制器将动画到全屏。这样做时,选定的视图控制器子视图也会缩放(框架、字体大小等)。虽然,UILabel 的字体属性不可动画化,从而导致问题。我尝试了多种解决方案,但都非常糟糕。
我尝试过的解决方案是:
截取较大视图的屏幕截图并为更改设置动画(类似于 Flipboard 所做的),使用 transform 属性设置动画 缩小 UIScrollView 并在进入全屏时将其放大。将 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];
}];
您可以使用如下动画更改 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);
}];
从 2017 年开始......
斯威夫特 3.0、4.0
UIView.animate(withDuration: 0.5) {
label.transform = CGAffineTransform(scaleX: 1.1, y: 1.1) //Scale label area
}
批判的:
避免模糊的关键点是你必须从最大的尺寸开始,然后缩小它。然后在需要时扩展为“1”。
对于快速“弹出”(如高亮动画),可以扩展到 1 以上,但如果您在两种尺寸之间转换,请将较大的尺寸设为“正确”的正常尺寸。
我在 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()
}
}
}
如果标签文本左对齐或右对齐,请取消注释行。
您还可以使用具有 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 来解决它)。
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.
重置为 fromValue 是任何 CABasicAnimation 的行为,幸运的是,此解决方案也适用于任何 CABasicAnimation
fontSizeAnimation.isRemovedOnCompletion = false fontSizeAnimation.fillMode = CAMediaTimingFillMode.forwards
对于那些不寻找转换但实际值变化的人:
UIView.transition(with: label, duration: 0.25, options: .transitionCrossDissolve, animations: {
self.label.font = UIFont.systemFont(ofSize: 15)
}) { isFinished in }
https://i.stack.imgur.com/UDLhF.gif
对于想要调整动画方向的人
我为 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];
斯威夫特 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
}
由于以下原因,我发现这里的每个建议都不合适:
他们实际上并没有改变字体大小。他们不能很好地调整框架大小和自动布局。他们的界面很重要和/或在动画块中播放效果不佳。
为了保留所有这些功能并仍然获得平滑的动画过渡,我结合了变换方法和字体方法。
界面很简单。只需更新 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
或调整方法来做到这一点。我个人调整了该方法,因为它让我可以为所有 UILabel
保留一个通用 fontSize
属性,但这取决于我无法在此处共享的一些库代码。以下是使用子类化的方法。
界面:
@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
如果你想从另一个锚点动画文本大小,这里是 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)
}
}
不定期副业成功案例分享
CGAffineTransform
不考虑字体跟踪。因此,如果尝试使用 San Francisco 字体缩放UILabel
,则缩放后的标签将具有不正确的跟踪值。