【问题标题】:How do I make UILabel display outlined text?如何让 UILabel 显示轮廓文本?
【发布时间】:2010-11-09 08:14:23
【问题描述】:

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

我用下面的代码对 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 屈服于我的意愿,尽管我专心地盯着它,皱着眉头。

【问题讨论】:

  • 澄清 - 我的应用程序中的缓慢很明显,因为当标签的值随着轻微的增长和收缩而变化时,我正在为标签设置动画。没有子类化它很流畅,但是标签动画上面的代码很不稳定。我应该只使用 UIWebView 吗?我觉得这样做很愚蠢,因为标签只显示一个数字......
  • 好吧,看起来我遇到的性能问题与大纲代码无关,但我似乎仍然无法让它垂直对齐。由于某种原因,pt.y 总是为零。
  • 这对于像 Chalkduster 这样的字体来说非常慢

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


【解决方案1】:

我可以通过覆盖 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;

}

【讨论】:

  • 我尝试了这种技术,但结果并不令人满意。在我的 iPhone 4 上,我尝试使用此代码绘制带有黑色轮廓的白色文本。我得到的是文本中每个字母左右边缘的黑色轮廓,而不是顶部或底部的轮廓。有什么想法吗?
  • 抱歉,我正在尝试在我的项目中使用您的代码,但没有任何反应!看到这张照片:link
  • @Momeks:你必须继承 UILabel 并将 -drawTextInRect: 实现放在那里。
  • @Momeks:如果你不知道如何在 Objective-C 中创建一个子类,你应该做一些谷歌搜索; Apple 有一份 Objective-C 语言指南。
  • 可能 - 我认为 2.5 年前我写这篇文章时不存在 :)
【解决方案2】:

一个更简单的解决方案是像这样使用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)

斯威夫特 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 在这种情况下,即只有内部轮廓有效。

【讨论】:

  • 为了方便在 Objective-C 中,这将是: label.attributedText = [[NSAttributedString alloc] initWithString:@"String" attributes:@{ NSStrokeColorAttributeName : [UIColor blackColor], NSForegroundColorAttributeName : [UIColor whiteColor] , NSStrokeWidthAttributeName : @-1.0 }];
  • 使用这个模因有效地传达了这种方法的细微差别
  • Swift 4.2: let strokeTextAttributes: [String: Any] = [ NSStrokeColorAttributeName : UIColor.black, NSForegroundColorAttributeName : UIColor.white, NSStrokeWidthAttributeName : -4.0, ] consoleView.typingAttributes = strokeTextAttributes
  • 谢谢@TeoSartori,我在回答中添加了 Swift 4.2 语法;)
【解决方案3】:

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

结果:

【讨论】:

  • 不错。我会用这个做一个 IBInspectable :)
  • @Lachezar 如何使用 UIButton 的文本来完成?
【解决方案4】:

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

替换:

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

我不能 100% 确定,如果这是真的,因为我不知道 self.textColor = textColor; 是否与 [textColor setFill] 具有相同的效果,但它应该可以工作。

披露:我是 THLabel 的开发者。

我不久前发布了一个 UILabel 子类,它允许文本中的轮廓和其他效果。你可以在这里找到它:https://github.com/tobihagemann/THLabel

【讨论】:

  • 刚用过THLabel,生活就是这么复杂
  • 感谢 THLabel,我只需添加两行代码就可以在文本周围画出轮廓。感谢您为我花了太长时间试图解决的问题提供解决方案!
  • 我很惊讶没有人注意到,但是内置的文本笔划命令不会创建 outline,而是创建一个“inline i>" - 即笔画绘制在字母的内侧,而不是外侧。使用 THLabel,我们可以选择将笔画实际绘制在外部。干得好!
【解决方案5】:

基于 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中实现。然后,您将能够从“属性”窗格设置轮廓的宽度和颜色。

【讨论】:

  • 也非常适合 Swift 5。
  • 文本在两侧被剪掉。
  • 这个标签有 5MB 的内存占用 - 哎哟!
【解决方案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..

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

【讨论】:

  • 这不是标签周围的一个像素边框。这是一个简单的投影到底部。
  • 这是错误的解决方案。天哪,谁把它标出来了?!
  • 他们说得很清楚'轮廓',这是一个影子。不回答问题
【解决方案7】:

如果你想为复杂的东西制作动画,最好的方法是以编程的方式截取它的屏幕截图,而不是动画!

要截取视图,您需要如下代码:

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

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

希望能加快你的动画速度!!

没有

【讨论】:

  • 这是一个很棒的技巧,我可以想到几个可以使用的地方,并且可能解决了性能问题,但我仍然希望有一种比我糟糕的子类更简单的方法来制作 uilabel文本显示轮廓。同时,我将使用您的示例,谢谢!
  • 我感受到你的痛苦。我不确定你会找到这样做的好方法。我经常为产品效果编写大量代码,然后对它们进行快照(如上)以获得流畅的动画!对于上面的示例,您需要在代码中包含 !祝你好运! N
【解决方案8】:

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

我会评论他的回答,但显然我不够“有信誉”。

【讨论】:

    【解决方案9】:

    如果你的目标是这样的:

    这是我实现它的方法:我在当前的 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)
    

    【讨论】:

    • 这个选项会破坏多行
    • 这个答案比其他一些答案更好,因为它不会在字符内部绘制,而是在字符周围绘制。对于其他一些答案,如果您将宽度增加得足够大,则不再有字符 - 只是轮廓。
    【解决方案10】:

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

    那么 我确实认为你让问题变得比现在更难...... 我不知道你应该使用哪个'draw rect / frameRect'函数,但你很容易找到它。这个方法只是演示了策略(让超类完成工作!):

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

    【讨论】:

    • 我不明白。可能是因为我已经盯着屏幕看了大约 12 个小时,而此时我没有得到太多……
    • 你正在继承 UILabel,一个已经绘制文本的类。子类化的原因是您想在文本周围添加黑色边框。所以如果你让超类绘制文本,那么你所要做的就是绘制边框,不是吗?
    • 这是一个好点...虽然,如果他想添加一个 1 像素的黑色边框,子类可能会将字母拉得太近! ....我会做一些类似于在原始问题中发布的事情并进行优化(如我的帖子中所述)! N
    • @nickcartwright:如果出现超类按照你说的那样做的情况,你可以在调用 [super drawRect] 之前插入 CGRect。仍然比重写超类的功能更容易、更安全。
    • 然后@kent 沮丧地离开了。很好的答案,+1。
    【解决方案11】:

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

    【讨论】:

      【解决方案12】:

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

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

      【讨论】:

        【解决方案13】:

        也可以使用以下逻辑对 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;
        }
        

        【讨论】:

          【解决方案14】:

          你为什么不在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 类中的。

          【讨论】:

          • 标签上的文字发生了变化,我需要文字本身有一个 1 像素的黑色轮廓。 (UILabel 的其余部分背景是透明的。)
          • 我以为我明白这将如何导致我放置在 UILabel 中的任何文本都被勾勒出来,但我非常疲倦,现在我似乎无法思考它如何实现这一点。我要去阅读 initWithPatternImage。
          【解决方案15】:

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

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

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

          【讨论】:

          • 这会在整个标签周围添加一个大边框,而不是在文本周围
          猜你喜欢
          • 2020-12-23
          • 1970-01-01
          • 2022-01-04
          • 1970-01-01
          • 1970-01-01
          • 2022-01-09
          • 2015-08-17
          • 1970-01-01
          相关资源
          最近更新 更多