Создайте UITextRange из NSRange

Мне нужно найти пиксельную рамку для разных диапазонов в текстовом представлении. Я использую - (CGRect)firstRectForRange:(UITextRange *)range; для этого. Однако я не могу понять, как на самом деле создать файл UITextRange.

В основном это то, что я ищу:

- (CGRect)frameOfTextRange:(NSRange)range inTextView:(UITextView *)textView {

    UITextRange*range2 = [UITextRange rangeWithNSRange:range]; //DOES NOT EXIST 
    CGRect rect = [textView firstRectForRange:range2];
    return rect;
}

Apple говорит, что нужно создать подклассы UITextRange и UITextPosition, чтобы принять протокол UITextInput. Я этого не делаю, но я все равно попытался, следуя примеру кода документа и передав подкласс firstRectForRange, что привело к сбою.

Если есть более простой способ добавить разноцветные UILables в текстовое представление, скажите, пожалуйста. Я пытался использовать UIWebView с content editable, установленным в TRUE, но я не люблю общаться с JS, и раскрашивание — это единственное, что мне нужно.

Заранее спасибо.


person Johannes Lund    schedule 03.02.2012    source источник
comment
Вы должны создать подкласс UITextRange, чтобы установить его. Единственный способ установить свойства UITextRange — получить к ним доступ в подклассе. Он определяет start и end как свойства, но их можно установить внутри, ссылаясь на _start и _end.   -  person Justin    schedule 04.02.2012
comment
Да, но как мне тогда создать UITextPosition, у которого вообще нет никаких свойств? 0.o Если я также подклассирую его, как 'firstRectForRange' может знать, какое свойство использовать из моего подкласса UITextPosition?   -  person Johannes Lund    schedule 05.02.2012
comment
Это то, чего я не знаю. Все, что я знаю об этом, это то, что вам нужно создать подкласс, чтобы установить readonly свойства. Вот почему это комментарий, а не ответ.   -  person Justin    schedule 05.02.2012


Ответы (6)


Вы можете создать текстовый диапазон с помощью метода textRangeFromPosition:toPosition. Этот метод требует две позиции, поэтому вам нужно вычислить позиции для начала и конца вашего диапазона. Это делается с помощью метода positionFromPosition:offset, который возвращает позицию из другой позиции и смещение символа.

- (CGRect)frameOfTextRange:(NSRange)range inTextView:(UITextView *)textView
{
    UITextPosition *beginning = textView.beginningOfDocument;
    UITextPosition *start = [textView positionFromPosition:beginning offset:range.location];
    UITextPosition *end = [textView positionFromPosition:start offset:range.length];
    UITextRange *textRange = [textView textRangeFromPosition:start toPosition:end];
    CGRect rect = [textView firstRectForRange:textRange];
    return [textView convertRect:rect fromView:textView.textInputView];
}
person Nicolas Bachschmidt    schedule 07.03.2012
comment
У меня есть небольшое замечание. Это работает только в том случае, если TextField является первым ответчиком. В противном случае все позиции будут NULL, а прямоугольник соответственно будет иметь нулевой размер. Если у кого-нибудь есть подсказка, как можно получить прямоугольник без того, чтобы TextField был первым ответчиком, поделитесь им с нами. - person thomas; 05.08.2012
comment
Потрясающий! Мне очень помог. @thomas для меня это работает без того, чтобы UITextView был первым ответчиком (у меня есть UITextView только для чтения внутри UITableViewCell). - person mluisbrown; 05.05.2013
comment
Я обнаружил ту же проблему, что и Томас: textView.beginningOfDocument равен нулю, если textView не является первым ответившим. Поскольку все остальное в этом примере, который, кстати, отлично работает, основано на beginOfDocument, все ваши вычисления в конечном итоге отправляются в нулевые объекты. - person JasonD; 09.05.2013
comment
текстовая система НАМНОГО переработана - person Daij-Djan; 07.10.2013
comment
Это здорово, но может ли кто-нибудь указать, как я могу получить прямоугольную часть только ВИДИМОЙ части этого. Например, если диапазон представляет собой вложение изображения, верхняя часть которого не видна (прокручивается над верхней границей UITextView). Есть ли простой способ получить пересечение (?) CGRect выше и видимого прямоугольника UITextView? - person Duncan Groenewald; 14.11.2013
comment
У меня была аналогичная проблема с отсутствием значения для beginningOfDocument. В моем случае у меня было UITextView как selectable = NO. Требовалось изменить это на YES... - person Gavin Hope; 24.03.2014
comment
Это работает, но только если textView/Field является firstResponder (в настоящее время редактируется), иначе beginningOfDocument будет nil. В качестве обходного пути установите его в качестве первого ответившего, а затем resignFirstResponder как можно скорее. Клавиатура не будет мигать в пользовательском интерфейсе. - person Jordan H; 15.05.2014

Немного смешно, что кажется таким сложным. Простым «обходным решением» было бы выбрать диапазон (принимает NSRange), а затем прочитать selectedTextRange (возвращает UITextRange):

- (CGRect)frameOfTextRange:(NSRange)range inTextView:(UITextView *)textView {
    textView.selectedRange = range;
    UITextRange *textRange = [textView selectedTextRange]; 
    CGRect rect = [textView firstRectForRange:textRange];
    return rect;
}

Это сработало для меня, даже если textView не является первым ответчиком.


Если вы не хотите, чтобы выбор сохранялся, вы можете сбросить selectedRange:

textView.selectedRange = NSMakeRange(0, 0);

...или сохранить текущий выбор и восстановить его позже

NSRange oldRange = textView.selectedRange;
// do something
// then check if the range is still valid and
textView.selectedRange = oldRange;
person auco    schedule 05.10.2013
comment
в iOS 7 текстовое представление должно быть доступно для выбора, в противном случае selectedTextRange: возвращает nil - person ZeCodea; 28.11.2013
comment
Это до смешного просто... и даже если его нельзя выбрать, оно все равно работает на iOS 7. - person brandonscript; 12.03.2014
comment
Всегда лучше позволить SDK сделать всю работу за вас. Молодец! - person miken.mkndev; 21.10.2014

Swift 4 ответа Эндрю Шрайбера для простого копирования/вставки

extension NSRange {
    func toTextRange(textInput:UITextInput) -> UITextRange? {
        if let rangeStart = textInput.position(from: textInput.beginningOfDocument, offset: location),
            let rangeEnd = textInput.position(from: rangeStart, offset: length) {
            return textInput.textRange(from: rangeStart, to: rangeEnd)
        }
        return nil
    }
}
person CodenameDuchess    schedule 17.09.2017

На вопрос заголовка, вот расширение Swift 2, которое создает UITextRange из NSRange.

Единственным инициализатором для UITextRange является метод экземпляра протокола UITextInput, поэтому расширение также требует передачи UITextInput, например UITextField или UITextView.

extension NSRange {
    func toTextRange(textInput textInput:UITextInput) -> UITextRange? {
        if let rangeStart = textInput.positionFromPosition(textInput.beginningOfDocument, offset: location),
            rangeEnd = textInput.positionFromPosition(rangeStart, offset: length) {
            return textInput.textRangeFromPosition(rangeStart, toPosition: rangeEnd)
        }
        return nil
    }
}
person Andrew Schreiber    schedule 18.05.2016

Swift 4 ответа Николаса Бахшмидта как расширение UITextView с использованием swifty Range<String.Index> вместо NSRange:

extension UITextView {
    func frame(ofTextRange range: Range<String.Index>?) -> CGRect? {
        guard let range = range else { return nil }
        let length = range.upperBound.encodedOffset-range.lowerBound.encodedOffset
        guard
            let start = position(from: beginningOfDocument, offset: range.lowerBound.encodedOffset),
            let end = position(from: start, offset: length),
            let txtRange = textRange(from: start, to: end)
            else { return nil }
        let rect = self.firstRect(for: txtRange)
        return self.convert(rect, to: textInputView)
    }
}

Возможное использование:

guard let rect = textView.frame(ofTextRange: text.range(of: "awesome")) else { return }
let awesomeView = UIView()
awesomeView.frame = rect.insetBy(dx: -3.0, dy: 0)
awesomeView.layer.borderColor = UIColor.black.cgColor
awesomeView.layer.borderWidth = 1.0
awesomeView.layer.cornerRadius = 3
self.view.insertSubview(awesomeView, belowSubview: textView)
person VictorN    schedule 09.03.2018

Вот объяснение.

Объект UITextRange представляет диапазон символов в текстовом контейнере; другими словами, он идентифицирует начальный индекс и конечный индекс в строке, поддерживающей объект ввода текста.

Классы, использующие протокол UITextInput, должны создавать настраиваемые объекты UITextRange для представления диапазонов в тексте, управляемом классом. Начальный и конечный индексы диапазона представлены объектами UITextPosition. Текстовая система использует объекты UITextRange и UITextPosition для передачи информации о макете текста. Есть две причины для использования объектов для текстовых диапазонов, а не примитивных типов, таких как NSRange:

Некоторые документы содержат вложенные элементы (например, теги HTML и встроенные объекты), и вам необходимо отслеживать как абсолютную позицию, так и позицию в видимом тексте.

Платформа WebKit, на которой основана текстовая система iPhone, требует, чтобы текстовые индексы и смещения были представлены объектами.

Если вы принимаете протокол UITextInput, вы должны создать собственный подкласс UITextRange, а также собственный подкласс UITextPosition.

Например, как в этих источниках

person Volodymyr B.    schedule 24.09.2014