UITextView attributedText and syntax highlighting
Asked Answered
P

2

6

Background

So, with iOS 6 an UITextView can take an attributedString, which could be useful for Syntax highlighting.

I'm doing some regex patterns in -textView:shouldChangeTextInRange:replacementText: and oftentimes I need to change the color of a word already typed. I see no other options than resetting the attributedText, which takes time.

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
    //A context will allow us to not call -attributedText on the textView, which is slow.
    //Keep context up to date
    [self.context replaceCharactersInRange:range withAttributedString:[[NSAttributedString alloc] initWithString:text attributes:self.textView.typingAttributes]];

    // […]

    self.textView.scrollEnabled = FALSE;

    [self.context setAttributes:self.defaultStyle range:NSMakeRange(0, self.context.length)];
    [self refresh]; //Runs regex-patterns in the context
    textView.attributedText = self.context;

    self.textView.selectedRange = NSMakeRange(range.location + text.length, 0);
    self.textView.scrollEnabled = TRUE;

    return FALSE;
}

This runs okayish on the simulator, but on an iPad 3 each -setAttributedText takes a few hundreds of milliseconds.

I filed a bug to Apple, with the request of being able to mutate the attributedText. It got marked as a duplicate, so I cannot see what they're saying about this.

The question

The more specific question: How can I change the color of certain ranges in a UITextView, with a large multicolored text, with good enough performance to do it in every shouldReplaceText...?

The more broad question: How do you do syntax highlighting with a UITextView in iOS 6?

Plano answered 22/9, 2012 at 11:49 Comment(5)
I am also trying to highlight the text being spoken in my web speaker app. I thought I would be able to do it easily with iOS6, but I couldn't find the api to change string color in certain range dynamically.. Setting full attributed text every time takes too much time.Puppet
I also filed a bug report to Apple. We need something like this method. "textView_.attributedText addAttribute: value:font range:"Puppet
Yes, but I bet we'll never see a mutable version of the attributedText, since the textView needs to know whenever the string is changed. I would hope for either a smarter setter, or an attributed version of UITextInput's -replaceRange: withText:Plano
That sounds right, we can only set nsstring into that "withText" now, so let's hope apple will change it.Puppet
If the text styling is purely cosmetic, you may not have to update the entire text view every single time the user types something. You could for instance use an NSTimer to update the attributed text maybe every 0.5 seconds.Rus
N
0

The attributedText accessors have to round-trip to/from HTML, so it's really non-optimal for a syntax-highlighted text view implementation. On iOS 6, you'll probably want to use CoreText directly.

Needlefish answered 13/10, 2012 at 5:32 Comment(1)
I don't know exactly how the textView's HTML works, but you could change attributes as easy as replacing some text in the DOM and updating the textView's text, couldn't you?Plano
S
1

I encountered the same problem for my application Zap-Guitar (No-Strings-Attached) where I allow users to type/paste/edit their own songs and the app highlights recognized chords.

Yes it is true apple uses an html writer and parser to display the attributed text. A wonderful explanation of behind the scene can be found here: http://www.cocoanetics.com/2012/12/uitextview-caught-with-trousers-down/

The only solution I found for this problem is not to use attributed text which is an overkill for syntax highlighting.

Instead I reverted to the good old UITextView with plain text and added buttons to the text view where highlighted was needed. To compute the buttons frames I used this answer: How to find position or get rect of any word in textview and place buttons over that?

This reduced CPU usage by 30% (give or take).

Here is a handy category:

@implementation UITextView (WithButtons)
- (CGRect)frameForTextRange:(NSRange)range {
    UITextPosition *beginning = self.beginningOfDocument;
    UITextPosition *start = [self positionFromPosition:beginning offset:range.location];
    UITextPosition *end = [self positionFromPosition:start offset:range.length];
    UITextRange *textRange = [self textRangeFromPosition:start toPosition:end];
    CGRect rect = [self firstRectForRange:textRange];
    return [self convertRect:rect fromView:self.textInputView];
}

@end
Sequestrate answered 4/6, 2013 at 12:3 Comment(0)
N
0

The attributedText accessors have to round-trip to/from HTML, so it's really non-optimal for a syntax-highlighted text view implementation. On iOS 6, you'll probably want to use CoreText directly.

Needlefish answered 13/10, 2012 at 5:32 Comment(1)
I don't know exactly how the textView's HTML works, but you could change attributes as easy as replacing some text in the DOM and updating the textView's text, couldn't you?Plano

© 2022 - 2024 — McMap. All rights reserved.