如何使UILabel显示轮廓文字?

问题描述 投票:98回答:14

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

我使用下面的代码完成了UILabel的子类化,我从几个相关的在线示例中笨拙地拼凑而成。并且它工作但它非常非常慢(除了在模拟器上)并且我无法使它垂直居中(因此我暂时硬编码最后一行的y值)。哈啊!

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屈服于我的意志,尽管专心地盯着它并且皱眉真的很难。

ios objective-c iphone cocoa-touch core-graphics
14个回答
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
投票

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


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

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

如果您在Storyboard中设置文本,那么:

- (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;
}

-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类中的。


-3
投票

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

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

(别忘了包含QuartzCore / QuartzCore.h)


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,即只有内部轮廓工作。


24
投票

在阅读了接受的答案以及对它的两个更正和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中的现有标签,并通过添加用户定义的运行时属性来更改边框的厚度及其颜色,如下所示:

结果:


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


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兼容..

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


4
投票

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

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

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

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

希望加快你的动画!

ñ


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];

我只是评论他的回答,但显然我不是“声誉好”。


2
投票

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

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

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

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

© www.soinside.com 2019 - 2024. All rights reserved.