我拼命想把 smallLabel
变成 bigLabel
。通过变形,我的意思是将一个标签的以下属性转换为与另一个标签的相应属性相匹配,并具有流畅的动画效果:
- 字体大小
- 字体粗细
- 框架(即边界和位置)
当使用大标题时,所需的效果应该类似于应用于导航 Controller 标题标签的动画:
现在我知道去年的 WWDC session Advanced Animations with UIKit他们在那里展示了如何做到这一点。然而,这种技术非常有限,因为它基本上只是对标签的框架应用变换,因此只有在除字体大小之外的所有属性都相同的情况下它才有效。
当一个标签具有常规
字体粗细而另一个标签具有粗体
粗细时,该技术已经失败——这些属性在应用时不会改变一个变换。因此,我决定更深入地挖掘并使用 Core Animation 进行变形。
首先,我创建了一个新的文本层,我将其设置为在视觉上与 smallLabel
相同:
/// Creates a text layer with its text and properties copied from the label.
func createTextLayer(from label: UILabel) -> CATextLayer {
let textLayer = CATextLayer()
textLayer.frame = label.frame
textLayer.string = label.text
textLayer.opacity = 0.3
textLayer.fontSize = label.font.pointSize
textLayer.foregroundColor = UIColor.red.cgColor
textLayer.backgroundColor = UIColor.cyan.cgColor
view.layer.addSublayer(textLayer)
return textLayer
}
然后,我创建必要的动画并将它们添加到该层:
func animate(from smallLabel: UILabel, to bigLabel: UILabel) {
let textLayer = createTextLayer(from: smallLabel)
view.layer.addSublayer(textLayer)
let group = CAAnimationGroup()
group.duration = 4
group.repeatCount = .infinity
// Animate font size
let fontSizeAnimation = CABasicAnimation(keyPath: "fontSize")
fontSizeAnimation.toValue = bigLabel.font.pointSize
// Animate font (weight)
let fontAnimation = CABasicAnimation(keyPath: "font")
fontAnimation.toValue = CGFont(bigLabel.font.fontName as CFString)
// Animate bounds
let boundsAnimation = CABasicAnimation(keyPath: "bounds")
boundsAnimation.toValue = bigLabel.bounds
// Animate position
let positionAnimation = CABasicAnimation(keyPath: "position")
positionAnimation.toValue = bigLabel.layer.position
group.animations = [
fontSizeAnimation,
boundsAnimation,
positionAnimation,
fontAnimation
]
textLayer.add(group, forKey: "group")
}
这是我得到的:
如您所见,它并没有完全按预期工作。这个动画有两个问题:
字体粗细不是动画而是在动画过程中突然切换。
当(青色)文本层的框架按预期移动并变大时,文本本身以某种方式向层的左下角移动并从右侧被截断。
我的问题是:
1️⃣为什么会出现这种情况(尤其是2.)?
和
2️⃣如何实现如上所示的大标题变形行为——包括字体粗细动画?
最佳答案
可能比您想象的更简单。只需快照层或 View 。红色文本在 apple 转换的视频中渗出,因此它们只是通过快照或转换混合在一起。我倾向于快照 View ,以免影响下面的真实 View 。这是一个 UIView 动画,尽管同样的事情可以用 CAAnimations 完成。
import UIKit
class ViewController: UIViewController {
lazy var slider : UISlider = {
let sld = UISlider(frame: CGRect(x: 30, y: self.view.frame.height - 60, width: self.view.frame.width - 60, height: 20))
sld.addTarget(self, action: #selector(sliderChanged), for: .valueChanged)
sld.value = 0
sld.maximumValue = 1
sld.minimumValue = 0
sld.tintColor = UIColor.blue
return sld
}()
lazy var fakeNavBar : UIView = {
let vw = UIView(frame: CGRect(origin: CGPoint(x: 0, y: 20), size: CGSize(width: self.view.frame.width, height: 60)))
vw.autoresizingMask = [.flexibleWidth]
return vw
}()
lazy var label1 : UILabel = {
let lbl = UILabel(frame: CGRect(x: 10, y: 5, width: 10, height: 10))
lbl.text = "HELLO"
lbl.font = UIFont.systemFont(ofSize: 17, weight: .light)
lbl.textColor = .red
lbl.sizeToFit()
return lbl
}()
lazy var label2 : UILabel = {
let lbl = UILabel(frame: CGRect(x: 10, y: label1.frame.maxY, width: 10, height: 10))
lbl.text = "HELLO"
lbl.font = UIFont.systemFont(ofSize: 40, weight: .bold)
lbl.textColor = .black
lbl.sizeToFit()
return lbl
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.view.addSubview(fakeNavBar)
self.fakeNavBar.addSubview(label1)
self.fakeNavBar.addSubview(label2)
self.view.addSubview(slider)
doAnimation()
}
func doAnimation(){
self.fakeNavBar.layer.speed = 0
let snap1 = label1.createImageView()
self.fakeNavBar.addSubview(snap1)
label1.isHidden = true
let snap2 = label2.createImageView()
self.fakeNavBar.addSubview(snap2)
label2.isHidden = true
let scaleForSnap1 = snap2.frame.height/snap1.frame.height
let scaleForSnap2 = snap1.frame.height/snap2.frame.height
let snap2Center = snap2.center
let snap1Center = snap1.center
snap2.transform = CGAffineTransform(scaleX: scaleForSnap2, y: scaleForSnap2)
snap2.alpha = 0
snap2.center = snap1Center
UIView.animateKeyframes(withDuration: 1.0, delay: 0, options: .calculationModeCubic, animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5, animations: {
snap1.alpha = 0.2
})
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5, animations: {
snap2.alpha = 0.2
})
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5, animations: {
snap2.alpha = 1
})
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.1, animations: {
snap1.alpha = 0
})
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1, animations: {
snap1.center = snap2Center
snap2.transform = .identity
snap2.center = snap2Center
snap1.transform = CGAffineTransform(scaleX: scaleForSnap1, y: scaleForSnap1)
})
}) { (finished) in
self.label2.isHidden = false
snap1.removeFromSuperview()
snap2.removeFromSuperview()
}
}
@objc func sliderChanged(){
if slider.value != 1.0{
fakeNavBar.layer.timeOffset = CFTimeInterval(slider.value)
}
}
}
extension UIImage {
convenience init(view: UIView) {
UIGraphicsBeginImageContext(view.frame.size)
view.layer.render(in:UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.init(cgImage: image!.cgImage!)
}
}
extension UIView {
func createImageView() ->UIImageView{
let imgView = UIImageView(frame: self.frame)
imgView.image = UIImage(view: self)
return imgView
}
}
关于ios - 如何在 iOS 中正确变形文本?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50876789/