Personalizar el patrón de subrayado en NSAttributedString (iOS7 +)

Estoy tratando de agregar un estilo de subrayado punteado a un NSAttributedString (en iOS7 + / TextKit). Por supuesto, probé el NSUnderlinePatternDot :

 NSString *string = self.label.text; NSRange underlineRange = [string rangeOfString:@"consetetur sadipscing elitr"]; NSMutableAttributedString *attString = [[NSMutableAttributedString alloc] initWithString:string]; [attString addAttributes:@{NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle | NSUnderlinePatternDot)} range:underlineRange]; self.label.attributedText = attString; 

Sin embargo, el estilo producido por esto es más bien punteado que punteado:

captura de pantalla

¿Me estoy perdiendo algo obvio aquí ( NSUnderlinePatternReallyDotted ?;)) O tal vez hay una manera de personalizar el patrón de puntos de línea?

He pasado un poco de time jugando con Text Kit para lograr que este y otros escenarios similares funcionen. La solución del Kit de text real para este problema es subclass NSLayoutManager y anular drawUnderline(forGlyphRange:underlineType:baselineOffset:lineFragmentRect:lineFragmentGlyphRange:containerOrigin:)

Este es el método que se llama para dibujar realmente el subrayado que solicitan los attributes en el NSTextStorage . Aquí hay una descripción de los parameters que se pasan en:

  • glyphRange el índice del primer glifo y el número de glifos siguientes para subrayar
  • underlineType NSUnderlineStyle valor NSUnderlineAttributeName atributo NSUnderlineAttributeName
  • baselineOffset la distancia entre la parte inferior del cuadro delimitador y el desplazamiento de línea base para los glifos en el range proporcionado
  • lineFragmentRect el rectángulo que encierra toda la línea que contiene glyphRange
  • lineFragmentGlyphRange el range de glifo que compone toda la línea que contiene glyphRange
  • containerOrigin el desplazamiento del contenedor dentro de la vista de text a la que pertenece

Pasos para dibujar subrayan:

  1. Encuentre el NSTextContainer que contiene glyphRange utilizando NSLayoutManager.textContainer(forGlyphAt:effectiveRange:) .
  2. Obtenga el rectángulo delimitador para glyphRange utilizando NSLayoutManager.boundingRect(forGlyphRange:in:)
  3. Desplace el rectángulo delimitador por el origen del contenedor de text usando CGRect.offsetBy(dx:dy:)
  4. Dibuje su subrayado personalizado en algún lugar entre la parte inferior del rectángulo delimitador de desplazamiento y la línea de base (según lo determinado por la baselineOffset )

Sé que esta es una historia de 2 años, pero tal vez ayudará a alguien que enfrenta el mismo problema, como lo hice la semana pasada. Mi solución era subclass UITextView , agregar una subvista (el contenedor de líneas de puntos) y utilizar el método layoutManager de textView enumerateLineFragmentsForGlyphRange para get el número de líneas y sus frameworks. Conociendo el marco de la línea, calculé el punto donde quería los puntos. Así que creé una vista (lineView en la que dibujé puntos), establezco clipsToBounds en YES , el ancho al text de textView y luego agregué como una subvista en linesContainer en ese y . Después de eso, establezco el width de lineView al devuelto por enumerateLineFragmentsForGlyphRange . Para varias filas, el mismo enfoque: actualizar frameworks, agregar nuevas líneas o eliminar de acuerdo con lo que enumerateLineFragmentsForGlyphRange LineFragmentsForGlyphRange. Este método se textViewDidChange en textViewDidChange después de cada actualización de text. Aquí está el código para get la matriz de líneas y sus frameworks

 NSLayoutManager *layoutManager = [self layoutManager]; [layoutManager enumerateLineFragmentsForGlyphRange:NSMakeRange(0, [layoutManager numberOfGlyphs]) usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer *textContainer, NSRange glyphRange, BOOL *stop) { [linesFramesArray addObject:NSStringFromCGRect(usedRect)]; } ];