Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSMutableAttributedString strike-through with animation

- (void)setStrokeLabel:(BOOL)strokeLabel
{
    _strokeLabel = strokeLabel;

    if (_strokeLabel) {
        _timer = [NSTimer scheduledTimerWithTimeInterval:0.4 target:self selector:@selector(setStrokeThrough) userInfo:nil repeats:NO];
    } else {
        [self cancelStrokeThrough];
    }
}

- (void)setStrokeThrough
{
    NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithAttributedString:self.attributedText];

    for (NSUInteger i = 1; i <= [attributedString length]; i++) {
        [attributedString addAttribute:NSStrikethroughStyleAttributeName
                                 value:[NSNumber numberWithInt:1]
                                 range:NSMakeRange(0, i)];
        self.attributedText = attributedString;
    }
}

- (void)cancelStrokeThrough
{
    NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithAttributedString:self.attributedText];
    [attributedString removeAttribute:NSStrikethroughStyleAttributeName
                                range:NSMakeRange(0, [attributedString length])];
    self.attributedText = attributedString;

}

I want to animate strike-through, like todo done animation. As I set timer for it, the timer only handle how to show the stoke through letter by letter??

like image 800
andyleehao Avatar asked Jan 31 '26 04:01

andyleehao


1 Answers

Here are two functions which do the job.

    BOOL setStrokethrough(UILabel *label, NSRange range)
    {
        if (range.location >= [label.attributedText length])
            return FALSE;

        if (range.location + range.length > [label.attributedText length])
            range.length = [label.attributedText length] - range.location;

        NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithAttributedString:label.attributedText];

        [attributedString addAttribute:NSStrikethroughStyleAttributeName
                                 value:@(NSUnderlineStyleSingle)
                                 range:range];

        label.attributedText = attributedString;
        return TRUE;
    }

    -(void)animateSetStrokethroughDuration:(float)duration
    {
        __block float const stepDuration = 0.1;
        float steps = duration / stepDuration;
        __block NSRange range = NSMakeRange(0, ceil((float)[self.label.attributedText length] / steps));

        void (^__block fn)();
        void (^__block __weak weakfn)();

        weakfn = fn = ^(){
            if (!setStrokethrough(self.label, range))
                return;
            range = NSMakeRange(range.location + range.length, range.length);
            [self performBlock:weakfn afterDelay:stepDuration];
        };
        fn();
    }

Notes

  1. To put a limit on animation time I animate by blocks of characters, rather than charater by character. The longer the string the longer the block of characters.
  2. __block __weak business explained in here
  3. self performBlock is an extension of NSObject by Mike Ash and also here
  4. The code assumes you have self.label member defined
like image 197
dmitri Avatar answered Feb 02 '26 19:02

dmitri