Swift文字翻转

Swift

920 发布: 2022/1/29 11:00 本文总阅读量

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