对于每个文字的处理是很复杂的一个工作,今天我们就先说一个文字掉落的效果,先看张效果图。
这个是通过 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地址