How to animate the string of a CATextLayer?

非 Y 不嫁゛ 提交于 2019-12-13 03:14:47

问题


I am currently overlaying some text in a video using the AVMutableComposition, I would like to change the string repeatedly based on some time interval. I am trying to use the Core Animation to achieve this; however, the string property does not seem to be animatable. Is there any other way to achieve the goal? Thanks.

Code (not working):

func getSubtitlesAnimation(withFrames frames: [String], duration: CFTimeInterval)->CAKeyframeAnimation {
    let animation = CAKeyframeAnimation(keyPath:"string")
    animation.calculationMode = kCAAnimationDiscrete
    animation.duration = duration
    animation.values = frames
    animation.keyTimes = [0,0.5,1]
    animation.repeatCount = Float(frames.count)
    animation.isRemovedOnCompletion = false
    animation.fillMode = kCAFillModeForwards
    animation.beginTime = AVCoreAnimationBeginTimeAtZero
    return animation
}

回答1:


String is not an animatable path on a CATextLayer. Here is the list of keyPaths that you can use on any CALayer.

Some other important things about using Core Animation with AVFoundation.

  • All animations have to have removedCompletion of false.
  • You can only apply one key path animation to a layer once. Meaning if you want to add an opacity of fade in and fade out it needs to be combined into a single keyframe animation. In core animation you could use beginTime and apply two different opacity animations but in my experience with AVFoundation and Core Animation this does not work. Because of this you will have to figure out the key times and values for what you would want to occur if you wanted to use two different (ex opacity) animations on the same keypath for the CALayer.
  • Your discrete curve will still work but you will have to work out the keytimes and values.
  • Because string is not available as an animatable property the next best thing would be to use multiple CATextLayers and fade them in one after the other. Here is an example. Replace CACurrentMediaTime() with AVCoreAnimationBeginTimeAtZero for use with AVFoundation. This was just an example so you could visualize what you want.

Example1Using-Discrete

 import UIKit

class ViewController: UIViewController {

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        //center the frame
        let textFrame = CGRect(x: (self.view.bounds.width - 200)/2, y: (self.view.bounds.height - 100)/2, width: 200, height: 50)

        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) {
            //animation spacing could be a negative value of half the animation to appear to fade between strings
            self.animateText(subtitles: ["Hello","Good Morning","Good Afternoon","Good Evening","Goodnight","Goodbye"], duration: 2, animationSpacing: 0, frame: textFrame, targetLayer: self.view.layer)
        }
    }

    func animateText(subtitles:[String],duration:Double,animationSpacing:Double,frame:CGRect,targetLayer:CALayer){
        var currentTime : Double = 0
        for x in 0..<subtitles.count{
            let string = subtitles[x]
            let textLayer = CATextLayer()
            textLayer.frame = frame
            textLayer.string = string
            textLayer.font = UIFont.systemFont(ofSize: 20)
            textLayer.foregroundColor = UIColor.black.cgColor
            textLayer.fontSize = 20.0
            textLayer.alignmentMode = kCAAlignmentCenter
            let anim = getSubtitlesAnimation(duration: duration, startTime: currentTime)
            targetLayer.addSublayer(textLayer)
            textLayer.add(anim, forKey: "opacityLayer\(x)")
            currentTime += duration + animationSpacing
        }
    }
    func getSubtitlesAnimation(duration: CFTimeInterval,startTime:Double)->CAKeyframeAnimation {
        let animation = CAKeyframeAnimation(keyPath:"opacity")
        animation.duration = duration
        animation.calculationMode = kCAAnimationDiscrete
        //have to fade in and out with a single animation because AVFoundation
        //won't allow you to animate the same propery on the same layer with
        //two different animations
        animation.values = [0,1,1,0,0]
        animation.keyTimes = [0,0.001,0.99,0.999,1]
        animation.isRemovedOnCompletion = false
        animation.fillMode = kCAFillModeBoth
        //Replace with AVCoreAnimationBeginTimeAtZero for AVFoundation
        animation.beginTime = CACurrentMediaTime() + startTime
        return animation
    }
}

EXAMPLE2-Using a long fade-Gif Attached

import UIKit

class ViewController: UIViewController {

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        //center the frame
        let textFrame = CGRect(x: (self.view.bounds.width - 200)/2, y: (self.view.bounds.height - 100)/2, width: 200, height: 50)

        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) {
            //animation spacing could be a negative value of half the animation to appear to fade between strings
            self.animateText(subtitles: ["Hello","Good Morning","Good Afternoon","Good Evening","Goodnight","Goodbye"], duration: 4, animationSpacing: -2, frame: textFrame, targetLayer: self.view.layer)
        }
    }

    func animateText(subtitles:[String],duration:Double,animationSpacing:Double,frame:CGRect,targetLayer:CALayer){
        var currentTime : Double = 0
        for x in 0..<subtitles.count{
            let string = subtitles[x]
            let textLayer = CATextLayer()
            textLayer.frame = frame
            textLayer.string = string
            textLayer.font = UIFont.systemFont(ofSize: 20)
            textLayer.foregroundColor = UIColor.black.cgColor
            textLayer.fontSize = 20.0
            textLayer.alignmentMode = kCAAlignmentCenter
            let anim = getSubtitlesAnimation(duration: duration, startTime: currentTime)
            targetLayer.addSublayer(textLayer)
            textLayer.add(anim, forKey: "opacityLayer\(x)")
            currentTime += duration + animationSpacing
        }
    }
    func getSubtitlesAnimation(duration: CFTimeInterval,startTime:Double)->CAKeyframeAnimation {
        let animation = CAKeyframeAnimation(keyPath:"opacity")
        animation.duration = duration
        //have to fade in and out with a single animation because AVFoundation
        //won't allow you to animate the same propery on the same layer with
        //two different animations
        animation.values = [0,0.5,1,0.5,0]
        animation.keyTimes = [0,0.25,0.5,0.75,1]
        animation.isRemovedOnCompletion = false
        animation.fillMode = kCAFillModeBoth
        //Replace with AVCoreAnimationBeginTimeAtZero for AVFoundation
        animation.beginTime = CACurrentMediaTime() + startTime
        return animation
    }
}

RESULT: Duration of 2 seconds in and 2 seconds out. Could be immediate.




回答2:


Try to think how subtitles are added in a movie?

A subtitle file contains time stamp values like, 00:31:21 : "Some text". So when the seekbar of a movie is at 00:31:21, you see "Some text" as the subtitle.

Similarly fo your requirement, you would need multiple CATextLayers which have animations corresponding to them (same or different for each CATExtLayer). When one CATextLayer animation ends, you can fade in your second CATextLayer and Fadeout the first one.

Last time I did this I remember, animations can be grouped and you can actually specify start point of an animation. Check beginTime property of CABasicAnimation



来源:https://stackoverflow.com/questions/51722320/how-to-animate-the-string-of-a-catextlayer

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!