¿Cómo hago para que UILabel muestre el texto delineado?

Todo lo que quiero es un borde negro de un píxel alrededor de mi texto UILabel blanco.

Llegué a subclasificar a UILabel con el siguiente código, el cual formé torpemente a partir de algunos ejemplos en línea tangencialmente relacionados. Y funciona, pero es muy, pero muy lento (excepto en el simulador) y tampoco pude hacer que centrara el texto verticalmente (así que codifiqué el valor y en la última línea temporalmente). Ahhhh!

void ShowStringCentered(CGContextRef gc, float x, float y, const char *str) { CGContextSetTextDrawingMode(gc, kCGTextInvisible); CGContextShowTextAtPoint(gc, 0, 0, str, strlen(str)); CGPoint pt = CGContextGetTextPosition(gc); CGContextSetTextDrawingMode(gc, kCGTextFillStroke); CGContextShowTextAtPoint(gc, x - pt.x / 2, y, str, strlen(str)); } - (void)drawRect:(CGRect)rect{ CGContextRef theContext = UIGraphicsGetCurrentContext(); CGRect viewBounds = self.bounds; CGContextTranslateCTM(theContext, 0, viewBounds.size.height); CGContextScaleCTM(theContext, 1, -1); CGContextSelectFont (theContext, "Helvetica", viewBounds.size.height, kCGEncodingMacRoman); CGContextSetRGBFillColor (theContext, 1, 1, 1, 1); CGContextSetRGBStrokeColor (theContext, 0, 0, 0, 1); CGContextSetLineWidth(theContext, 1.0); ShowStringCentered(theContext, rect.size.width / 2.0, 12, [[self text] cStringUsingEncoding:NSASCIIStringEncoding]); } 

Solo tengo la sensación de que estoy pasando por alto una forma más simple de hacer esto. Tal vez anulando “drawTextInRect”, pero parece que no puedo hacer que drawTextInRect se doble a mi voluntad a pesar de mirarlo atentamente y fruncir el ceño realmente duro.

Pude hacerlo anulando drawTextInRect:

 - (void)drawTextInRect:(CGRect)rect { CGSize shadowOffset = self.shadowOffset; UIColor *textColor = self.textColor; CGContextRef c = UIGraphicsGetCurrentContext(); CGContextSetLineWidth(c, 1); CGContextSetLineJoin(c, kCGLineJoinRound); CGContextSetTextDrawingMode(c, kCGTextStroke); self.textColor = [UIColor whiteColor]; [super drawTextInRect:rect]; CGContextSetTextDrawingMode(c, kCGTextFill); self.textColor = textColor; self.shadowOffset = CGSizeMake(0, 0); [super drawTextInRect:rect]; self.shadowOffset = shadowOffset; } 

Una solución más simple es usar una Cadena Atribuida así:

 let strokeTextAttributes = [ NSStrokeColorAttributeName : UIColor.blackColor(), NSForegroundColorAttributeName : UIColor.whiteColor(), NSStrokeWidthAttributeName : -1.0, ] myLabel.attributedText = NSAttributedString(string: "Foo", attributes: strokeTextAttributes) 

Sintaxis de Swift 4:

 let strokeTextAttributes: [NSAttributedStringKey : Any] = [ NSAttributedStringKey.strokeColor : UIColor.black, NSAttributedStringKey.foregroundColor : UIColor.white, NSAttributedStringKey.strokeWidth : -2.0, ] myLabel.attributedText = NSAttributedString(string: "Foo", attributes: strokeTextAttributes) 

En un UITextField puede establecer los defaultTextAttributes y el attributedPlaceholder también.

Tenga en cuenta que NSStrokeWidthAttributeName tiene que ser negativo en este caso, es decir, que solo funcionan los contornos internos.

UITextFields con un esquema que usa textos atribuidos

Después de leer la respuesta aceptada y las dos correcciones y la respuesta de Axel Guilmin, decidí comstackr una solución general en Swift, que me conviene:

 import UIKit class UIOutlinedLabel: UILabel { var outlineWidth: CGFloat = 1 var outlineColor: UIColor = UIColor.whiteColor() override func drawTextInRect(rect: CGRect) { let strokeTextAttributes = [ NSStrokeColorAttributeName : outlineColor, NSStrokeWidthAttributeName : -1 * outlineWidth, ] self.attributedText = NSAttributedString(string: self.text ?? "", attributes: strokeTextAttributes) super.drawTextInRect(rect) } } 

Puede agregar esta clase UILabel personalizada a una etiqueta existente en Interface Builder y cambiar el grosor del borde y su color agregando atributos de tiempo de ejecución definidos por el usuario como este: Configuración de Interface Builder

Resultado:

gran G roja con contorno

Hay un problema con la implementación de la respuesta. Dibujar un texto con trazo tiene un ancho de glifo de carácter ligeramente diferente que dibujar un texto sin trazo, que puede producir resultados “no centrados”. Se puede arreglar agregando un trazo invisible alrededor del texto de relleno.

Reemplazar:

 CGContextSetTextDrawingMode(c, kCGTextFill); self.textColor = textColor; self.shadowOffset = CGSizeMake(0, 0); [super drawTextInRect:rect]; 

con:

 CGContextSetTextDrawingMode(context, kCGTextFillStroke); self.textColor = textColor; [[UIColor clearColor] setStroke]; // invisible stroke self.shadowOffset = CGSizeMake(0, 0); [super drawTextInRect:rect]; 

No estoy 100% seguro, si ese es el verdadero negocio, porque no sé si self.textColor = textColor; tiene el mismo efecto que [textColor setFill] , pero debería funcionar.

Divulgación: soy el desarrollador de THLabel.

Hace un tiempo publiqué una subclase UILabel, que permite un esquema en texto y otros efectos. Puede encontrarlo aquí: https://github.com/tobihagemnn/THLabel

Esto no creará un esquema per se, pero pondrá una sombra alrededor del texto, y si hace que el radio de sombra sea lo suficientemente pequeño, podría parecerse a un contorno.

 label.layer.shadowColor = [[UIColor blackColor] CGColor]; label.layer.shadowOffset = CGSizeMake(0.0f, 0.0f); label.layer.shadowOpacity = 1.0f; label.layer.shadowRadius = 1.0f; 

No sé si es compatible con versiones anteriores de iOS …

De todos modos, espero que ayude …

Si quieres animar algo complicado, la mejor manera es tomar una captura de pantalla de manera programática.

Para tomar una captura de pantalla de una vista, necesitará un código como este:

 UIGraphicsBeginImageContext(mainContentView.bounds.size); [mainContentView.layer renderInContext:UIGraphicsGetCurrentContext()]; UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); 

Donde mainContentView es la vista de la que desea tomar una captura de pantalla. Añada viewImage a UIImageView y anímela.

Espero que acelere tu animación !!

norte

Como MuscleRumble mencionó, la frontera de la respuesta aceptada está un poco fuera de foco . Pude corregir esto al establecer el ancho de trazo en cero en lugar de cambiar el color a borrar.

es decir, reemplazando:

 CGContextSetTextDrawingMode(c, kCGTextFill); self.textColor = textColor; self.shadowOffset = CGSizeMake(0, 0); [super drawTextInRect:rect]; 

con:

 CGContextSetTextDrawingMode(c, kCGTextFillStroke); self.textColor = textColor; CGContextSetLineWidth(c, 0); // set stroke width to zero self.shadowOffset = CGSizeMake(0, 0); [super drawTextInRect:rect]; 

Acabo de comentar su respuesta, pero aparentemente no soy lo suficientemente “respetable”.

si TODO lo que desea es un borde negro de un píxel alrededor de mi texto UILabel blanco,

entonces sí creo que estás haciendo que el problema sea más difícil de lo que es … No sé por memoria qué función ‘draw rect / frameRect’ debes usar, pero te será fácil encontrarla. este método simplemente demuestra la estrategia (¡deje que la superclase haga el trabajo!):

 - (void)drawRect:(CGRect)rect { [super drawRect:rect]; [context frameRect:rect]; // research which rect drawing function to use... } 

Encontré un problema con la respuesta principal. La posición del texto no está necesariamente centrada correctamente en la ubicación de subpíxeles, por lo que el contorno puede no coincidir con el texto. Lo arreglé usando el siguiente código, que usa CGContextSetShouldSubpixelQuantizeFonts(ctx, false) :

 - (void)drawTextInRect:(CGRect)rect { CGContextRef ctx = UIGraphicsGetCurrentContext(); [self.textOutlineColor setStroke]; [self.textColor setFill]; CGContextSetShouldSubpixelQuantizeFonts(ctx, false); CGContextSetLineWidth(ctx, self.textOutlineWidth); CGContextSetLineJoin(ctx, kCGLineJoinRound); CGContextSetTextDrawingMode(ctx, kCGTextStroke); [self.text drawInRect:rect withFont:self.font lineBreakMode:NSLineBreakByWordWrapping alignment:self.textAlignment]; CGContextSetTextDrawingMode(ctx, kCGTextFill); [self.text drawInRect:rect withFont:self.font lineBreakMode:NSLineBreakByWordWrapping alignment:self.textAlignment]; } 

Esto supone que ha definido textOutlineColor y textOutlineWidth como propiedades.

Una versión de clase de Swift 4 basada en la respuesta de kprevas

 import Foundation import UIKit public class OutlinedText: UILabel{ internal var mOutlineColor:UIColor? internal var mOutlineWidth:CGFloat? @IBInspectable var outlineColor: UIColor{ get { return mOutlineColor ?? UIColor.clear } set { mOutlineColor = newValue } } @IBInspectable var outlineWidth: CGFloat{ get { return mOutlineWidth ?? 0 } set { mOutlineWidth = newValue } } override public func drawText(in rect: CGRect) { let shadowOffset = self.shadowOffset let textColor = self.textColor let c = UIGraphicsGetCurrentContext() c?.setLineWidth(outlineWidth) c?.setLineJoin(.round) c?.setTextDrawingMode(.stroke) self.textColor = mOutlineColor; super.drawText(in:rect) c?.setTextDrawingMode(.fill) self.textColor = textColor self.shadowOffset = CGSize(width: 0, height: 0) super.drawText(in:rect) self.shadowOffset = shadowOffset } } 

Puede implementarse completamente en el Creador de Interfaces estableciendo la clase personalizada de UILabel en Texto Extendido. A continuación, tendrá la capacidad de establecer el ancho y el color del esquema desde el panel Propiedades.

enter image description here

¿Por qué no creas una vista UIView de 1px en Photoshop, luego configuras una UIView con la imagen y la colocas detrás de tu UILabel?

Código:

 UIView *myView; UIImage *imageName = [UIImage imageNamed:@"1pxBorderImage.png"]; UIColor *tempColour = [[UIColor alloc] initWithPatternImage:imageName]; myView.backgroundColor = tempColour; [tempColour release]; 

Te va a ahorrar la subclasificación de un objeto y es bastante simple de hacer.

Sin mencionar que si quieres hacer animación, está integrada en la clase UIView.

Para poner un borde con bordes redondeados alrededor de un UILabel, hago lo siguiente:

 labelName.layer.borderWidth = 1; labelName.layer.borderColor = [[UIColor grayColor] CGColor]; labelName.layer.cornerRadius = 10; 

(no olvides incluir QuartzCore / QuartzCore.h)