VC
import UIKit
///16进制色号 设置颜色 示例:UIColorHex(0x26A7E8)
public func Color_Hex(value:UInt32) -> UIColor{
return Color_Hex_A(value: value, alpha: 1.0)
}
public func Color_Hex_A(value:UInt32, alpha:CGFloat) -> UIColor{
let color = UIColor.init(red: (((CGFloat)((value & 0xFF0000) >> 16)) / 255.0), green: (((CGFloat)((value & 0xFF0000) >> 16)) / 255.0), blue: (((CGFloat)((value & 0xFF0000) >> 16)) / 255.0), alpha: alpha)
return color
}
class TextEffectsViewController: UIViewController {
var textEffectLabel: TextAnimationLabel!
var changeText:UIButton = UIButton(type: .system)
var backgroundImage:UIImageView = UIImageView()
private var textArray = [
"What is design?",
"Design Code By Swift",
"你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好",
"Design is not just",
"what it looks like",
"and feels like.",
"Hello,Swift",
"is how it works.",
"- Steve Jobs",
"Older people",
"sit down and ask,",
"'What is it?'",
"but the boy asks,",
"展示性文字,展示性文字,展示性文字,展示性文字;",
"'What can I do with it?'.",
"- Steve Jobs",
"Swift",
"Objective-C",
"iPhone",
"iPad",
"Mac Mini",
"MacBook Pro",
"Mac Pro",
"爱老婆"
]
override func viewDidLoad() {
super.viewDidLoad()
self.backgroundViewSetup()
let frame = CGRect(x: 0, y: 100, width: UIScreen.main.bounds.width, height: 100)
self.textEffectLabel = TextAnimationLabel(frame: frame)
self.view.addSubview(self.textEffectLabel)
self.navigationController?.setNavigationBarHidden(true, animated: true)
self.textEffectLabel.font = UIFont.systemFont(ofSize: 18)
self.textEffectLabel.numberOfLines = 5
self.textEffectLabel.textAlignment = .center
self.textEffectLabel.text = "Yes,Hello World"
self.textEffectLabel.textColor = .white
self.textEffectLabel.backgroundColor = Color_Hex_A(value: 0x000000, alpha: 0.5)
self.changeButtonSetup()
print("OutSide:\(String(describing: self.view.action(for: self.view.layer, forKey: "backgroundColor")))")
UIView.beginAnimations(nil, context: nil)
print("InSide:\(String(describing: self.view.action(for: self.view.layer, forKey: "backgroundColor")))")
UIView.commitAnimations()
}
func backgroundViewSetup() {
self.backgroundImage.image = UIImage(named: "2.jpg")
self.backgroundImage.contentMode = .scaleAspectFill
self.backgroundImage.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(self.backgroundImage)
self.view.addConstraints(
[
NSLayoutConstraint(item: self.backgroundImage, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1.0, constant: 0.0),
NSLayoutConstraint(item: self.backgroundImage, attribute: .bottom, relatedBy: .equal, toItem: self.view, attribute: .bottom, multiplier: 1.0, constant: 0.0),
NSLayoutConstraint(item: self.backgroundImage, attribute: .left, relatedBy: .equal, toItem: self.view, attribute: .left, multiplier: 1.0, constant: 0.0),
NSLayoutConstraint(item: self.backgroundImage, attribute: .right, relatedBy: .equal, toItem: self.view, attribute: .right, multiplier: 1.0, constant: 0.0),
]
)
let maskView = UIView()
maskView.alpha = 0.5
maskView.backgroundColor = UIColor(red: 29.0/255.0, green: 29.0/255.0, blue: 29.0/255.0, alpha: 1.0)
maskView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(maskView)
self.view.addConstraints(
[
NSLayoutConstraint(item: maskView, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1.0, constant: 0.0),
NSLayoutConstraint(item: maskView, attribute: .bottom, relatedBy: .equal, toItem: self.view, attribute: .bottom, multiplier: 1.0, constant: 0.0),
NSLayoutConstraint(item: maskView, attribute: .left, relatedBy: .equal, toItem: self.view, attribute: .left, multiplier: 1.0, constant: 0.0),
NSLayoutConstraint(item: maskView, attribute: .right, relatedBy: .equal, toItem: self.view, attribute: .right, multiplier: 1.0, constant: 0.0),
]
)
}
func changeButtonSetup() {
changeText.addTarget(self, action: #selector(clickTextBtn), for: .touchUpInside)
changeText.setTitle("Change Text", for: .normal)
changeText.backgroundColor = .red;
changeText.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(changeText)
self.view.addConstraints(
[
NSLayoutConstraint(item: changeText, attribute: .top, relatedBy: .lessThanOrEqual, toItem: self.textEffectLabel, attribute: .bottom, multiplier: 1.0, constant: 100),
NSLayoutConstraint(item: changeText, attribute: .bottom, relatedBy: .lessThanOrEqual, toItem: self.view, attribute: .bottom, multiplier: 1.0, constant: 100),
NSLayoutConstraint(item: changeText, attribute: .left, relatedBy: .equal, toItem: self.view, attribute: .left, multiplier: 1.0, constant: 50),
NSLayoutConstraint(item: changeText, attribute: .right, relatedBy: .equal, toItem: self.view, attribute: .right, multiplier: 1.0, constant: -50),
]
)
}
@objc func clickTextBtn(sender:UIButton) {
let picture = Int(arc4random_uniform(6))
if picture > 0 && picture < 7 {
self.backgroundImage.image = UIImage(named: "\(picture).jpg")
}
let index = Int(arc4random_uniform(20))
if index < textArray.count {
let text:String = textArray[index]
self.textEffectLabel.text = text
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
动画label
import UIKit
typealias textAnimationClosure = ()->()
typealias effectAnimatableLayerColsure = (_ layer:CALayer) ->CALayer
typealias effectTextAnimationClosure = (layer:CALayer,duration:TimeInterval,delay:TimeInterval,isFinished:Bool)
class TextAnimationLabel: UILabel,NSLayoutManagerDelegate {
var oldCharacterTextLayers:[CATextLayer] = []
var newCharacterTextLayers:[CATextLayer] = []
let textStorage:NSTextStorage = NSTextStorage(string: "")
let textLayoutManager:NSLayoutManager = NSLayoutManager()
let textContainer:NSTextContainer = NSTextContainer()
var animationOut: textAnimationClosure?
var animationIn:textAnimationClosure?
override init(frame: CGRect) {
super.init(frame: frame)
textKitObjectSetup()
}
override func awakeFromNib() {
super.awakeFromNib()
textKitObjectSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
textKitObjectSetup()
fatalError("init(coder:) has not been implemented")
}
func textKitObjectSetup() {
textStorage.addLayoutManager(textLayoutManager)
textLayoutManager.addTextContainer(textContainer)
textLayoutManager.delegate = self
textContainer.size = bounds.size
textContainer.maximumNumberOfLines = numberOfLines
textContainer.lineBreakMode = lineBreakMode
}
override var lineBreakMode:NSLineBreakMode {
get {
return super.lineBreakMode
}
set {
textContainer.lineBreakMode = newValue
super.lineBreakMode = newValue
}
}
override var numberOfLines:Int {
get {
return super.numberOfLines
}
set {
textContainer.maximumNumberOfLines = newValue
super.numberOfLines = newValue
}
}
override var bounds: CGRect {
get {
return super.bounds
}
set {
textContainer.size = newValue.size
super.bounds = newValue
}
}
override var textColor:UIColor! {
get {
return super.textColor
}
set {
super.textColor = newValue
let text = self.textStorage.string
self.text = text
}
}
override var text:String!{
get {
return super.text
}
set {
super.text = text
let attributedText = NSMutableAttributedString(string: newValue)
// let textRange = NSMakeRange(0, newValue.characters.count)
let textRange = NSMakeRange(0, newValue.count)
attributedText.setAttributes([NSAttributedString.Key.foregroundColor:self.textColor!], range: textRange)
attributedText.setAttributes([NSAttributedString.Key.font:self.font!], range: textRange)
let paragraphyStyle = NSMutableParagraphStyle()
paragraphyStyle.alignment = self.textAlignment
attributedText.addAttributes([NSAttributedString.Key.paragraphStyle:paragraphyStyle], range: textRange)
self.attributedText = attributedText
}
}
override var attributedText:NSAttributedString!{
get {
return self.textStorage as NSAttributedString
}
set{
cleanOutOldCharacterTextLayers()
oldCharacterTextLayers = Array(newCharacterTextLayers)
textStorage.setAttributedString(newValue)
self.startAnimation { () -> () in
}
self.endAnimation(animationClosure: nil)
}
}
//MARK:NSLayoutManagerDelegate
func layoutManager(_ layoutManager: NSLayoutManager, didCompleteLayoutFor textContainer: NSTextContainer?, atEnd layoutFinishedFlag: Bool) {
calculateTextLayers()
print("\(textStorage.string)")
}
//MARK:CalculateTextLayer
func calculateTextLayers()
{
// newCharacterTextLayers.removeAll(keepCapacity: false)
newCharacterTextLayers.removeAll()
let attributedText = textStorage.string
let wordRange = NSMakeRange(0, attributedText.count)
let attributedString = self.internalAttributedText();
let layoutRect = textLayoutManager.usedRect(for: textContainer)
var index = wordRange.location
let totalLength = NSMaxRange(wordRange)
while index < totalLength
{
let glyphRange = NSMakeRange(index, 1)
let characterRange = textLayoutManager.characterRange(forGlyphRange: glyphRange, actualGlyphRange: nil)
let textContainer = textLayoutManager.textContainer(forGlyphAt: index, effectiveRange: nil)
var glyphRect = textLayoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer!)
let kerningRange = textLayoutManager.range(ofNominallySpacedGlyphsContaining: index)
if kerningRange.location == index && kerningRange.length > 1 {
if newCharacterTextLayers.count > 0 {
//如果前一个textlayer的frame.size.width不变大的话,当前的textLayer会遮挡住字体的一部分,比如“You”的Y右上角会被切掉一部分
let previousLayer = newCharacterTextLayers[newCharacterTextLayers.endIndex - 1]
var frame = previousLayer.frame
frame.size.width += glyphRect.maxX - frame.maxX
previousLayer.frame = frame
// previousLayer.borderColor = UIColor.blueColor().CGColor
// previousLayer.borderWidth = 4.0
}
}
//中间垂直
glyphRect.origin.y += (self.bounds.size.height/2)-(layoutRect.size.height/2)
//打印 font的metric信息
// let attributedCharacter = attributedString.attributedSubstringFromRange(characterRange)
// let font = attributedCharacter.attribute(NSFontAttributeName, atIndex: 0, effectiveRange: nil) as! UIFont
// print("\ncharacter:\(attributedCharacter.string)||lineHeight:\(font.lineHeight)||capHeight:\(font.capHeight)||descender:\(font.descender)||Ascender:\(font.ascender)||x-height:\(font.xHeight)")
let textLayer = CATextLayer(frame: glyphRect, string: (attributedString?.attributedSubstring(from: characterRange))!);
self.initialTextLayerAttributes(textLayer: textLayer)
// textLayer.borderColor = UIColor.redColor().CGColor
// textLayer.borderWidth = 1.0
layer.addSublayer(textLayer);
newCharacterTextLayers.append(textLayer);
index += characterRange.length
}
}
func cleanOutOldCharacterTextLayers()
{
for textLayer in oldCharacterTextLayers
{
textLayer.removeFromSuperlayer()
}
oldCharacterTextLayers.removeAll()
}
func internalAttributedText() -> NSMutableAttributedString! {
let wordRange = NSMakeRange(0, textStorage.string.count);
let attributedText = NSMutableAttributedString(string: textStorage.string);
attributedText.addAttribute(NSAttributedString.Key.foregroundColor , value: self.textColor.cgColor, range:wordRange);
attributedText.addAttribute(NSAttributedString.Key.font , value: self.font!, range:wordRange);
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = self.textAlignment
attributedText.addAttribute(NSAttributedString.Key.paragraphStyle, value:paragraphStyle, range: wordRange)
return attributedText;
}
func initialTextLayerAttributes(textLayer: CATextLayer) {
textLayer.opacity = 0.0
}
//MARK:TextAnimation
func startAnimation(animationClosure:textAnimationClosure?)
{
var longestAnimationDuration = 0.0
var longestAniamtionIndex = -1
var index = 0
for textlayer in oldCharacterTextLayers
{
// let duration = NSTimeInterval(arc4random()%200/100)+0.25
let duration = (TimeInterval(arc4random()%100)/125.0)+0.35
let delay = TimeInterval(arc4random_uniform(100)/500)
let distance = CGFloat(arc4random()%50)+25
let angle = CGFloat((Double(arc4random())/Double.pi/2)-Double.pi/4)
var transform = CATransform3DMakeTranslation(0, distance, 0)
transform = CATransform3DRotate(transform, angle, 0, 0, 1)
if delay+duration > longestAnimationDuration
{
longestAnimationDuration = delay+duration
longestAniamtionIndex = index
}
TELayerAniamtion.textLayerAnimation(layer: textlayer, durationTime: duration, delayTime: delay, animationClosure: { (layer) -> CALayer in
layer.transform = transform
layer.opacity = 0.0
return layer
}, completion: {[weak self](finished) -> () in
textlayer.removeFromSuperlayer()
if let textLayers = self?.oldCharacterTextLayers
{
if textLayers.count > longestAniamtionIndex && textlayer == textLayers[longestAniamtionIndex]
{
if let animationOut = animationClosure
{
animationOut()
}
}
}
})
index += 1
}
}
func endAnimation(animationClosure:textAnimationClosure?)
{
for textLayer in newCharacterTextLayers
{
// textLayer.opacity = 0.0
let duration = TimeInterval(arc4random()%200/100)+0.25
let delay = 0.06//NSTimeInterval(arc4random_uniform(100)/500)
TELayerAniamtion.textLayerAnimation(layer: textLayer, durationTime: duration, delayTime: delay, animationClosure: { (layer) -> CALayer in
layer.opacity = 1.0
return layer
}, completion: { (finished) -> () in
if let animationIn = animationClosure {
animationIn()
}
})
}
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func drawRect(rect: CGRect) {
// Drawing code
}
*/
}
//MARK: CATextLayer extension
extension CATextLayer {
convenience init(frame:CGRect, string: NSAttributedString)
{
self.init()
// self.contentsScale = UIScreen.mainScreen().scale
self.contentsScale = UIScreen.main.scale
self.frame = frame
self.string = string
}
}
layer
import UIKit
typealias completionClosure = (_ finished:Bool)->()
private let textAnimationGroupKey = "textAniamtionGroupKey"
class TELayerAniamtion: NSObject, CAAnimationDelegate {
var completionBLK:completionClosure? = nil
var textLayer:CALayer?
class func textLayerAnimation(layer:CALayer, durationTime duration:TimeInterval, delayTime delay:TimeInterval,animationClosure effectAnimation:effectAnimatableLayerColsure?, completion finishedClosure:completionClosure?) -> Void
{
let animationObjc = TELayerAniamtion()
DispatchQueue.main.asyncAfter(deadline: .now(), execute: {
let olderLayer = animationObjc.animatableLayerCopy(layer: layer)
var newLayer:CALayer?
var animationGroup:CAAnimationGroup?
animationObjc.completionBLK = finishedClosure
if let effectAnimationClosure = effectAnimation {
//改变Layer的properties,同时关闭implicit animation
CATransaction.begin()
CATransaction.setDisableActions(true)
newLayer = effectAnimationClosure(layer)
CATransaction.commit()
}
animationGroup = animationObjc.groupAnimationWithLayerChanges(old: olderLayer, new: newLayer!)
if let textAniamtionGroup = animationGroup
{
animationObjc.textLayer = layer
textAniamtionGroup.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
textAniamtionGroup.beginTime = CACurrentMediaTime()
textAniamtionGroup.duration = duration
textAniamtionGroup.delegate = animationObjc
layer.add(textAniamtionGroup, forKey: textAnimationGroupKey)
}else
{
if let completion = finishedClosure
{
completion(true)
}
}
})
// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) { () -> Void in
//
//
// }
//
}
func groupAnimationWithLayerChanges(old olderLayer:CALayer, new newLayer:CALayer) -> CAAnimationGroup?
{
var animationGroup:CAAnimationGroup?
var animations:[CABasicAnimation] = [CABasicAnimation]()
if !olderLayer.position.equalTo(newLayer.position) {
let basicAnimation = CABasicAnimation()
basicAnimation.fromValue = NSValue(cgPoint: olderLayer.position)
basicAnimation.toValue = NSValue(cgPoint:newLayer.position)
basicAnimation.keyPath = "position"
animations.append(basicAnimation)
}
if !CATransform3DEqualToTransform(olderLayer.transform, newLayer.transform) {
let basicAnimation = CABasicAnimation(keyPath: "transform")
basicAnimation.fromValue = NSValue(caTransform3D: olderLayer.transform)
basicAnimation.toValue = NSValue(caTransform3D: newLayer.transform)
animations.append(basicAnimation)
}
if !olderLayer.frame.equalTo(newLayer.frame)
{
let basicAnimation = CABasicAnimation(keyPath: "frame")
basicAnimation.fromValue = NSValue(cgRect: olderLayer.frame)
basicAnimation.toValue = NSValue(cgRect: newLayer.frame)
animations.append(basicAnimation)
}
if !olderLayer.bounds.equalTo(olderLayer.bounds)
{
let basicAnimation = CABasicAnimation(keyPath: "bounds")
basicAnimation.fromValue = NSValue(cgRect: olderLayer.bounds)
basicAnimation.toValue = NSValue(cgRect: newLayer.bounds)
animations.append(basicAnimation)
}
if olderLayer.opacity != newLayer.opacity
{
let basicAnimation = CABasicAnimation(keyPath: "opacity")
basicAnimation.fromValue = olderLayer.opacity
basicAnimation.toValue = newLayer.opacity
animations.append(basicAnimation)
}
if animations.count > 0 {
animationGroup = CAAnimationGroup()
animationGroup!.animations = animations
}
return animationGroup
}
func animatableLayerCopy(layer:CALayer)->CALayer
{
let layerCopy = CALayer()
layerCopy.opacity = layer.opacity
layerCopy.bounds = layer.bounds
layerCopy.transform = layer.transform
layerCopy.position = layer.position
return layerCopy
}
//MARK:animationDelegate
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
if let tempCompletionBLK = self.completionBLK
{
self.textLayer?.removeAnimation(forKey: textAnimationGroupKey)
tempCompletionBLK(flag)
}
}
}