最近做項目的時候遇到一個需求何乎,由于App兼容的語言類型太多句惯,導致App內(nèi)很多標簽顯示的文字不全,這時候需要滾動去顯示支救。需求類似下面這個樣子抢野。
思考
1.整個App的label去做一個滾動顯示,首先要考慮到性能問題各墨,性能問題的話就不考慮使用UIView層去實現(xiàn)該效果指孤,因此,文字滾動層考慮用Layer去處理贬堵。
2.能夠影響到文本顯示和是否能完全顯示文本的屬性有text恃轩、frame、font黎做、textColor叉跛,因此我們需要對這幾項屬性進行處理。
在設(shè)置text的屬性的時候蒸殿,我們需要判斷text的長度是否大于label的長度筷厘, 如果大于label的長度鸣峭,我們需要將text文本處理到layer層,并且做滾動動畫酥艳。在這是frame和font的時候我們需要重新做該判斷摊溶,因為這兩個屬性影響了label的長度和文本的大小,設(shè)置textColor的時候我們需要把layer上的文本顏色也相應修改成該顏色玖雁。
實現(xiàn)
創(chuàng)建一個UILabel的子類更扁,并且添加CATextLayer的屬性
import UIKit
class LCAutoScrollLabel: UILabel {
private var textLayer : CATextLayer = CATextLayer()
private var shouldScroll : Bool = false
}
重寫以上四個屬性的set方法,加上判斷是否需要滾動的邏輯
override var text: String? {
didSet {
shouldScroll = false
///把String轉(zhuǎn)化成NSString赫冬,根據(jù)文本和Font得到文本的Size
let textString : NSString = NSString(string: self.text ?? "")
let size = textString.size(withAttributes: [NSAttributedString.Key.font : self.font!])
let stringWidth = size.width
let labelWidth = frame.size.width
if labelWidth < stringWidth {
shouldScroll = true
}
if shouldScroll
{
/// 如果判斷需要滾動浓镜,設(shè)置textLayer的屬性
let textString : NSString = self.text as NSString? ?? ""
let size = textString.size(withAttributes: [NSAttributedString.Key.font : self.font!])
let stringWidth = size.width
let stringHeight = size.height
textLayer.frame = CGRect(x: 0, y: (frame.height - stringHeight)/2, width: stringWidth, height: stringHeight)
textLayer.string = text
textLayer.alignmentMode = .center
textLayer.font = font
textLayer.fontSize = font.pointSize
textLayer.foregroundColor = self.textColor.cgColor
/// 動畫
let ani = CABasicAnimation(keyPath: "position.x")
ani.toValue = -textLayer.frame.width
ani.fromValue = textLayer.frame.width
ani.duration = 4
ani.fillMode = .backwards
ani.repeatCount = 1000000000.0
ani.isRemovedOnCompletion = false
textLayer.add(ani, forKey: nil)
layer.addSublayer(textLayer)
}
else
{
/// 如果不需要滾動,移除動畫和layer
textLayer.removeAllAnimations()
textLayer.removeFromSuperlayer()
}
}
}
因為四個重寫的set方法里面邏輯基本一致劲厌,整理一下代碼
override var text: String? {
didSet {
shouldScroll = shouldAutoScroll()
setTextLayerScroll()
}
}
override var font: UIFont! {
didSet {
shouldScroll = shouldAutoScroll()
setTextLayerScroll()
}
}
override var frame: CGRect {
didSet {
shouldScroll = shouldAutoScroll()
setTextLayerScroll()
}
}
override var textColor: UIColor! {
didSet {
textLayer.foregroundColor = textColor.cgColor
}
}
func setTextLayerScroll() {
if shouldScroll
{
setTextLayer()
textLayer.add(getLayerAnimation(), forKey: nil)
layer.addSublayer(textLayer)
}
else
{
textLayer.removeAllAnimations()
textLayer.removeFromSuperlayer()
}
}
func shouldAutoScroll() -> Bool {
var shouldScroll = false
let textString : NSString = NSString(string: self.text ?? "")
let size = textString.size(withAttributes: [NSAttributedString.Key.font : self.font!])
let stringWidth = size.width
let labelWidth = frame.size.width
if labelWidth < stringWidth {
shouldScroll = true
}
return shouldScroll
}
func setTextLayer() {
let textString : NSString = self.text as NSString? ?? ""
let size = textString.size(withAttributes: [NSAttributedString.Key.font : self.font!])
let stringWidth = size.width
let stringHeight = size.height
textLayer.frame = CGRect(x: 0, y: (frame.height - stringHeight)/2, width: stringWidth, height: stringHeight)
textLayer.string = text
textLayer.alignmentMode = .center
textLayer.font = font
textLayer.fontSize = font.pointSize
textLayer.foregroundColor = self.textColor.cgColor
}
func getLayerAnimation() -> CABasicAnimation {
let ani = CABasicAnimation(keyPath: "position.x")
ani.toValue = -textLayer.frame.width
ani.fromValue = textLayer.frame.width
ani.duration = 4
ani.fillMode = .backwards
ani.repeatCount = 1000000000.0
ani.isRemovedOnCompletion = false
return ani
}
測試結(jié)果得到如下結(jié)果膛薛,因為忘記處理label自己本身顯示的文本。
我們可以出重寫drawText的方法补鼻,根據(jù)是否需要滾動的判斷去決定是否要顯示text
override func drawText(in rect: CGRect) {
if !shouldScroll
{
super.drawText(in: rect)
}
}
經(jīng)過簡單測試哄啄,無論是改變font、frame风范、text都能夠正常判斷并且達到想要的效果咨跌。
但是有一個新的問題來了,由于項目是老項目硼婿,代碼量已經(jīng)十分龐大锌半,如果要一個一個去把以前使用UILabel的地方全部替換成這個類,是一項非常耗時的工作寇漫,由于工期緊張刊殉,顯然是給不了我這么多時間的。這時候我們就要用到我們的黑魔法Runtime了
黑魔法Runtime修改系統(tǒng)方法州胳,快速達到目的
我們創(chuàng)建一個UILabel的分類记焊,并且根據(jù)我們現(xiàn)有代碼的邏輯,得到如下代碼
#import "UILabel+AutoScroll.h"
#import <objc/runtime.h>
static NSString * textLayer = @"textLayer";
static NSString * scrollAnimation = @"scrollAnimation";
@implementation UILabel (AutoScroll)
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
/// 替換系統(tǒng)方法栓撞,我們需要重寫的方法我們都要替換
Method setTextMethod = class_getInstanceMethod(self, @selector(setText:));
Method setColorMethod = class_getInstanceMethod(self, @selector(setTextColor:));
Method setFontMethod = class_getInstanceMethod(self, @selector(setFont:));
Method setFrameMethod = class_getInstanceMethod(self, @selector(setFrame:));
Method drawTextMethon = class_getInstanceMethod(self, @selector(drawTextInRect:));
Method scrollSetTextMethod = class_getInstanceMethod(self, @selector(autoScrollSetText:));
Method scrollSetColorMethod = class_getInstanceMethod(self, @selector(autoScrollSetTextColor:));
Method scrollSetFontMethod = class_getInstanceMethod(self, @selector(autoScrollSetFont:));
Method scrollSetFrameMethod = class_getInstanceMethod(self, @selector(autoScrollSetFrame:));
Method scrollDrawText = class_getInstanceMethod(self, @selector(autoScrollDrawText:));
method_exchangeImplementations(setTextMethod, scrollSetTextMethod);
method_exchangeImplementations(setColorMethod, scrollSetColorMethod);
method_exchangeImplementations(setFontMethod, scrollSetFontMethod);
method_exchangeImplementations(setFrameMethod, scrollSetFrameMethod);
method_exchangeImplementations(drawTextMethon, scrollDrawText);
});
}
/// 用于替換系統(tǒng)setText方法
/// @param text 標簽顯示的文字
- (void)autoScrollSetText:(NSString *)text
{
[self autoScrollSetText:text];
// 這句是為了讓textlayer超出label的部分不顯示
self.layer.masksToBounds = true;
[self setTextLayerScroll];
}
/// 用于替換系統(tǒng)setTextColor方法
/// @param color 文字顏色
- (void)autoScrollSetTextColor:(UIColor *)color
{
[self autoScrollSetTextColor:color];
[self setTextLayerScroll];
}
/// 用于替換系統(tǒng)的setFont方法
/// @param font 字體
- (void)autoScrollSetFont:(UIFont *)font
{
[self autoScrollSetFont:font];
[self setTextLayerScroll];
}
/// 用于替換系統(tǒng)的setFrame方法
/// @param frame 坐標
- (void)autoScrollSetFrame:(CGRect)frame
{
[self autoScrollSetFrame:frame];
[self setTextLayerScroll];
}
/// 用于替換系統(tǒng)的drawText方法
/// @param rect frame
- (void)autoScrollDrawText:(CGRect)rect
{
BOOL shouldScroll = [self shouldAutoScroll];
if (!shouldScroll)
{
[self autoScrollDrawText:rect];
}
}
/// 根據(jù)文字長短自動判斷是否需要顯示TextLayer遍膜,并且滾動
- (void)setTextLayerScroll
{
BOOL shouldScroll = [self shouldAutoScroll];
CATextLayer * textLayer = [self getTextLayer];
if (shouldScroll)
{
CABasicAnimation * ani = [self getAnimation];
[textLayer addAnimation:ani forKey:nil];
[self.layer addSublayer:textLayer];
}
else
{
[textLayer removeAllAnimations];
[textLayer removeFromSuperlayer];
}
}
/// runtime存放textLayer,避免多次生成
- (CATextLayer *)getTextLayer
{
CATextLayer * layer = objc_getAssociatedObject(self, &textLayer);
if (!layer) {
layer = [CATextLayer layer];
objc_setAssociatedObject(self, &textLayer, layer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
CGSize size = [self.text sizeWithAttributes:@{NSFontAttributeName:self.font}];
CGFloat stringWidth = size.width;
CGFloat stringHeight = size.height;
layer.frame = CGRectMake(0, (self.frame.size.height - stringHeight)/2, stringWidth, stringHeight);
layer.alignmentMode = kCAAlignmentCenter;
layer.font = (__bridge CFTypeRef _Nullable)(self.font.fontName);
layer.fontSize = self.font.pointSize;
layer.foregroundColor = self.textColor.CGColor;
layer.string = self.text;
return layer;
}
/// runtime存放動畫對象瓤湘,避免多次生成
- (CABasicAnimation *)getAnimation
{
CABasicAnimation * ani = objc_getAssociatedObject(self, &scrollAnimation);
if (!ani) {
ani = [CABasicAnimation animationWithKeyPath:@"position.x"];
objc_setAssociatedObject(self, &scrollAnimation, ani, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
CATextLayer * textLayer = [self getTextLayer];
id toValue = @(-textLayer.frame.size.width);
id fromValue = @(textLayer.frame.size.width);
ani.toValue = toValue;
ani.fromValue = fromValue;
ani.duration = 4;
ani.fillMode = @"backwards";
ani.repeatCount = 1000000000.0;
ani.removedOnCompletion = false;
return ani;
}
/// 判斷是否需要滾動
- (BOOL)shouldAutoScroll
{
BOOL shouldScroll = false;
CGSize size = [self.text sizeWithAttributes:@{NSFontAttributeName:self.font}];
CGFloat stringWidth = size.width;
CGFloat labelWidth = self.frame.size.width;
if (labelWidth < stringWidth) {
shouldScroll = true;
}
return shouldScroll;
}
@end
最后在pch文件中導入該分類瓢颅,項目中所有的UILabel就能自動判斷并且實現(xiàn)滾動顯示的效果。
大家可以試一試岭粤,如果有問題可以相互交流一下惜索。
手擼代碼不易,如果對您有幫助的剃浇,可以幫忙點個贊巾兆!謝謝