我拼命试图将smallLabel
变身为bigLabel
。通过变形,我的意思是从一个标签转换以下属性以匹配另一个标签的相应属性,并使用平滑动画:
在使用大型标题时,所需效果应类似于应用于导航控制器标题标签的动画:
现在我知道去年的WWDC会议Advanced Animations with UIKit,他们在那里展示了如何做到这一点。然而,这种技术非常有限,因为它基本上只是将变换应用于标签的框架,因此只有在字体大小以外的所有属性都相同时才有效。
当一个标签的字体权重为regular
而另一个标签的权重为bold
时,该技术已失败 - 这些属性在应用转换时不会发生变化。因此,我决定深入挖掘并使用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")
}
这是我得到的:
正如您所看到的,它并没有按预期工作。这个动画有两个问题:
字体粗细没有动画,但在动画过程中突然切换。
当(青色)文本图层的框架按预期移动并增大尺寸时,文本本身会以某种方式向图层的左下角移动,并从右侧切除。
我的问题是:
和
答案 0 :(得分:1)
可能比你想象的更简单。只需对图层或视图进行快照。红色文本在Apple转换的视频中流血,因此它们只是通过快照或仅仅转换混合在一起。我倾向于快照视图,以便不影响下面的真实视图。这是一个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
}
}