objective-c ios5 uitextview nsrange

objective c - Crear UITextRange desde NSRange



objective-c ios5 (6)

A la pregunta del título, aquí hay una extensión de Swift 2 que crea un UITextRange desde un NSRange.

El único inicializador para UITextRange es un método de instancia en el protocolo UITextInput, por lo que la extensión también requiere que pase en UITextInput como UITextField o 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 } }

Necesito encontrar el marco de píxeles para diferentes rangos en una vista de texto. Estoy usando el - (CGRect)firstRectForRange:(UITextRange *)range; para hacerlo. Sin embargo, no puedo averiguar cómo crear realmente un UITextRange .

Básicamente esto es lo que estoy buscando:

- (CGRect)frameOfTextRange:(NSRange)range inTextView:(UITextView *)textView { UITextRange*range2 = [UITextRange rangeWithNSRange:range]; //DOES NOT EXIST CGRect rect = [textView firstRectForRange:range2]; return rect; }

Apple dice que uno tiene que subclasificar UITextRange y UITextPosition para adoptar el protocolo UITextInput . No hago eso, pero lo intenté de todos modos, siguiendo el código de ejemplo del doc y pasando la subclase a firstRectForRange que resultó en firstRectForRange .

Si hay una manera más fácil de agregar UILables diferentes colores a una vista de texto, por favor dígame. He intentado usar UIWebView con el content editable establecido en TRUE, pero no me gusta comunicarme con JS, y colorear es lo único que necesito.

Gracias por adelantado.


Es un poco ridículo que parece ser tan complicado. Una simple "solución alternativa" sería seleccionar el rango (acepta NSRange) y luego leer el SelectedTextRange (devuelve UITextRange):

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

Esto funcionó para mí, incluso si textView no es el primero en responder.

Si no desea que la selección persista, puede restablecer el Rango seleccionado:

textView.selectedRange = NSMakeRange(0, 0);

... o guardar la selección actual y restaurarla después

NSRange oldRange = textView.selectedRange; // do something // then check if the range is still valid and textView.selectedRange = oldRange;


Puede crear un rango de texto con el método textRangeFromPosition:toPosition . Este método requiere dos posiciones, por lo que debe calcular las posiciones para el inicio y el final de su rango. Esto se hace con el método positionFromPosition:offset , que devuelve una posición desde otra posición y un desplazamiento de caracteres.

- (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]; }


Swift 4 de la respuesta de Andrew Schreiber para copiar / pegar fácilmente

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 } }


Swift 4 de la respuesta de Nicolas Bachschmidt como una extensión UITextView utilizando Range<String.Index> lugar de 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) } }

Posible uso:

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)


Aquí está la explicación.

Un objeto UITextRange representa un rango de caracteres en un contenedor de texto; en otras palabras, identifica un índice inicial y un índice final en una cadena que respalda un objeto de entrada de texto.

Las clases que adoptan el protocolo UITextInput deben crear objetos personalizados UITextRange para representar los rangos dentro del texto administrado por la clase. Los índices inicial y final del rango están representados por objetos UITextPosition. El sistema de texto utiliza los objetos UITextRange y UITextPosition para comunicar información de diseño de texto. Hay dos razones para usar objetos para rangos de texto en lugar de tipos primitivos como NSRange:

Algunos documentos contienen elementos anidados (por ejemplo, etiquetas HTML y objetos incrustados) y necesita realizar un seguimiento de la posición y la posición absolutas en el texto visible.

El marco WebKit, en el que se basa el sistema de texto del iPhone, requiere que los índices y compensaciones de texto estén representados por objetos.

Si adopta el protocolo UITextInput, debe crear una subclase personalizada UITextRange, así como una subclase personalizada UITextPosition.

Por ejemplo, como en esas fuentes