如何使UILabel显示轮廓文字?

时间:2009-07-09 10:47:20

标签: ios objective-c iphone cocoa-touch core-graphics

我想要的只是我的白色UILabel文字周围的一个像素黑色边框。

我使用下面的代码对UILabel进行了子类化,我从几个相关的在线示例中笨拙地拼凑而成。并且它工作但它非常非常慢(除了在模拟器上)并且我无法使它垂直居中(因此我暂时硬编码最后一行的y值)。 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]);
}

我只是有一种唠叨的感觉,我忽略了一种更简单的方法。也许是通过重写“drawTextInRect”,但我似乎无法让drawTextInRect屈服于我的意志,尽管专心地盯着它并且皱着眉头真的很难。

15 个答案:

答案 0 :(得分:162)

我能够通过覆盖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;

}

答案 1 :(得分:100)

更简单的解决方案是使用Attributed String,如此:

斯威夫特4:

let strokeTextAttributes: [NSAttributedStringKey : Any] = [
    NSAttributedStringKey.strokeColor : UIColor.black,
    NSAttributedStringKey.foregroundColor : UIColor.white,
    NSAttributedStringKey.strokeWidth : -2.0,
    ]

myLabel.attributedText = NSAttributedString(string: "Foo", attributes: strokeTextAttributes)

Swift 4.2:

let strokeTextAttributes: [NSAttributedString.Key : Any] = [
    .strokeColor : UIColor.black,
    .foregroundColor : UIColor.white,
    .strokeWidth : -2.0,
    ]

myLabel.attributedText = NSAttributedString(string: "Foo", attributes: strokeTextAttributes)

UITextField上,您也可以设置defaultTextAttributesattributedPlaceholder

请注意,在这种情况下NSStrokeWidthAttributeName has to be negative,即只有内部轮廓可以使用。

UITextFields with an outline using attributed texts

答案 2 :(得分:23)

在阅读了接受的答案及其中的两个更正和Axel Guilmin的答案之后,我决定在Swift中编译一个适合我的整体解决方案:

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)
    }
}

您可以将此自定义UILabel类添加到Interface Builder中的现有标签,并通过添加用户定义的运行时属性来更改边框的厚度及其颜色,如下所示: Interface Builder setup

结果:

big red G with outline

答案 3 :(得分:8)

答案的实施存在一个问题。使用笔划绘制文本的字符字形宽度与绘制没有笔划的文本略有不同,这会产生“未中心”的结果。可以通过在填充文本周围添加不​​可见的笔划来修复它。

替换:

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

使用:

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

我不是百分百肯定,如果这是真正的交易,因为我不知道self.textColor = textColor;是否与[textColor setFill]具有相同的效果,但它应该有效。

披露:我是THLabel的开发者。

我刚刚发布了一个UILabel子类,它允许在文本和其他效果中使用大纲。您可以在此处找到它:https://github.com/tobihagemann/THLabel

答案 4 :(得分:6)

这不会创建轮廓本身,但它会在文本周围留下阴影,如果使阴影半径足够小,它可能类似于轮廓。

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

我不知道它是否与旧版本的iOS兼容..

无论如何,我希望它有所帮助...

答案 5 :(得分:4)

如果你想制作一些复杂的动画,最好的方法是以编程方式将它的截图设为一个动画,而不是!

要获取视图的屏幕截图,您需要的代码有点像这样:

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

其中mainContentView是您要截取屏幕截图的视图。将viewImage添加到UIImageView并为其设置动画。

希望加快你的动画!!

<磷>氮

答案 6 :(得分:3)

MuscleRumble所述,接受答案的边界有点偏离中心。我可以通过将笔触宽度设置为零而不是将颜色更改为清除来纠正此问题。

即。替换:

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

使用:

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

我会对他的回答发表评论,但显然我不是“声誉好”。

答案 7 :(得分:2)

如果你想要的只是我白色UILabel文字周围的一个像素黑色边框,

然后 我认为你的问题比现在更难...... 我不知道你应该使用哪种'draw rect / frameRect'功能,但你很容易找到它。这个方法只是演示了策略(让超类做的工作!):<​​/ p>

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

答案 8 :(得分:2)

基于answer by kprevas

的Swift 4类版本
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
    }
}

通过将UILabel的自定义类设置为OutlinedText,可以完全在Interface Builder中实现它。然后,您可以从“属性”窗格中设置轮廓的宽度和颜色。

enter image description here

答案 9 :(得分:1)

我发现主答案有问题。文本位置不一定正确地居中于子像素位置,因此轮廓可能在文本周围不匹配。我使用以下代码修复了它,该代码使用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];
}

这假定您已将textOutlineColortextOutlineWidth定义为属性。

答案 10 :(得分:1)

如果您的目标是这样的:

enter image description here

这是我如何实现的:我在当前的UILabel中添加了一个自定义类的新label作为子视图(受此answer的启发)。

只需将其复制并粘贴到您的项目中,您就可以开始了:

extension UILabel {
    func addTextOutline(usingColor outlineColor: UIColor, outlineWidth: CGFloat) {
        class OutlinedText: UILabel{
            var outlineWidth: CGFloat = 0
            var outlineColor: UIColor = .clear

            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.textAlignment = .center
                self.textColor = outlineColor
                super.drawText(in:rect)

                c?.setTextDrawingMode(.fill)
                self.textColor = textColor
                self.shadowOffset = CGSize(width: 0, height: 0)
                super.drawText(in:rect)

                self.shadowOffset = shadowOffset
            }
        }

        let textOutline = OutlinedText()
        let outlineTag = 9999

        if let prevTextOutline = viewWithTag(outlineTag) {
            prevTextOutline.removeFromSuperview()
        }

        textOutline.outlineColor = outlineColor
        textOutline.outlineWidth = outlineWidth
        textOutline.textColor = textColor
        textOutline.font = font
        textOutline.text = text
        textOutline.tag = outlineTag

        sizeToFit()
        addSubview(textOutline)
        textOutline.frame = CGRect(x: -(outlineWidth / 2), y: -(outlineWidth / 2),
                                   width: bounds.width + outlineWidth,
                                   height: bounds.height + outlineWidth)
    }
}

用法:

yourLabel.addTextOutline(usingColor: .red, outlineWidth: 6)

它也适用于UIButton及其所有动画:

yourButton.titleLabel?.addTextOutline(usingColor: .red, outlineWidth: 6)

答案 11 :(得分:0)

这是在标签上设置轮廓文字的另一个答案。

extension UILabel {

func setOutLinedText(_ text: String) {
    let attribute : [NSAttributedString.Key : Any] = [
        NSAttributedString.Key.strokeColor : UIColor.black,
        NSAttributedString.Key.foregroundColor : UIColor.white,
        NSAttributedString.Key.strokeWidth : -2.0,
        NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 12)
        ] as [NSAttributedString.Key  : Any]

    let customizedText = NSMutableAttributedString(string: text,
                                                   attributes: attribute)
    attributedText = customizedText
 }

}

只需使用扩展方法即可设置轮廓文字。

lblTitle.setOutLinedText("Enter your email address or username")

答案 12 :(得分:0)

还可以使用以下逻辑将UILabel子类化:

- (void)setText:(NSString *)text {
    [self addOutlineForAttributedText:[[NSAttributedString alloc] initWithString:text]];
}

- (void)setAttributedText:(NSAttributedString *)attributedText {
    [self addOutlineForAttributedText:attributedText];
}

- (void)addOutlineForAttributedText:(NSAttributedString *)attributedText {
    NSDictionary *strokeTextAttributes = @{
                                           NSStrokeColorAttributeName: [UIColor blackColor],
                                           NSStrokeWidthAttributeName : @(-2)
                                           };

    NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithAttributedString:attributedText];
    [attrStr addAttributes:strokeTextAttributes range:NSMakeRange(0, attrStr.length)];

    super.attributedText = attrStr;
}

,如果您在情节提要中设置了文字,则:

- (instancetype)initWithCoder:(NSCoder *)aDecoder {

    self = [super initWithCoder:aDecoder];
    if (self) {
        // to apply border for text from storyboard
        [self addOutlineForAttributedText:[[NSAttributedString alloc] initWithString:self.text]];
    }
    return self;
}

答案 13 :(得分:-3)

为什么不在Photoshop中创建1px边框UIView,然后使用图像设置UIView,并将其放在UILabel后面?

代码:

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

它可以节省你对一个对象的子类,这很简单。

更不用说你是否想要做动画,它是内置于UIView类中的。

答案 14 :(得分:-3)

要在UILabel周围放置带圆角的边框,请执行以下操作:

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

(不要忘记包含QuartzCore / QuartzCore.h)