Color de fondo NSAttributedString y esquinas redondeadas

Tengo una pregunta sobre las esquinas redondeadas y el color de fondo del texto para una UIView personalizada.

Básicamente, necesito lograr un efecto como este (imagen adjunta – observe las esquinas redondeadas en un lado) en una UIView personalizada: Fondo resaltado

Estoy pensando que el enfoque para usar es:

  • Usa el Texto central para obtener ejecuciones de glifos.
  • Verifique el rango de resaltado.
  • Si la ejecución actual está dentro del rango de resaltado, dibuje un rectángulo de fondo con las esquinas redondeadas y el color de relleno deseado antes de dibujar la ejecución del glifo.
  • Dibuja la ejecución del glifo.

Sin embargo, no estoy seguro de si esta es la única solución (o para el caso, si esta es la solución más eficiente).

Usar UIWebView no es una opción, así que tengo que hacerlo en una UIView personalizada.

Mi pregunta es, ¿es este el mejor enfoque para usar, y estoy en el camino correcto? ¿O me estoy perdiendo algo importante o lo estoy haciendo mal?

Logré lograr el efecto anterior, así que pensé en publicar una respuesta para el mismo.

Si alguien tiene alguna sugerencia para hacer esto más efectivo, siéntase libre de contribuir. Me aseguraré de marcar su respuesta como la correcta. 🙂

Para hacer esto, deberá agregar un “atributo personalizado” a NSAttributedString .

Básicamente, lo que eso significa es que puede agregar cualquier par de clave-valor, siempre que sea algo que pueda agregar a una instancia de NSDictionary . Si el sistema no reconoce ese atributo, no hace nada. Depende de usted, como desarrollador, proporcionar una implementación personalizada y comportamiento para ese atributo.

A los efectos de esta respuesta, supongamos que he agregado un atributo personalizado llamado: @"MyRoundedBackgroundColor" con un valor de [UIColor greenColor] .

Para los pasos siguientes, deberá tener una comprensión básica de cómo CoreText hace las cosas. Consulte la Guía de progtwigción de texto principal de Apple para comprender qué es un marco / línea / ejecución de glifo / glifo, etc.

Entonces, aquí están los pasos:

  1. Crea una subclase UIView personalizada.
  2. Tener una propiedad para aceptar un NSAttributedString .
  3. Cree un CTFramesetter utilizando esa instancia NSAttributedString .
  4. Reemplazar el método drawRect:
  5. Cree una instancia de CTFrame desde CTFramesetter .
    1. Tendrá que dar un CGPathRef para crear el CTFrame . Haga que CGPath sea ​​el mismo que el marco en el que desea dibujar el texto.
  6. Obtenga el contexto gráfico actual y cambie el sistema de coordenadas de texto.
  7. Usando CTFrameGetLines(...) , obtenga todas las líneas en el CTFrame que acaba de crear.
  8. Utilizando CTFrameGetLineOrigins(...) , obtenga todos los orígenes de línea para CTFrame .
  9. Comience un for loop – para cada línea en la matriz de CTLine
  10. Establezca la posición del texto al comienzo de la CTLine usando CGContextSetTextPosition(...) .
  11. Usando CTLineGetGlyphRuns(...) obtiene todas las carreras de glifo ( CTRunRef ) de CTLine .
  12. Inicie otro for loop : para cada glyphRun en la matriz de CTRun
  13. Obtenga el rango de la ejecución usando CTRunGetStringRange(...) .
  14. Obtenga límites tipográficos utilizando CTRunGetTypographicBounds(...) .
  15. Obtenga el desplazamiento x para la ejecución usando CTLineGetOffsetForStringIndex(...) .
  16. Calcule el rect límite (llamémoslo runBounds ) usando los valores devueltos por las funciones mencionadas anteriormente.
    1. Recuerde: CTRunGetTypographicBounds(...) requiere punteros a las variables para almacenar el “ascenso” y el “descenso” del texto. Debe agregar esos para obtener la altura de ejecución.
  17. Obtenga los atributos para la ejecución usando CTRunGetAttributes(...) .
  18. Compruebe si el diccionario de atributos contiene su atributo.
  19. Si su atributo existe, calcule los límites del rectángulo que debe pintarse.
  20. El texto principal tiene los orígenes de línea en la línea de base. Necesitamos dibujar desde el punto más bajo del texto hasta el punto más alto. Por lo tanto, tenemos que ajustarnos para el descenso.
  21. Por lo tanto, resta el descenso del rect límite que calculamos en el paso 16 ( runBounds ).
  22. Ahora que tenemos los runBounds , sabemos qué área queremos pintar, ahora podemos usar cualquiera de los métodos UIBezierPath / UIBezierPath para dibujar y completar un rect con esquinas redondeadas específicas.
    1. UIBezierPath tiene un método de clase de conveniencia llamado bezierPathWithRoundedRect:byRoundingCorners:cornerRadii: que le permite redondear esquinas específicas. Usted especifica las esquinas usando máscaras de bits en el 2do parámetro.
  23. Ahora que ha llenado el rect, simplemente dibuje la ejecución del glifo usando CTRunDraw(...) .
  24. Celebre la victoria por haber creado su atributo personalizado: ¡beba una cerveza o algo así! :RE

En cuanto a la detección de que el rango de atributos se extiende a varias ejecuciones, puede obtener todo el rango efectivo de su atributo personalizado cuando la primera ejecución encuentra el atributo. Si encuentra que la longitud del scope efectivo máximo de su atributo es mayor que la longitud de su recorrido, necesita pintar esquinas agudas en el lado derecho (para un guión de izquierda a derecha). Más matemáticas le permitirán detectar el estilo de esquina más destacado para la próxima línea también. 🙂

Adjunto hay una captura de pantalla del efecto. El cuadro en la parte superior es una UITextView estándar, para la cual he establecido el texto atribuido. El cuadro en la parte inferior es el que se ha implementado utilizando los pasos anteriores. La misma cadena atribuida se ha establecido para ambos textViews. atributo personalizado con esquinas redondeadas

Nuevamente, si hay un enfoque mejor que el que he usado, ¡házmelo saber! :RE

Espero que esto ayude a la comunidad. 🙂

¡Aclamaciones!