问题
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