iOS 文字掉落效果 CATextLayer

對於每個文字的處理是很複雜的一個工作,今天我們就先說一個文字掉落的效果,先看張效果圖。

 

這個是通過 NSTextStorage,NSLayoutManager,NSTextContainer以及CATextLayer實現的。我們先看代碼:

設備:xcode 10.1

語言:swift 4.2 

  • NSTextStorage
Note for subclassing NSTextStorage: NSTextStorage is a semi-abstract subclass of NSMutableAttributedString. It implements change management (beginEditing/endEditing), verification of attributes, delegate handling, and layout management notification. The one aspect it does not implement is the actual attributed string storage --- this is left up to the subclassers, which need to override the two NSMutableAttributedString primitives in addition to two NSAttributedString primitives:
     
     - (NSString *)string;
     - (NSDictionary *)attributesAtIndex:(NSUInteger)location effectiveRange:(NSRangePointer)range;
    
     - (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str;
     - (void)setAttributes:(NSDictionary *)attrs range:(NSRange)range;
     
     These primitives should perform the change then call edited:range:changeInLength: to get everything else to happen.

大意思是說他主要是保存文字信息屬性,但是他並不真正的保存文字,需要我們自己來實現這幾個方法,他是NSMutableAttributedString的子類。

  • NSLayoutManager
NSLayoutManager maps Unicode character codes to glyphs, sets the glyphs in a series of NSTextContainer objects, and displays them in a series of NSTextView objects. In addition to its core function of laying out text, a layout manager object coordinates its text view objects, provides services to those text views to support NSRulerView instances for editing paragraph styles, and handles the layout and display of text attributes not inherent in glyphs (such as underline or strikethrough). You can create a subclass of NSLayoutManager to handle additional text attributes, whether inherent or not.

這個是文字排版及文字內容的佈局。

  • NSTextContainer
An NSLayoutManager uses NSTextContainer to determine where to break lines, lay out portions of text, and so on. An NSTextContainer object typically defines rectangular regions, but you can define exclusion paths inside the text container to create regions where text does not flow. You can also subclass to create text containers with nonrectangular regions, such as circular regions, regions with holes in them, or regions that flow alongside graphics.

Instances of the NSTextContainer, NSLayoutManager, and NSTextStorage classes can be accessed from threads other than the main thread as long as the app guarantees access from only one thread at a time.

這個是需要顯示文字的區域。

代碼實現:

class TextAnimationView: UIView , NSLayoutManagerDelegate,CAAnimationDelegate{

    private let textStorage = NSTextStorage(string: "");
    private let textLayout = NSLayoutManager();
    private let textContainer = NSTextContainer();
    
    private var textLayers = [CALayer]();
    private var textLayerPostions = [CGPoint]();
    
    var text: String!{
        didSet{
            let textA = NSAttributedString(string: text, attributes: [NSAttributedString.Key.foregroundColor:UIColor.red,NSAttributedString.Key.font:UIFont.fitSize(size: 20)]);
            textStorage.setAttributedString(textA);

        }
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame);
        
       
        
        textStorage.addLayoutManager(textLayout);
        textLayout.addTextContainer(textContainer);
        textLayout.delegate = self;
        textContainer.size = bounds.size;
        textContainer.maximumNumberOfLines = 0;
        
    }

    
    
    
    func layoutManager(_ layoutManager: NSLayoutManager, didCompleteLayoutFor textContainer: NSTextContainer?, atEnd layoutFinishedFlag: Bool) {
        
        
        if layoutFinishedFlag {
            calculateLayer();
        }
        
    }
    
    func calculateLayer() -> Void {
        textLayers.removeAll();
        textLayerPostions.removeAll();
        if text == nil {
            return;
        }
        
        var index = 0;
        
        while index < text.count {
            
            let glyRang = NSRange(location: index, length: 1);
            
            let count = textStorage.attributedSubstring(from: glyRang);
            

            let charRange = textLayout.characterRange(forGlyphRange: glyRang, actualGlyphRange: nil);
            let glyRect = textLayout.boundingRect(forGlyphRange: glyRang, in: textContainer);
            
            let textLayer = CATextLayer();
            textLayer.string = count;
            textLayer.frame = glyRect;
            textLayer.backgroundColor = UIColor.clear.cgColor;
            textLayer.contentsScale = iOSIPhoneInfoData.scale;
            layer.addSublayer(textLayer);
            textLayers.append(textLayer);
            textLayerPostions.append(textLayer.position);

            index += charRange.length;

        }
    }
    
    func runTextAnimationPostion() -> Void {
        
        for (idx,item) in textLayers.enumerated() {
            item.position = textLayerPostions[idx];
            item.removeAllAnimations();
        }
        
        for (index,item) in textLayers.enumerated() {
            let position = textLayerPostions[index];
            
            let point = CGPoint(x: item.position.x, y: -100);
            item.position = point;
            let animation = CABasicAnimation(keyPath: "position");
            animation.fromValue = point
            animation.toValue = position;
            let duration = Double(10.1 / Double(textLayers.count));
            animation.duration = duration;
            animation.repeatCount = 1;
            animation.beginTime = CACurrentMediaTime() + Double(index) * duration;
            animation.fillMode = .forwards;
            animation.isRemovedOnCompletion = false;
            item.add(animation, forKey: "\(index)");
            
            
        }
        
    }

    
    func runTextAnimation() -> Void {
        runTextAnimationPostion();
        return;
        for item in textLayers {
            var transform = CATransform3DRotate(item.transform, .pi/4, 0, 0, 1);
            transform.m34 = 0.1/400.0;
            
            let animation = CABasicAnimation(keyPath: "transform");
            animation.fromValue = transform//CGPoint(x: CGFloat(arc4random_uniform(UInt32(width))), y: CGFloat(arc4random_uniform(UInt32(height))));
            animation.toValue = item.transform;
            animation.duration = 4;
            animation.repeatCount = 1;
            animation.fillMode = .forwards;
            animation.isRemovedOnCompletion = false;
            item.add(animation, forKey: "123");
            
        }
        
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
}

調用:

textLabel = TextAnimationView(frame: bounds().insetBy(dx: 20, dy: 80));
addSubview(subView: textLabel);
textLabel.backgroundColor = UIColor.white;
textLabel.text = "1.要渲染展示的內容。2.將內容渲染在某個視圖上。3.內容渲染在視圖上的尺寸位置和形狀。在TextKit框架中,提供了幾個類分別對應處理上述的必要條件:1.NSTextStorage對應要渲染展示的內容。2.UITextView對應要渲染的視圖。";

運行:

textLabel.runTextAnimation();

最後:demo地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章