文字动画效果

新建俩个类:

DNOLabelAnimation : UILabel

DNOSingleText : NSObject

DNOLabelAnimation.h

#import <UIKit/UIKit.h>

typedef enum {
    DNOLabelAnimationTypeNormal = 0,
    DNOLabelAnimationTypeWave   = 1
}DNOLabelAnimationType;

@interface DNOLabelAnimation : UILabel
@property (nonatomic, copy) NSString           *text;
@property (nonatomic, copy) NSAttributedString *attributedText;

@property (nonatomic, assign) DNOLabelAnimationType type;

@property (nonatomic, assign) CGFloat    animationHeight;
@property (nonatomic, assign) NSUInteger rate; // 1 is fastest 10 is slowest, default is 2
@property (nonatomic, assign) CGFloat    kerning;

- (instancetype)initWithFrame:(CGRect)frame text:(NSString *)text;
- (instancetype)initWithFrame:(CGRect)frame attributedText:(NSAttributedString *)attributedText;

- (void)startAnimation;
- (void)pauseAnimation;
- (void)stopAnimation;

@end

DNOLabelAnimation.m

#import "DNOLabelAnimation.h"
#import "DNOSingleText.h"

//static int loopCount_ = -1;

@interface DNOLabelAnimation ()

@property (nonatomic, strong) NSMutableArray <DNOSingleText *>*allText;
@property (nonatomic, strong) CADisplayLink *time;
@property (nonatomic, assign) int loopCount;

@property (nonatomic, assign) int steps;

@end

@implementation DNOLabelAnimation

@synthesize text = _text;
@synthesize attributedText = _attributedText;

- (instancetype)initWithCoder:(NSCoder *)coder
{
    self = [super initWithCoder:coder];
    if (self) {
        [self prepare];
        [self setNeedsDisplay];
    }
    return self;
}

- (instancetype)initWithFrame:(CGRect)frame attributedText:(NSAttributedString *)attributedText
{
    self = [super initWithFrame:frame];
    if (self) {
        [self prepare];
        self.attributedText = attributedText;
    }
    return self;
}

- (instancetype)initWithFrame:(CGRect)frame text:(NSString *)text
{
    self = [super initWithFrame:frame];
    if (self) {
        [self prepare];
        self.text = text;
    }
    return self;
}

- (void)prepare
{
    self.loopCount = -1;
}

- (void)sizeToFit
{
    [self setFrame:({
        CGFloat x = self.frame.origin.x;
        CGFloat y = self.frame.origin.y;
        CGFloat width = self.kerning * self.allText.count;
        CGFloat height = self.font.ascender - self.font.descender + self.animationHeight;
        CGRectMake(x, y, width, height);
    })];
}
- (void)start
{
    self.steps = 0;
    self.steps ++;
    if (self.steps % 3 == 0 || self.steps % 3 == 1 ) {
        [self setNeedsDisplay];
    }
    if (self.steps > 1000) {
        self.steps = 0;
    }
}


- (void)startAnimation
{
    [self.time invalidate];
    self.time = [CADisplayLink displayLinkWithTarget:self selector:@selector(start)];
    [self.time addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}

- (void)pauseAnimation
{
    [self.time invalidate];
    self.time = nil;
}

- (void)stopAnimation
{
    [self.time invalidate];
    self.time = nil;
    self.loopCount = -1;
    [self.allText makeObjectsPerformSelector:@selector(normalReset)];
    [self.allText makeObjectsPerformSelector:@selector(waveReset)];
    [self setNeedsDisplay];
}


- (void)drawRect:(CGRect)rect {
    __block CGFloat x = 0;
    __weak typeof(self) weakSelf = self;
    [self.allText enumerateObjectsUsingBlock:^(DNOSingleText *singleText, NSUInteger idx, BOOL *stop) {
        CGFloat location = 0;
        
        // 仅仅在能第一次遍历玩所有的文字的时候调用
        if (self.loopCount == idx * weakSelf.rate) {
            location = [singleText locationWithFirstEnumerate];
        }else{
            // 让其他的文字在一开始的时候不要动
            location = 0;
        }
        // 已经被遍历过的标志
        if (singleText.enumerated) {
            // 判断动画模式
            if (weakSelf.type) {
                location = [singleText locationWithWaveAnimationCompletion:^{
                    if (idx == weakSelf.allText.count - 1) {
                        [weakSelf.allText makeObjectsPerformSelector:@selector(waveReset)];
                        self.loopCount = -1;
                    }
                }];
            } else {
                location = [singleText locationWithNormalAnimation];
            }
        }
        CGFloat trueLocation = weakSelf.animationHeight - location;

        CGRect trueRect = CGRectMake(x, trueLocation, weakSelf.textSize, weakSelf.textSize);
        NSMutableDictionary *attibutes = [NSMutableDictionary dictionary];
        attibutes[NSFontAttributeName] = weakSelf.font;
        attibutes[NSForegroundColorAttributeName] = singleText.textColor;
        [singleText.text drawInRect:trueRect withAttributes:attibutes];
        x += weakSelf.kerning;
    }];
    self.loopCount ++;

}

#pragma mark -
#pragma mark setter && getter

- (CGFloat)textSize
{
    return self.font.pointSize + self.animationHeight;
}

- (void)setText:(NSString *)text
{
    _text = text;
    
    [self.allText removeAllObjects];
    for (int i = 0; i < text.length; ++i) {
        
        DNOSingleText *singleText = [DNOSingleText singleTextWithAnimationRange: text.length];
        singleText.text = [text substringWithRange:NSMakeRange(i, 1)];
        [self.allText addObject:singleText];
    }
    [self sizeToFit];
    [self setNeedsDisplay];
}

- (void)setAttributedText:(NSAttributedString *)attributedText
{
    [super setAttributedText:attributedText];
    [self.allText removeAllObjects];
    for (int i = 0; i < attributedText.length; ++i) {
        
        DNOSingleText *singleText = [DNOSingleText singleTextWithAnimationRange: attributedText.length];
        singleText.text = [attributedText.string substringWithRange:NSMakeRange(i, 1)];
        NSDictionary *attributed = [attributedText attributesAtIndex:i effectiveRange:nil];
        singleText.textColor = attributed[NSForegroundColorAttributeName];
        [self.allText addObject:singleText];
    }
    [self sizeToFit];
    [self setNeedsDisplay];
}

- (void)setType:(DNOLabelAnimationType)type
{
    _type = type;
    self.loopCount = -1;
    [self.allText makeObjectsPerformSelector:@selector(normalReset)];
    [self.allText makeObjectsPerformSelector:@selector(waveReset)];
}

- (CGFloat)kerning
{
    return _kerning + self.font.pointSize;
}


- (NSUInteger)rate
{
    if (!_rate) {
        _rate = 2;
    }
    if (_rate < 1) {
        _rate = 1;
    }
    if (_rate > 10) {
        _rate = 10;
    }
    return _rate;
}

//- (UIFont *)font
//{
//    if (![super font]) {
//        return [UIFont systemFontOfSize:15];
//    }
//    return [super font];
//}

@synthesize animationHeight = _animationHeight;

- (void)setAnimationHeight:(CGFloat)animationHeight
{
    _animationHeight = animationHeight + self.font.pointSize;
}

- (CGFloat)animationHeight
{
    if (!_animationHeight) {
        _animationHeight = self.font.pointSize;
    }
    return _animationHeight;
}

- (NSMutableArray<DNOSingleText *> *)allText
{
    if (!_allText) {
        _allText = [@[] mutableCopy];
    }
    return _allText;
}

@end

DNOSingleText.h

#import <UIKit/UIKit.h>

@interface DNOSingleText : NSObject

@property (nonatomic, copy  ) NSString *text;
@property (nonatomic, strong) UIColor  *textColor;
@property (nonatomic, assign) CGFloat  animationRange;
@property (nonatomic, assign) BOOL     enumerated;

+ (instancetype)singleTextWithAnimationRange:(CGFloat)animationRange;

- (CGFloat)locationWithFirstEnumerate;

- (CGFloat)locationWithNormalAnimation;
- (void)normalReset;

- (CGFloat)locationWithWaveAnimationCompletion:(void(^)())completion;
- (void)waveReset;

@end

DNOSingleText.m

#import "DNOSingleText.h"
#define myRandomColor [UIColor colorWithRed:arc4random()%256/255.0 green:arc4random()%256/255.0 blue:arc4random()%256/255.0 alpha:1]

@interface DNOSingleText ()
{
    CGFloat _normalSteps;
    CGFloat _waveSteps;
    BOOL    _directionChange;
}

@end

@implementation DNOSingleText

+ (instancetype)singleTextWithAnimationRange:(CGFloat)animationRange
{
    DNOSingleText *obj = [[self alloc] init];
    obj.animationRange = animationRange;
    return obj;
}

- (void)calculateStep
{
    _directionChange ? _normalSteps-- : _normalSteps++;
    
    if (_normalSteps > self.animationRange) {
        _directionChange = YES;
    } else if (_normalSteps < 0) {
        _directionChange = NO;
    }
}

- (CGFloat)locationWithNormalAnimation
{
    [self calculateStep];
    return _normalSteps;
}

- (CGFloat)locationWithFirstEnumerate
{
    [self calculateStep];
    _normalSteps += 1;
    if (_normalSteps > self.animationRange) {
        _normalSteps = self.animationRange - (_normalSteps - self.animationRange);
    }
    self.enumerated = YES;
    return _normalSteps;
}

- (CGFloat)locationWithWaveAnimationCompletion:(void (^)())completion
{
    _waveSteps++;
    if (_waveSteps > 2 * self.animationRange) {
        completion();
        return 0;
    }
    return [self locationWithNormalAnimation];
}

- (void)normalReset
{
    self.enumerated  = NO;
    _directionChange = NO;
    _normalSteps     = 0;
}

- (void)waveReset
{
    _waveSteps = 0;
    [self normalReset];
}


@end

然后在需要的地方调用

#import "ViewController.h"
#import "DNOLabelAnimation.h"

#define myRandomColor [UIColor colorWithRed:arc4random()%256/255.0 green:arc4random()%256/255.0 blue:arc4random()%256/255.0 alpha:1]
#define myWidth  [UIScreen mainScreen].bounds.size.width
#define myHeight [UIScreen mainScreen].bounds.size.height

@interface ViewController ()
@property (nonatomic, strong) NSMutableArray *textViews;
@property (nonatomic, assign) DNOLabelAnimationType type;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    [self demo1];
    [self demo2];
    [self demo3];
    [self demo4];
    [self demo5];
}
- (void)demo1{
    
    NSDictionary *attribute = @{NSForegroundColorAttributeName : myRandomColor};
    NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:@"一一一一一一一一一一一一一" attributes:attribute];
    
    [string addAttribute:NSForegroundColorAttributeName value:myRandomColor range:NSMakeRange(2, 2)];
    [string addAttribute:NSForegroundColorAttributeName value:myRandomColor range:NSMakeRange(4, 2)];
    [string addAttribute:NSForegroundColorAttributeName value:myRandomColor range:NSMakeRange(6, 2)];
    [string addAttribute:NSForegroundColorAttributeName value:myRandomColor range:NSMakeRange(8, 2)];
    
    DNOLabelAnimation *textView = [[DNOLabelAnimation alloc] initWithFrame:CGRectMake(20, 60, 0, 0) attributedText:string];
    
    [textView sizeToFit];
    [self.view addSubview:textView];
    [self.textViews addObject:textView];
    [self getButtonWithPoint:CGPointMake(myWidth - 60, 60) tag:1];
    
    
    
    
}
- (void)getButtonWithPoint:(CGPoint)point tag:(NSInteger)tag
{
    UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
    button.frame = CGRectMake(point.x, point.y, 60, 30);
    [button addTarget:self action:@selector(buttonDidClick:) forControlEvents:UIControlEventTouchUpInside];
    [button setTitle:@"start" forState:UIControlStateNormal];
    [button setTitle:@"pause" forState:UIControlStateSelected];
    [self.view addSubview:button];
    button.tag = tag;
}

- (void)buttonDidClick:(UIButton *)sender
{
    sender.selected = !sender.selected;
    if (sender.selected) {
        [self.textViews[sender.tag - 1] startAnimation];
    }else{
        [self.textViews[sender.tag - 1] stopAnimation];
        //[self.textViews[sender.tag - 1] pauseAnimation];
    }
}
- (NSMutableArray *)textViews
{
    if (!_textViews) {
        _textViews = [[NSMutableArray alloc] init];
    }
    return _textViews;
}

- (void)demo2 {
    NSDictionary *attribute = @{NSForegroundColorAttributeName : myRandomColor};
    NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:@"ABCDEFGHIJ" attributes:attribute];
    
    DNOLabelAnimation *textView = [[DNOLabelAnimation alloc] initWithFrame:CGRectMake(20, 120, 0, 0) attributedText:string];
    
    textView.type = self.type;
    textView.font = [UIFont systemFontOfSize:30];
    [textView sizeToFit];
    
    [self.view addSubview:textView];
    [self.textViews addObject:textView];
    
    [self getButtonWithPoint:CGPointMake(myWidth - 60, 120) tag:2];
}

- (void)demo3 {
    NSDictionary *attribute = @{NSForegroundColorAttributeName : myRandomColor};
    NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:@"abcdefghij" attributes:attribute];
    DNOLabelAnimation *textView = [[DNOLabelAnimation alloc] initWithFrame:CGRectMake(20, 180, 0, 0) attributedText:string];
    
    textView.type = self.type;
    textView.font = [UIFont fontWithName:@"Courier" size:30];
    textView.rate = 1;
    [textView sizeToFit];
    [self.view addSubview:textView];
    [self.textViews addObject:textView];
    
    [self getButtonWithPoint:CGPointMake(myWidth - 60, 180) tag:3];
}

- (void)demo4 {
    
    DNOLabelAnimation *textView = [[DNOLabelAnimation alloc] initWithFrame:CGRectMake(20, 240, 0, 0) text:@"黑化肥会挥发"];
    
    textView.type = self.type;
    textView.font = [UIFont fontWithName:@"Courier" size:30];
    textView.rate = 10;
    [textView sizeToFit];
    [self.view addSubview:textView];
    [self.textViews addObject:textView];
    
    [self getButtonWithPoint:CGPointMake(myWidth - 60, 240) tag:4];
}

- (void)demo5 {
    
    DNOLabelAnimation *textView = [[DNOLabelAnimation alloc] initWithFrame:CGRectMake(20, 300, 0, 0) text:@"做人呢,最重要的就是不能让别人开心."];
    
    textView.type = self.type;
    textView.font = [UIFont systemFontOfSize:10];
    textView.kerning = 4;
    [textView sizeToFit];
    [self.view addSubview:textView];
    [self.textViews addObject:textView];
    
    [self getButtonWithPoint:CGPointMake(myWidth - 60, 300) tag:5];
}
原文地址:https://www.cnblogs.com/LzwBlog/p/5814844.html