2012-02-14 8 views
10

Mi objetivo es marcar todas las palabras mal escritas visibles en un UITextView.¿Cómo encontrar eficientemente CGRects para palabras visibles en UITextView?

El algoritmo ineficiente es utilizar el corrector ortográfico para buscar todos los rangos de palabras mal escritas en el texto, convertirlos a objetos UITextRange utilizando positionFromPosition: inDirection: offset etc, luego obtener los rects de gráficos utilizando el método UITextInput firstRectFromRange.

lo tanto todo el texto -> mal escrito palabras-> colección NSRange -> colección UITextRange -> colección CGRect -> evaluar la visibilidad, dibujar las visibles

El problema es que esto requiere que todo el texto está marcada, y todas las palabras mal escritas se convierten a rects gráficos.

Por lo tanto, me imagino que el camino a seguir es encontrar de alguna manera las partes del texto subyacente en el UITextView que está visible en este momento.

Así, por rango de texto visible -> mal escrito palabras-> colección NSRange -> colección UITextRange -> colección CGRect -> evaluar la visibilidad, dibujar las visibles

El código en ios - how to find what is the visible range of text in UITextView? podría funcionar como una forma de cota qué partes del texto revisar, pero todavía requiere que se mida todo el texto, lo que imagino que podría ser bastante costoso.

¿Alguna sugerencia?

Respuesta

18

Dado que UITextView es una subclase de UIScrollView, su propiedad bounds refleja la parte visible de su sistema de coordenadas. Entonces algo como esto debería funcionar:

- (NSRange)visibleRangeOfTextView:(UITextView *)textView { 
    CGRect bounds = textView.bounds; 
    UITextPosition *start = [textView characterRangeAtPoint:bounds.origin].start; 
    UITextPosition *end = [textView characterRangeAtPoint:CGPointMake(CGRectGetMaxX(bounds), CGRectGetMaxY(bounds))].end; 
    return NSMakeRange([textView offsetFromPosition:textView.beginningOfDocument toPosition:start], 
     [textView offsetFromPosition:start toPosition:end]); 
} 

Esto supone un diseño de texto de arriba a abajo, de izquierda a derecha. Si desea que funcione para otras instrucciones de diseño, tendrá que trabajar más duro. :)

+0

Niza. Para mayor eficiencia, busque el fin moviendo caracteres de longitud hacia delante desde el inicio; resulta que es lento desde el principio en un documento, pero es barato avanzar. –

+0

Cuando dices "algo como esto debería funcionar", ¿lo has intentado de verdad? Esto está devolviendo casi el triple del rango del texto visual real para mí. Gracias. –

+1

@JasonTyler [Aquí hay un repositorio que contiene una aplicación de prueba] (https://github.com/mayoff/textview-visible-range) Funciona muy bien para mí. Probablemente requiere Xcode 6. –

3

Respuesta de Rob, escrita en Swift. He agregado algunos controles de seguridad.

private func visibleRangeOfTextView(textView: UITextView) -> NSRange { 
    let bounds = textView.bounds 
    let origin = CGPointMake(10,10) //Overcome the default UITextView left/top margin 
    let startCharacterRange = textView.characterRangeAtPoint(origin) 
    if startCharacterRange == nil { 
     return NSMakeRange(0,0) 
    } 
    let startPosition = textView.characterRangeAtPoint(origin)!.start 

    let endCharacterRange = textView.characterRangeAtPoint(CGPointMake(CGRectGetMaxX(bounds), CGRectGetMaxY(bounds))) 
    if endCharacterRange == nil { 
     return NSMakeRange(0,0) 
    } 
    let endPosition = textView.characterRangeAtPoint(CGPointMake(CGRectGetMaxX(bounds), CGRectGetMaxY(bounds)))!.end 

    let startIndex = textView.offsetFromPosition(textView.beginningOfDocument, toPosition: startPosition) 
    let endIndex = textView.offsetFromPosition(startPosition, toPosition: endPosition) 
    return NSMakeRange(startIndex, endIndex) 
} 

Actualizado para Swift 4:

private func visibleRangeOfTextView(textView: UITextView) -> NSRange { 
    let bounds = textView.bounds 
    let origin = CGPoint(x: 10, y: 10) //Overcome the default UITextView left/top margin 
    let startCharacterRange = textView.characterRange(at: origin) 
    if startCharacterRange == nil { 
     return NSRange(location: 0, length: 0) 
    } 
    let startPosition = textView.characterRange(at: origin)?.start 

    let endCharacterRange = textView.characterRange(at: CGPoint(x: bounds.maxX, y: bounds.maxY)) 
    if endCharacterRange == nil { 
     return NSRange(location: 0, length: 0) 
    } 
    let endPosition = textView.characterRange(at: CGPoint(x: bounds.maxX, y: bounds.maxY))!.end 

    let startIndex = textView.offset(from: textView.beginningOfDocument, to: startPosition!) 
    let endIndex = textView.offset(from: startPosition!, to: endPosition) 
    return NSRange(location: startIndex, length: endIndex) 
} 

Ejemplo de uso, llamado de un grifo botón:

@IBAction func buttonTapped(sender: AnyObject) { 
    let range = visibleRangeOfTextView(self.textView) 

    // Note: "as NSString" won't work correctly with Emoji and stuff, 
    // see also: http://stackoverflow.com/a/24045156/1085556 
    let nsText = self.textView.text as NSString 
    let text = nsText.substringWithRange(range) 
    NSLog("range: \(range), text = \(text)")   
} 
Cuestiones relacionadas