如何在iOS中正确变形文字? [英] How to properly morph text in iOS?

查看:138
本文介绍了如何在iOS中正确变形文字?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正拼命试图将 smallLabel 变形为 bigLabel .通过 morphing ,我的意思是使用平滑的动画从一个标签转换以下属性以匹配另一个标签的相应属性:

  • 字体大小
  • 字体粗细
  • 框架(即边界和位置)

所需的效果应类似于使用大标题时应用于导航控制器标题标签的动画:

现在我知道去年的WWDC会议

如您所见,它并没有按预期工作.此动画有两个问题:

  1. 字体粗细不会动画,但会在动画过程的中间突然切换.

  2. (青色)文本层的框架按预期移动并增大大小时,文本本身以某种方式向该层的左下角移动,并从右侧切掉.p>

我的问题是:

1️⃣为什么会发生这种情况(尤其是2)?

2️⃣如何实现大标题的变形行为-包括上图所示的字体粗细动画?

解决方案

可能比您想象的更简单.只需快照图层或视图即可.苹果过渡的视频中的红色文本会出血,因此它们都只是与快照或转换融为一体.我倾向于对视图进行快照,以免影响下面的真实视图.这是一个UIView动画,尽管使用CAAnimations可以完成相同的事情.

 导入UIKit类ViewController:UIViewController {惰性var滑块:UISlider = {让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 = 0sld.maximumValue = 1sld.minimumValue = 0sld.tintColor = UIColor.blue返回sld}()懒惰的var fakeNavBar:UIView = {让vw = UIView(frame:CGRect(来源:CGPoint(x:0,y:20),size:CGSize(width:self.view.frame.width,height:60)))vw.autoresizingMask = [.flexibleWidth]返回大众}()惰性变量标签1:UILabel = {让lbl = UILabel(框架:CGRect(x:10,y:5,宽度:10,高度:10))lbl.text ="HELLO"lbl.font = UIFont.systemFont(ofSize:17,weight:.light)lbl.textColor = .redlbl.sizeToFit()返回lbl}()惰性变量标签2:UILabel = {令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 = .blacklbl.sizeToFit()返回lbl}()覆盖func viewDidLoad(){super.viewDidLoad()//加载视图后进行其他任何设置,通常是从笔尖进行.self.view.addSubview(fakeNavBar)self.fakeNavBar.addSubview(label1)self.fakeNavBar.addSubview(label2)self.view.addSubview(滑块)doAnimation()}func doAnimation(){self.fakeNavBar.layer.speed = 0让snap1 = label1.createImageView()self.fakeNavBar.addSubview(snap1)label1.isHidden = true让snap2 = label2.createImageView()self.fakeNavBar.addSubview(snap2)label2.isHidden = true让scaleForSnap1 = snap2.frame.height/snap1.frame.height让scaleForSnap2 = snap1.frame.height/snap2.frame.height让snap2Center = snap2.center让snap1Center = snap1.centersnap2.transform = CGAffineTransform(scaleX:scaleForSnap2,y:scaleForSnap2)snap2.alpha = 0snap2.center = snap1CenterUIView.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,1,animations:{snap1.center = snap2Centersnap2.transform = .identitysnap2.center = snap2Centersnap1.transform = CGAffineTransform(scaleX:scaleForSnap1,y:scaleForSnap1)})}){(已完成)在self.label2.isHidden = falsesnap1.removeFromSuperview()snap2.removeFromSuperview()}}@objc func SliderChanged(){如果slide.value!= 1.0 {fakeNavBar.layer.timeOffset = CFTimeInterval(slider.value)}}}扩展程序UIImage {便利init(view:UIView){UIGraphicsBeginImageContext(view.frame.size)view.layer.render(in:UIGraphicsGetCurrentContext()!)让图像= UIGraphicsGetImageFromCurrentImageContext()UIGraphicsEndImageContext()self.init(cgImage:图片!.cgImage!)}}扩展UIView {func createImageView()-> UIImageView {让imgView = UIImageView(frame:self.frame)imgView.image = UIImage(视图:自我)返回imgView}} 

结果:

I'm desperately trying to morph a smallLabel into a bigLabel. By morphing, I mean transforming the following properties from one label to match the respective properties of the other label, with a smooth animation:

  • font size
  • font weight
  • frame (i.e. bounds and position)

The desired effect should look similar to the animation that is applied to the navigation controller's title label when using large titles:

Now I'm aware of last year's WWDC session Advanced Animations with UIKit where they show how to do just that. However, this technique is pretty limited as it's basically just applying a transform to the label's frame and thus it only works if all properties other than the font size are identical.

The technique fails already when one label has a regular font weight and the other a bold weight – those properties do not change when applying a transform. Thus, I decided to dig a little deeper and use Core Animation for morphing.

First, I create a new text layer which I set up to be visually identical with the 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
}

Then, I create the necessary animations and add them to this layer:

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

Here is what I get:

As you can see, it doesn't quite work as intended. There are two issues with this animation:

  1. The font weight doesn't animate but switches abruptly in the middle of the animation process.

  2. While the frame of the (cyan colored) text layer moves and increases in size as expected, the text itself somehow moves towards the lower-left corner of the layer and is cut off from the right side.

My questions are:

1️⃣Why does this happen (especially 2.)?

and

2️⃣How can I achieve the large title morphing behavior – including a font-weight animation – as shown above?

解决方案

Probably something more simple than you think. Just snapshot the layers or views. The red text bleeds in the video of the apple transition so they are both just being blended together either with a snapshot or just a transform. I tend to snapshot views so as to not effect the real view underneath. Here is a UIView animation although the same thing could be done with 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
    }
}

RESULT:

这篇关于如何在iOS中正确变形文字?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆