在一个UILabel中如何同时显示粗体和非粗体文本?

261

如何在UILabel中包含粗体和非粗体文本?

我不想使用UIWebView...我也读到可能可以使用NSAttributedString实现这个目标,但我不知道该如何使用。有任何想法吗?

苹果在他们的一些应用程序中实现了这一点; 示例截图:link text

谢谢! - Dom


请查看之前 Stack Overflow 上的这个主题。(基本上,创建两个 UILabel 并将它们正确地相对定位。) - samkass
15个回答

365

更新

在Swift中,除了语法更短,因此一切变得非常简单外,我们不必再处理iOS5的旧东西:

Swift 5

func attributedString(from string: String, nonBoldRange: NSRange?) -> NSAttributedString {
    let fontSize = UIFont.systemFontSize
    let attrs = [
        NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: fontSize),
        NSAttributedString.Key.foregroundColor: UIColor.black
    ]
    let nonBoldAttribute = [
        NSAttributedString.Key.font: UIFont.systemFont(ofSize: fontSize),
    ]
    let attrStr = NSMutableAttributedString(string: string, attributes: attrs)
    if let range = nonBoldRange {
        attrStr.setAttributes(nonBoldAttribute, range: range)
    }
    return attrStr
}

Swift 3

func attributedString(from string: String, nonBoldRange: NSRange?) -> NSAttributedString {
    let fontSize = UIFont.systemFontSize
    let attrs = [
        NSFontAttributeName: UIFont.boldSystemFont(ofSize: fontSize),
        NSForegroundColorAttributeName: UIColor.black
    ]
    let nonBoldAttribute = [
        NSFontAttributeName: UIFont.systemFont(ofSize: fontSize),
    ]
    let attrStr = NSMutableAttributedString(string: string, attributes: attrs)
    if let range = nonBoldRange {
        attrStr.setAttributes(nonBoldAttribute, range: range)
    }
    return attrStr
}

使用方法:

let targetString = "Updated 2012/10/14 21:59 PM"
let range = NSMakeRange(7, 12)

let label = UILabel(frame: CGRect(x:0, y:0, width:350, height:44))
label.backgroundColor = UIColor.white
label.attributedText = attributedString(from: targetString, nonBoldRange: range)
label.sizeToFit()

奖励:国际化

有些人评论了国际化的问题。我个人认为这超出了本问题的范围,但出于教学目的,以下是我的解决方案。

// Date we want to show
let date = Date()

// Create the string.
// I don't set the locale because the default locale of the formatter is `NSLocale.current` so it's good for internationalisation :p
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .short
let targetString = String(format: NSLocalizedString("Update %@", comment: "Updated string format"),
                          formatter.string(from: date))

// Find the range of the non-bold part
formatter.timeStyle = .none
let nonBoldRange = targetString.range(of: formatter.string(from: date))

// Convert Range<Int> into NSRange
let nonBoldNSRange: NSRange? = nonBoldRange == nil ?
    nil :
    NSMakeRange(targetString.distance(from: targetString.startIndex, to: nonBoldRange!.lowerBound),
                targetString.distance(from: nonBoldRange!.lowerBound, to: nonBoldRange!.upperBound))

// Now just build the attributed string as before :)
label.attributedText = attributedString(from: targetString,
                                        nonBoldRange: nonBoldNSRange)

结果(假设有英语和日语的Localizable.strings可用)

enter image description here

enter image description here


iOS6及以后版本的先前答案(Objective-C仍然有效):

在iOS6中,UILabelUIButtonUITextViewUITextField支持属性字符串,这意味着我们不需要创建CATextLayer作为属性字符串的接收者。此外,为了创建属性字符串,我们不再需要使用CoreText :) 我们在obj-c Foundation.framework中有新的类,如NSParagraphStyle和其他常量,这将使我们的生活更轻松。耶!

因此,如果我们有这个字符串:

NSString *text = @"Updated: 2012/10/14 21:59"

我们只需要创建属性字符串:
if ([_label respondsToSelector:@selector(setAttributedText:)])
{
    // iOS6 and above : Use NSAttributedStrings
    
    // Create the attributes
    const CGFloat fontSize = 13;
    NSDictionary *attrs = @{
        NSFontAttributeName:[UIFont boldSystemFontOfSize:fontSize],
        NSForegroundColorAttributeName:[UIColor whiteColor]
    };
    NSDictionary *subAttrs = @{
        NSFontAttributeName:[UIFont systemFontOfSize:fontSize]
    };
    
    // Range of " 2012/10/14 " is (8,12). Ideally it shouldn't be hardcoded
    // This example is about attributed strings in one label
    // not about internationalisation, so we keep it simple :)
    // For internationalisation example see above code in swift
    const NSRange range = NSMakeRange(8,12);

    // Create the attributed string (text + attributes)
    NSMutableAttributedString *attributedText =
      [[NSMutableAttributedString alloc] initWithString:text
                                             attributes:attrs];
    [attributedText setAttributes:subAttrs range:range];

    // Set it in our UILabel and we are done!
    [_label setAttributedText:attributedText];
} else {
    // iOS5 and below
    // Here we have some options too. The first one is to do something
    // less fancy and show it just as plain text without attributes.
    // The second is to use CoreText and get similar results with a bit
    // more of code. Interested people please look down the old answer.

    // Now I am just being lazy so :p
    [_label setText:text];
}

这里有一对很好的入门博客文章在这里,由invasivecode的人撰写,用更多示例解释了NSAttributedString的用途,可以查看"iOS 6下NSAttributedString简介""使用Interface Builder创建带属性的字符串(iOS)" :)

附注:上述代码应该能够正常工作,但它是脑部编译的。我希望这已经足够了:)


iOS5及以下版本的旧答案

使用带有NSAttributedString的CATextLayer!比两个UILabel更轻巧和简单。 (iOS 3.2及以上)

示例。

不要忘记添加QuartzCore框架(CALayers所需),以及CoreText(属性字符串所需)。

#import <QuartzCore/QuartzCore.h>
#import <CoreText/CoreText.h>

以下示例将向导航控制器的工具栏添加一个子层,类似于 iPhone 上的 Mail.app。 :)

- (void)setRefreshDate:(NSDate *)aDate
{
    [aDate retain];
    [refreshDate release];
    refreshDate = aDate;

    if (refreshDate) {

        /* Create the text for the text layer*/    
        NSDateFormatter *df = [[NSDateFormatter alloc] init];
        [df setDateFormat:@"MM/dd/yyyy hh:mm"];

        NSString *dateString = [df stringFromDate:refreshDate];
        NSString *prefix = NSLocalizedString(@"Updated", nil);
        NSString *text = [NSString stringWithFormat:@"%@: %@",prefix, dateString];
        [df release];

        /* Create the text layer on demand */
        if (!_textLayer) {
            _textLayer = [[CATextLayer alloc] init];
            //_textLayer.font = [UIFont boldSystemFontOfSize:13].fontName; // not needed since `string` property will be an NSAttributedString
            _textLayer.backgroundColor = [UIColor clearColor].CGColor;
            _textLayer.wrapped = NO;
            CALayer *layer = self.navigationController.toolbar.layer; //self is a view controller contained by a navigation controller
            _textLayer.frame = CGRectMake((layer.bounds.size.width-180)/2 + 10, (layer.bounds.size.height-30)/2 + 10, 180, 30);
            _textLayer.contentsScale = [[UIScreen mainScreen] scale]; // looks nice in retina displays too :)
            _textLayer.alignmentMode = kCAAlignmentCenter;
            [layer addSublayer:_textLayer];
        }

        /* Create the attributes (for the attributed string) */
        CGFloat fontSize = 13;
        UIFont *boldFont = [UIFont boldSystemFontOfSize:fontSize];
        CTFontRef ctBoldFont = CTFontCreateWithName((CFStringRef)boldFont.fontName, boldFont.pointSize, NULL);
        UIFont *font = [UIFont systemFontOfSize:13];
        CTFontRef ctFont = CTFontCreateWithName((CFStringRef)font.fontName, font.pointSize, NULL);
        CGColorRef cgColor = [UIColor whiteColor].CGColor;
        NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
                                    (id)ctBoldFont, (id)kCTFontAttributeName,
                                    cgColor, (id)kCTForegroundColorAttributeName, nil];
        CFRelease(ctBoldFont);
        NSDictionary *subAttributes = [NSDictionary dictionaryWithObjectsAndKeys:(id)ctFont, (id)kCTFontAttributeName, nil];
        CFRelease(ctFont);

        /* Create the attributed string (text + attributes) */
        NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString:text attributes:attributes];
        [attrStr addAttributes:subAttributes range:NSMakeRange(prefix.length, 12)]; //12 is the length of " MM/dd/yyyy/ "

        /* Set the attributes string in the text layer :) */
        _textLayer.string = attrStr;
        [attrStr release];

        _textLayer.opacity = 1.0;
    } else {
        _textLayer.opacity = 0.0;
        _textLayer.string = nil;
    }
}

在这个例子中,我只有两种不同的字体(加粗和普通),但你也可以有不同的字体大小、不同的颜色、斜体、下划线等。请查看NSAttributedString / NSMutableAttributedStringCoreText attributes string keys

2
不幸的是,这个答案(以及其他答案)都不太适合国际化。像 Android 一样支持 HTML 标签(<b>,<i>)会很棒。 - Victor G
1
由于这只是一个示例,我更倾向于不处理那个东西。如果您需要本地化,可以从NSDate获取日期组件,并通过编程方式找到适当的粗体/非粗体范围(而不是硬编码范围,在上面的代码中有提到硬编码不理想的注释)。 - nacho4d
1
你应该考虑在代码中使用更易读的Objective-C字面量。例如,[NSDictionary dictionaryWithObjectsAndKeys: boldFont, NSFontAttributeName, foregroundColor, NSForegroundColorAttributeName, nil] 可以变成 @{ NSFontAttributeName: boldFont, NSForegroundColorAttributeName: foregroundColor } - PatrickNLT
@nacho4d 太好了!但是有一个错别字:语法需要花括号({),而不是方括号([)。 - PatrickNLT
我已经添加了一些代码,展示了一种友好的国际化方法。 - nacho4d
NSMutableAttributedString(string: string, attributes: attrs)是错误的,您应该像这样设置它NSMutableAttributedString(string: string),然后只有使用范围,否则它将使整个文本加粗,而不是范围。 - Mayur Kothawade

85

尝试一下UILabel上的类别:

以下是它的使用方法:

myLabel.text = @"Updated: 2012/10/14 21:59 PM";
[myLabel boldSubstring: @"Updated:"];
[myLabel boldSubstring: @"21:59 PM"];

这是分类的部分。

UILabel+Boldify.h

- (void) boldSubstring: (NSString*) substring;
- (void) boldRange: (NSRange) range;

UILabel+Boldify.m

- (void) boldRange: (NSRange) range {
    if (![self respondsToSelector:@selector(setAttributedText:)]) {
        return;
    }
    NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithAttributedString:self.attributedText];
    [attributedText setAttributes:@{NSFontAttributeName:[UIFont boldSystemFontOfSize:self.font.pointSize]} range:range];

    self.attributedText = attributedText;    
}

- (void) boldSubstring: (NSString*) substring {
    NSRange range = [self.text rangeOfString:substring];
    [self boldRange:range];
}

请注意,此功能仅适用于 iOS 6 及更高版本。在 iOS 5 及以下版本中将被忽略。


2
不错的分类。虽然它不会使字体加粗。要这样做,您应该将它设置为:@ {NSFontAttributeName:[UIFont boldSystemFontOfSize:self.font.pointSize]} 我已经点赞。 - Vivienne Fosh
1
如果您的标签字体不是系统字体,则需要更改:[UIFont boldSystemFontOfSize:self.font.pointSize][UIFont fontWithName:self.font.fontName size:self.font.pointSize] - lee

52

这在界面构建器(Interface Builder)中很容易实现:

1) 在属性检查器(Attributes Inspector)中使 UILabel 变成 富文本(Attributed)

加粗示例 步骤 1

2) 选择要加粗的短语部分

加粗示例 步骤 2

3) 修改字体 (或同一字体的加粗字型) 在 字体选择器(font selector)

加粗示例 步骤 3

就是这样了!


看起来你只能对粗体(和其他字体类型)进行此操作,而不能应用其他属性,比如下划线?(即使字体选择器有这些选项,但对我来说下划线是灰色的)你是否也遇到了同样的情况? - pj4533
3
看起来,这对于静态文本很好,不管怎样,在阅读这篇文章之前我不知道这一点。 - Preetam Jadakar
我对这个新的Interface Builder功能的担忧是,你被迫选择一个特定的自定义字体,而不再是系统字体,因此会错过所有针对部分视力障碍人士/可访问性的系统实现。 - Litome
2
我已经将文本的某些部分加粗,并且在属性检查器中显示应该是这样的,但在模拟器甚至在故事板中都没有显示出来。 - Besat

46

基于bbrame的分类,有一些类别。它的功能类似,但允许您多次使用带有累积结果的相同UILabel

UILabel+Boldify.h

@interface UILabel (Boldify)
- (void) boldSubstring: (NSString*) substring;
- (void) boldRange: (NSRange) range;
@end

UILabel+Boldify.m

@implementation UILabel (Boldify)
- (void)boldRange:(NSRange)range {
    if (![self respondsToSelector:@selector(setAttributedText:)]) {
        return;
    }
    NSMutableAttributedString *attributedText;
    if (!self.attributedText) {
        attributedText = [[NSMutableAttributedString alloc] initWithString:self.text];
    } else {
        attributedText = [[NSMutableAttributedString alloc] initWithAttributedString:self.attributedText];
    }
    [attributedText setAttributes:@{NSFontAttributeName:[UIFont boldSystemFontOfSize:self.font.pointSize]} range:range];
    self.attributedText = attributedText;
}

- (void)boldSubstring:(NSString*)substring {
    NSRange range = [self.text rangeOfString:substring];
    [self boldRange:range];
}
@end

有了这些更正,您可以多次使用它,例如:

myLabel.text = @"Updated: 2012/10/14 21:59 PM";
[myLabel boldSubstring: @"Updated:"];
[myLabel boldSubstring: @"21:59 PM"];

将会产生以下结果: "更新时间:2012/10/14 21:59 PM"。


疯狂的是它只会加粗最后一个子字符串,即仅限于 "21:59 PM"。 - Prajeet Shrestha
我在一年前测试过它,当时似乎是有效的。 我发帖的整个目的是将bbrame的类别更改为处理多个粗体。 我现在无法做到这一点,但两周后我会重新测试此代码,以确保其正常工作。 - Crazy Yoghurt
疯狂地检查我的答案,然后请建议如何使其可重用。 - Prajeet Shrestha

27

这个方法对我有用:

CGFloat boldTextFontSize = 17.0f;

myLabel.text = [NSString stringWithFormat:@"%@ 2012/10/14 %@",@"Updated:",@"21:59 PM"];

NSRange range1 = [myLabel.text rangeOfString:@"Updated:"];
NSRange range2 = [myLabel.text rangeOfString:@"21:59 PM"];

NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString:myLabel.text];

[attributedText setAttributes:@{NSFontAttributeName:[UIFont boldSystemFontOfSize:boldTextFontSize]}
                        range:range1];
[attributedText setAttributes:@{NSFontAttributeName:[UIFont boldSystemFontOfSize:boldTextFontSize]}
                        range:range2];

myLabel.attributedText = attributedText;

若要查看Swift版本,请参见此处


美丽而简单的!谢谢! - Mona

25
我采用了Crazy Yoghurt的答案来扩展Swift。
extension UILabel {

    func boldRange(_ range: Range<String.Index>) {
        if let text = self.attributedText {
            let attr = NSMutableAttributedString(attributedString: text)
            let start = text.string.characters.distance(from: text.string.startIndex, to: range.lowerBound)
            let length = text.string.characters.distance(from: range.lowerBound, to: range.upperBound)
            attr.addAttributes([NSFontAttributeName: UIFont.boldSystemFont(ofSize: self.font.pointSize)], range: NSMakeRange(start, length))
            self.attributedText = attr
        }
    }

    func boldSubstring(_ substr: String) {
        if let text = self.attributedText {
            var range = text.string.range(of: substr)
            let attr = NSMutableAttributedString(attributedString: text)
            while range != nil {
                let start = text.string.characters.distance(from: text.string.startIndex, to: range!.lowerBound)
                let length = text.string.characters.distance(from: range!.lowerBound, to: range!.upperBound)
                var nsRange = NSMakeRange(start, length)
                let font = attr.attribute(NSFontAttributeName, at: start, effectiveRange: &nsRange) as! UIFont
                if !font.fontDescriptor.symbolicTraits.contains(.traitBold) {
                    break
                }
                range = text.string.range(of: substr, options: NSString.CompareOptions.literal, range: range!.upperBound..<text.string.endIndex, locale: nil)
            }
            if let r = range {
                boldRange(r)
            }
        }
    }
}

也许Range和NSRange之间的转换不太好,但我没有找到更好的方法。


1
非常感谢!正是我所需要的!我在 boldSubstring(_:) 的第二行更改为 var range = text.string.range(of: substr, options: .caseInsensitive),以使具有不同大小写的字符串也可以加粗。 - heyfrank

21

看看TTTAttributedLabel。它是UILabel的替代品,通过将NSAttributedString设置为标签的文本,允许您在单个标签中具有混合字体和颜色。


5
同意使用替换方案(有几个可供选择)。苹果公司在这方面的工作还没有完成。除了作为学术演习外,我认为没有必要尝试理解和实现这个混乱的东西——它可能会在下一个发布版本中得到很好的整理。 :) https://github.com/AliSoftware/OHAttributedLabel - trapper
@trapper - 你用这个链接救了我的一天... +1000! - Duck
我也推荐使用OHAttributedLabel。您可以在字符串中直接使用HTML标签,如<b>和<u>(以及其他标签)。 - RyanG

12
在这种情况下,您可以尝试,
UILabel *displayLabel = [[UILabel alloc] initWithFrame:/*label frame*/];
displayLabel.font = [UIFont boldSystemFontOfSize:/*bold font size*/];

NSMutableAttributedString *notifyingStr = [[NSMutableAttributedString alloc] initWithString:@"Updated: 2012/10/14 21:59 PM"];
[notifyingStr beginEditing];
[notifyingStr addAttribute:NSFontAttributeName
                     value:[UIFont systemFontOfSize:/*normal font size*/]
                     range:NSMakeRange(8,10)/*range of normal string, e.g. 2012/10/14*/];
[notifyingStr endEditing];

displayLabel.attributedText = notifyingStr; // or [displayLabel setAttributedText: notifyingStr];

请先将值分配给标签(例如:displayLabel.text = @"更新时间:2013/12/23 21:59 PM";) - Mazen Kasser

8
要在UILabel中使文本加粗并带有下划线,只需在您的代码中添加以下几行即可。
NSRange range1 = [lblTermsAndCondition.text rangeOfString:NSLocalizedString(@"bold_terms", @"")];
NSRange range2 = [lblTermsAndCondition.text rangeOfString:NSLocalizedString(@"bold_policy", @"")];
NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString:lblTermsAndCondition.text];
[attributedText setAttributes:@{NSFontAttributeName:[UIFont fontWithName:fontBold size:12.0]}
                        range:range1];
[attributedText setAttributes:@{NSFontAttributeName:[UIFont fontWithName:fontBold size:12.0]}
                        range:range2];


[attributedText addAttribute:(NSString*)kCTUnderlineStyleAttributeName
                  value:[NSNumber numberWithInt:kCTUnderlineStyleSingle]
                  range:range1];

[attributedText addAttribute:(NSString*)kCTUnderlineStyleAttributeName
                       value:[NSNumber numberWithInt:kCTUnderlineStyleSingle]
                       range:range2];



lblTermsAndCondition.attributedText = attributedText;

6
NSString *needToChangeStr=@"BOOK";
NSString *display_string=[NSString stringWithFormat:@"This is %@",book];

NSMutableAttributedString *attri_str=[[NSMutableAttributedString alloc]initWithString:display_string];

int begin=[display_string length]-[needToChangeStr length];
int end=[needToChangeStr length];


[attri_str addAttribute:NSFontAttributeName value:[UIFont fontWithName:@"HelveticaNeue-Bold" size:30] range:NSMakeRange(begin, end)];

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接