iphone macos ios core-animation text-alignment

iphone - alinear verticalmente el texto en un CATextLayer?



macos ios (12)

El código de gbk funciona. A continuación se muestra el código de gbk actualizado para XCode 8 beta 6. Actual al 1 de octubre de 2016

Paso 1. Subclase CATextLayer. En el código siguiente, he llamado a la subclase "MyCATextLayer" Fuera de su clase de controlador de vista, copie / pegue el siguiente código.

class MyCATextLayer: CATextLayer { // REF: http://lists.apple.com/archives/quartz-dev/2008/Aug/msg00016.html // CREDIT: David Hoerl - https://github.com/dhoerl // USAGE: To fix the vertical alignment issue that currently exists within the CATextLayer class. Change made to the yDiff calculation. override init() { super.init() } required init(coder aDecoder: NSCoder) { super.init(layer: aDecoder) } override func draw(in ctx: CGContext) { let height = self.bounds.size.height let fontSize = self.fontSize let yDiff = (height-fontSize)/2 - fontSize/10 ctx.saveGState() ctx.translateBy(x: 0.0, y: yDiff) super.draw(in: ctx) ctx.restoreGState() } }

Paso 2. Dentro de su clase de controlador de vista en su archivo ".swift", cree su CATextLabel. En el ejemplo de código, he llamado a la subclase "MyDopeCATextLayer".

let MyDopeCATextLayer: MyCATextLayer = MyCATextLayer()

Paso 3. Establezca su nuevo CATextLayer con el texto / color / límites / marco deseado.

MyDopeCATextLayer.string = "Hello World" // displayed text MyDopeCATextLayer.foregroundColor = UIColor.purple.cgColor //color of text is purple MyDopeCATextLayer.frame = CGRect(x: 0, y:0, width: self.frame.width, height: self.frame.height) MyDopeCATextLayer.font = UIFont(name: "HelveticaNeue-UltraLight", size: 5) //5 is ignored, set actual font size using ".fontSize" (below) MyDopeCATextLayer.fontSize = 24 MyDopeCATextLayer.alignmentMode = kCAAlignmentCenter //Horizontally centers text. text is automatically centered vertically because it''s set in subclass code MyDopeCATextLayer.contentsScale = UIScreen.main.scale //sets "resolution" to whatever the device is using (prevents fuzzyness/blurryness)

Paso 4. hecho

Estoy trabajando en un CATextLayer que quiero usar tanto en Mac como en iOS. ¿Puedo controlar la alineación vertical del texto dentro de la capa?

En este caso particular, quiero centrarlo verticalmente, pero la información sobre otras alineaciones verticales también sería interesante.

EDIT: Encontré this , pero no puedo hacer que funcione.


El código para Swift 3, basado en el código @iamktothed

Si usa una cadena con atributos para establecer las propiedades de la fuente, puede usar la función size () de NSAttributedString para calcular la altura de la cadena. Creo que este código también resuelve los problemas descritos por @Enix

class LCTextLayer: CATextLayer { override init() { super.init() } override init(layer: Any) { super.init(layer: layer) } required init(coder aDecoder: NSCoder) { super.init(layer: aDecoder) } override open func draw(in ctx: CGContext) { if let attributedString = self.string as? NSAttributedString { let height = self.bounds.size.height let stringSize = attributedString.size() let yDiff = (height - stringSize.height) / 2 ctx.saveGState() ctx.translateBy(x: 0.0, y: yDiff) super.draw(in: ctx) ctx.restoreGState() } } }


Es una respuesta tardía, pero tengo la misma pregunta en estos días y he resuelto el problema con la siguiente investigación.

La alineación vertical depende del texto que necesita dibujar y de la fuente que está utilizando, por lo que no existe una solución de una sola manera para hacerlo vertical en todos los casos.

Pero todavía podemos calcular el punto medio vertical para diferentes casos.

De acuerdo con Acerca del manejo de texto de Apple en iOS , necesitamos saber cómo se dibuja el texto.

Por ejemplo, estoy tratando de hacer una alineación vertical para las cadenas de días de la semana: Sun, Mon, Tue, ....

Para este caso, la altura del texto depende de la altura de la tapa , y no hay descenso para estos caracteres. Entonces, si necesitamos hacer que este texto se alinee con el centro, podemos calcular el desplazamiento del carácter de la parte superior de la tapa, por ejemplo, la posición de la parte superior del carácter "S".

De acuerdo con la siguiente figura:

El espacio superior para el carácter de capital "S" sería

font.ascender - font.capHeight

Y el espacio inferior para el carácter de capital "S" sería

font.descender + font.leading

Así que necesitamos mover "S" un poco de la parte superior por:

y = (font.ascender - font.capHeight + font.descender + font.leading + font.capHeight) / 2

Eso es igual a:

y = (font.ascender + font.descender + font.leading) / 2

Entonces puedo hacer que el texto se alinee verticalmente en el medio.

Conclusión:

  1. Si su texto no incluye ningún carácter exceda la línea de base, por ejemplo, "p", "j", "g", y ningún carácter sobre la parte superior de la altura de la tapa, por ejemplo, "f". Puede usar la fórmula anterior para alinear el texto verticalmente.

    y = (font.ascender + font.descender + font.leading) / 2

  2. Si su texto incluye caracteres debajo de la línea de base, por ejemplo, "p", "j", y ningún carácter excede la parte superior de la altura del límite, por ejemplo, "f". Entonces la fórmula vertical sería:

    y = (font.ascender + font.descender) / 2

  3. Si su inclusión de texto no incluye caracteres dibujados debajo de la línea de base, por ejemplo, "j", "p", e incluye caracteres dibujados sobre la línea de altura del límite, por ejemplo, "f". Entonces y sería:

    y = (font.descender + font.leading) / 2

  4. Si todos los caracteres se produjeran en su texto, entonces y es igual a:

    y = font.leading / 2


Gracias a @iamktothed, funciona. La siguiente es la versión swift 3:

class CXETextLayer : CATextLayer { override init() { super.init() } override init(layer: Any) { super.init(layer: layer) } required init(coder aDecoder: NSCoder) { super.init(layer: aDecoder) } override func draw(in ctx: CGContext) { let height = self.bounds.size.height let fontSize = self.fontSize let yDiff = (height-fontSize)/2 - fontSize/10 ctx.saveGState() ctx.translateBy(x: 0.0, y: yDiff) super.draw(in: ctx) ctx.restoreGState() } }


Lo mejor que puedo decir, la respuesta a mi pregunta es "No."


Me gustaría proponer una solución que tenga en cuenta el ajuste multilínea dentro del cuadro disponible:

final class CACenteredTextLayer: CATextLayer { override func draw(in ctx: CGContext) { guard let attributedString = string as? NSAttributedString else { return } let height = self.bounds.size.height let boundingRect: CGRect = attributedString.boundingRect( with: CGSize(width: bounds.width, height: CGFloat.greatestFiniteMagnitude), options: NSStringDrawingOptions.usesLineFragmentOrigin, context: nil) let yDiff: CGFloat = (height - boundingRect.size.height) / 2 ctx.saveGState() ctx.translateBy(x: 0.0, y: yDiff) super.draw(in: ctx) ctx.restoreGState() } }


Necesita saber dónde CATextLayer colocará la línea de base de su texto. Una vez que lo sepa, desplace el sistema de coordenadas dentro de la capa, es decir, ajuste los límites.origen.y por la diferencia entre el lugar donde normalmente se encuentra la línea de base y el lugar donde desea que esté, dadas las métricas de la fuente.

CATextLayer es un poco de una caja negra y encontrar dónde se ubicará la línea de base es un poco complicado (vea mi respuesta aquí para iOS ) No tengo idea de cuál es el comportamiento de Mac.



Tal vez demasiado tarde para responder, pero puede calcular el tamaño del texto y luego establecer la posición de textLayer. También es necesario poner el modo de alineación de texto de texto en "centro"

CGRect labelRect = [text boundingRectWithSize:view.bounds.size options:NSStringDrawingUsesLineFragmentOrigin attributes:@{ NSFontAttributeName : [UIFont fontWithName:@"HelveticaNeue" size:17.0] } context:nil]; CATextLayer *textLayer = [CATextLayer layer]; [textLayer setString:text]; [textLayer setForegroundColor:[UIColor redColor].CGColor]; [textLayer setFrame:labelRect]; [textLayer setFont:CFBridgingRetain([UIFont fontWithName:@"HelveticaNeue" size:17.0].fontName)]; [textLayer setAlignmentMode:kCAAlignmentCenter]; [textLayer setFontSize:17.0]; textLayer.masksToBounds = YES; textLayer.position = CGPointMake(CGRectGetMidX(view.bounds), CGRectGetMidY(view.bounds)); [view.layer addSublayer:textLayer];


Versión Swift 3 para cadenas regulares y atribuidas.

class ECATextLayer: CATextLayer { override open func draw(in ctx: CGContext) { let yDiff: CGFloat let fontSize: CGFloat let height = self.bounds.height if let attributedString = self.string as? NSAttributedString { fontSize = attributedString.size().height yDiff = (height-fontSize)/2 } else { fontSize = self.fontSize yDiff = (height-fontSize)/2 - fontSize/10 } ctx.saveGState() ctx.translateBy(x: 0.0, y: yDiff) super.draw(in: ctx) ctx.restoreGState() } }


ligeramente . Las diferencias son:

  • el cálculo de la altura del texto se basa en NSString.size(with: Attributes) . No sé si es una mejora respecto a (height-fontSize)/2 - fontSize/10 , pero me gusta pensar que lo es. Aunque, según mi experiencia, NSString.size(with: Attributes) no siempre devuelve el tamaño más apropiado.
  • Se ha agregado la propiedad invertedYAxis . Fue útil para mis propósitos de exportar esta subclase AVVideoCompositionCoreAnimationTool usando AVVideoCompositionCoreAnimationTool . AVFoundation opera en el eje y "normal", y es por eso que tuve que agregar esta propiedad.
  • Funciona solo con NSString . Sin embargo, puede usar la clase String de Swift, porque se NSString automáticamente a NSString .
  • Ignora la propiedad CATextLayer.fontSize y se basa completamente en la propiedad CATextLayer.font que DEBE ser una instancia de UIFont .

    class VerticallyCenteredTextLayer: CATextLayer { var invertedYAxis: Bool = true override func draw(in ctx: CGContext) { guard let text = string as? NSString, let font = self.font as? UIFont else { super.draw(in: ctx) return } let attributes = [NSAttributedString.Key.font: font] let textSize = text.size(withAttributes: attributes) var yDiff = (bounds.height - textSize.height) / 2 if !invertedYAxis { yDiff = -yDiff } ctx.saveGState() ctx.translateBy(x: 0.0, y: yDiff) super.draw(in: ctx) ctx.restoreGState() } }


La respuesta correcta, como ya ha encontrado, está this en Objective-C y funciona para iOS . Funciona subclasificando CATextLayer y anulando la función drawInContext .

Sin embargo, he hecho algunas mejoras al código , como se muestra a continuación, utilizando el código de David Hoerl como base. Los cambios se producen únicamente al recalcular la posición vertical del texto representado por el yDiff . Lo he probado con mi propio código.

Aquí está el código para los usuarios de Swift:

class LCTextLayer : CATextLayer { // REF: http://lists.apple.com/archives/quartz-dev/2008/Aug/msg00016.html // CREDIT: David Hoerl - https://github.com/dhoerl // USAGE: To fix the vertical alignment issue that currently exists within the CATextLayer class. Change made to the yDiff calculation. override init!() { super.init() } override init!(layer: AnyObject!) { super.init(layer: layer) } required init(coder aDecoder: NSCoder) { super.init(layer: aDecoder) } override func drawInContext(ctx: CGContext!) { let height = self.bounds.size.height let fontSize = self.fontSize let yDiff = (height-fontSize)/2 - fontSize/10 CGContextSaveGState(ctx) CGContextTranslateCTM(ctx, 0.0, yDiff) super.drawInContext(ctx) CGContextRestoreGState(ctx) } }