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:
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
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
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
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.
Por lo tanto, no hay una forma "directa" de hacer esto, pero puede lograr lo mismo usando métricas de texto:
... por ejemplo, encuentre el tamaño del texto y luego use esa información para ubicarla donde desee en la capa principal. Espero que esto ayude.
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 subclaseAVVideoCompositionCoreAnimationTool
usandoAVVideoCompositionCoreAnimationTool
. 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 claseString
de Swift, porque seNSString
automáticamente aNSString
. Ignora la propiedad
CATextLayer.fontSize
y se basa completamente en la propiedadCATextLayer.font
que DEBE ser una instancia deUIFont
.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)
}
}