3

Here's my situation:

I have an NSMutableAttributedString with no attributes in a text view. Whenever the user presses the backspace key, I do not want a character to be deleted, I want it to be struck through, just like the "Track Changes" feature of productivity suites. I want the user to be able to continue typing normally after that. Here's how I started out:

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
if (text.length == 0 && textView.text.length == 0) return YES;

if (text.length == 0 && !([[textView.text substringFromIndex:textView.text.length - 1] isEqualToString:@" "] || [[textView.text substringFromIndex:textView.text.length - 1] isEqualToString:@"\n"])) {

    textView.attributedText = [self strikeText:textView.attributedText];

    textView.selectedRange = NSMakeRange(textView.attributedText.length - 1, 0);

    return NO;
}
return YES;
}

- (NSAttributedString *)strikeText:(NSAttributedString *)text
{
NSRange range;
NSMutableAttributedString *returnValue = [[NSMutableAttributedString alloc] initWithAttributedString:text];

if (![text attribute:NSStrikethroughStyleAttributeName atIndex:text.length - 1 effectiveRange:&range]) {
    NSLog(@"%@", NSStringFromRange(range));
    NSLog(@"%@", [text attribute:NSStrikethroughStyleAttributeName atIndex:text.length - 1 effectiveRange:&range]);

    [returnValue addAttribute:NSStrikethroughStyleAttributeName value:@(NSUnderlineStyleSingle) range:NSMakeRange(text.length - 1, 1)];
    [returnValue addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(text.length - 1, 1)];
}
else {
    [returnValue addAttribute:NSStrikethroughStyleAttributeName value:@(NSUnderlineStyleSingle) range:NSMakeRange(range.location - 1, 1)];
    [returnValue addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(range.location - 1, 1)];
}

[returnValue removeAttribute:NSStrikethroughStyleAttributeName range:NSMakeRange(returnValue.length, 1)];

return returnValue;


}

However, no matter how hard I think, I can't wrap my head around the situation. This code doesn't work, or works partially. The value returned by attribute: atIndex: effectiveRange: is always nil, doesn't matter if the attribute actually exists or not. The effective range is out of bounds of the text I have.

Please help me out here.

2 Answers 2

6

In your strikeText: method you're only checking the very end of your attributedString. If you want to check the last character you should check from text.length -2, assuming that text is long enough. Also you're removeAttribute in the end of the method does not make much sense too me.

A simple approach on how you can reuse the range from the Delegate-Protocol to strike only the characters you need:

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
    // check if your replacement is going to be empty, therefor deleting
    if ([text length] == 0) {

        // don't strike spaces and newlines, could use NSCharacterSet here
        NSString *textToDelete = [textView.text substringWithRange:range];
        if (![@[ @" ", @"\n" ] containsObject:textToDelete]) {
            textView.attributedText = [self textByStrikingText:textView.attributedText inRange:range];
        }

        textView.selectedRange = NSMakeRange(range.location, 0);

        return NO;
    }

    return YES;
}

- (NSAttributedString *)textByStrikingText:(NSAttributedString *)text inRange:(NSRange)range
{    
    NSMutableAttributedString *strickenText = [[NSMutableAttributedString alloc] initWithAttributedString:text];

    [strickenText addAttribute:NSStrikethroughStyleAttributeName value:@(NSUnderlineStyleSingle) range:range];
    [strickenText addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:range];

    return strickenText;
}

There might be more edge cases but this is a simple approach that does what you want.

Sign up to request clarification or add additional context in comments.

2 Comments

Unfortunately, your code too has problems. I want the cursor to be at the end of the struck through text. And if I type anything else after some text has been struck through, the newly inserted text too is struck through.
I explained the reason for your problem with attribute:atIndex:effectiveRange: in the first part. The code given should not be the full solution but a direction for you to continue with.
5

You should try:

NSMutableAttributedString *str = [[NSMutableAttributedString alloc] initWithString:@"Hello. That is a test"];
[str addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:22] range:NSMakeRange(2,1)];
[str addAttribute:NSForegroundColorAttributeName value:[UIColor greenColor] range:NSMakeRange(8,2)];
[str addAttribute:NSForegroundColorAttributeName value:[UIColor blueColor] range:NSMakeRange(18,2)];

And attributes are described here: https://developer.apple.com/documentation/foundation/nsattributedstringkey?language=objc

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.