Obtenga información de línea de UITextView y NSLayoutManager

Para admitir el protocolo UIAccessibilityReadingContent , necesito que mi UITextView me responda preguntas acerca de sus líneas. Estos son los methods del protocolo que necesito implementar:

  • accessibilityLineNumberForPoint: <- Proporcionó una coorderada, devuelve un número de línea
  • accessibilityContentForLineNumber: <- Devuelve el text de una línea determinada
  • accessibilityFrameForLineNumber: <- Dado un número de línea, devuelve su marco
  • accessibilityPageContent <- Todo el contenido del text. Eso tengo. 🙂

Me imagino que NSLayoutManager puede ayudarme, pero no soy tan experimentado con eso. He pensado algo de eso (creo), pero aún necesito ayuda.

Apple tiene un código de ejemplo ( aquí ) que puede get el número de líneas en la vista de text:

 NSLayoutManager *layoutManager = [textView layoutManager]; unsigned numberOfLines, index, numberOfGlyphs = [layoutManager numberOfGlyphs]; NSRange lineRange; for (numberOfLines = 0, index = 0; index < numberOfGlyphs; numberOfLines++){ (void) [layoutManager lineFragmentRectForGlyphAtIndex:index effectiveRange:&lineRange]; index = NSMaxRange(lineRange); } 

Me imagino que con lineRange arriba, puedo calcular los rectos de línea usando este método en NSLayoutManager :

 - (NSRect)boundingRectForGlyphRange:(NSRange)glyphRange inTextContainer:(NSTextContainer *)container 

Y dado los lineRanges , debería ser capaz de calcular el número de línea para un punto usando (encontrando el lineRange que contiene el índice de glifo:

 - (NSUInteger)glyphIndexForPoint:(CGPoint)point inTextContainer:(NSTextContainer *)container fractionOfDistanceThroughGlyph:(CGFloat *)partialFraction 

Entonces, ¿qué queda es, cómo obtengo el contenido de una línea (como un NSString ), dado un número de línea?

pensando en una solución fácil, aquí hay un pequeño código que reproduce lo que necesita

 - (void)analyse:(UITextView *)textView { NSLayoutManager *layoutManager = [textView layoutManager]; NSString *string = textView.text; unsigned numberOfLines, index, stringLength = [string length]; NSMutableArray *ranges = [NSMutableArray new]; NSMutableArray *frames = [NSMutableArray new]; for (index = 0, numberOfLines = 0; index < stringLength; numberOfLines++) { NSRange tmprange; NSRange range = [string lineRangeForRange:NSMakeRange(index, 0)]; CGRect rect = [layoutManager lineFragmentRectForGlyphAtIndex:index effectiveRange:&tmprange]; [ranges addObject:[NSValue valueWithRange:range]]; [frames addObject:[NSValue valueWithCGRect:rect]]; index = NSMaxRange(range); } self.ranges = ranges; self.frames = frames; self.numberOfLines = numberOfLines; } 

Eche un vistazo a las properties:

 self.ranges = ranges; self.frames = frames; self.numberOfLines = numberOfLines; 

Puede tener lo siguiente en su class para crear estas properties:

 @property (nonatomic) NSInteger numberOfLines; @property (strong, nonatomic) NSArray *ranges; @property (strong, nonatomic) NSArray *frames; 

Sugiero que agregue la llamada de análisis dentro del siguiente delegado:

 - (void)textViewDidChange:(UITextView *)textView 

Allí puede, por ejemplo, después de analizar get el marco de la segunda línea simplemente haciendo: self.frames [1]

O haciendo que el text de la segunda línea funcione : [textView.text substringWithRange: [self.ranges [1] rangeValue]]

Por ejemplo así:

 if (self.numberOfLines > 1) { NSRange range = [self.ranges[1] rangeValue]; NSLog(@"2nd line = %@", [textView.text substringWithRange:range]); NSLog(@"2nd line frame = %@", self.frames[1]); } 

Al tener todos los frameworks en self.frames , creo que puedes hacer otra cosa fácilmente, adivina el número de línea usando una coorderada.