Заменить текст UITextViews на атрибутную строку

У меня есть UITextView, и когда пользователь вводит в него текст, я хочу отформатировать текст "на лету". Что-то вроде подсветки синтаксиса...

Для этого я хотел бы использовать UITextView...

Все работает нормально, ожидайте одну проблему: я беру текст из текстового представления и делаю из него NSAttributedString. Я делаю некоторые изменения в этой атрибутной строке и устанавливаю ее как textView.attributedText.

Это происходит каждый раз, когда пользователь вводит данные. Поэтому я должен запомнить selectedTextRange перед редактированием attributedText и установить его обратно, чтобы пользователь мог продолжить вводить текст в том месте, которое он печатал раньше. Единственная проблема заключается в том, что после того, как текст достаточно длинный, чтобы потребовать прокрутки, UITextView теперь начнет прокрутку вверх, если я напечатаю медленно.

Вот пример кода:

- (void)formatTextInTextView:(UITextView *)textView
{
  NSRange selectedRange = textView.selectedRange;
  NSString *text = textView.text;

  // This will give me an attributedString with the base text-style
  NSMutableAttributedString *attributedString = [self attributedStringFromString:text];

  NSError *error = nil;
  NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"#(\\w+)" options:0 error:&error];
  NSArray *matches = [regex matchesInString:text
                                    options:0
                                      range:NSMakeRange(0, text.length)];

  for (NSTextCheckingResult *match in matches)
  {
    NSRange matchRange = [match rangeAtIndex:0];
    [attributedString addAttribute:NSForegroundColorAttributeName
                             value:[UIColor redColor]
                             range:matchRange];
  }

  textView.attributedText = attributedString;
  textView.selectedRange = selectedRange;
}

Есть ли какое-либо решение без непосредственного использования CoreText? Мне нравится возможность UITextView выбирать текст и т.д....

Ответ 1

Я не уверен, что это правильное решение, но оно работает.
Просто отключите прокрутку перед форматированием текста и включите его после форматирования

- (void)formatTextInTextView:(UITextView *)textView
{
    textView.scrollEnabled = NO;
    NSRange selectedRange = textView.selectedRange;
    NSString *text = textView.text;

    // This will give me an attributedString with the base text-style
    NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:text];

    NSError *error = nil;
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"#(\\w+)" options:0 error:&error];
    NSArray *matches = [regex matchesInString:text
                                      options:0
                                        range:NSMakeRange(0, text.length)];

    for (NSTextCheckingResult *match in matches)
    {
        NSRange matchRange = [match rangeAtIndex:0];
        [attributedString addAttribute:NSForegroundColorAttributeName
                                 value:[UIColor redColor]
                                 range:matchRange];
    }

    textView.attributedText = attributedString;
    textView.selectedRange = selectedRange;
    textView.scrollEnabled = YES;
}

Ответ 2

Используемые Sergeys отвечали на меня и переносили на Swift 2:

func formatTextInTextView(textView: UITextView) {
    textView.scrollEnabled = false
    let selectedRange = textView.selectedRange
    let text = textView.text

    // This will give me an attributedString with the base text-style
    let attributedString = NSMutableAttributedString(string: text)

    let regex = try? NSRegularExpression(pattern: "#(\\w+)", options: [])
    let matches = regex!.matchesInString(text, options: [], range: NSMakeRange(0, text.characters.count))

    for match in matches {
        let matchRange = match.rangeAtIndex(0)
        attributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.redColor(), range: matchRange)
    }

    textView.attributedText = attributedString
    textView.selectedRange = selectedRange
    textView.scrollEnabled = true
}

Ответ 3

Swift 2.0:

let myDisplayTxt:String = "Test String"

    let string: NSMutableAttributedString = NSMutableAttributedString(string: self.myDisplayTxt)
    string.addAttribute(NSForegroundColorAttributeName, value: UIColor.redColor(), range: NSMakeRange(0, 5))
    string.addAttribute(String(kCTForegroundColorAttributeName), value: UIColor.redColor().CGColor as AnyObject, range: NSMakeRange(0, 5))

    self.sampleTextView.attributedText =  string