You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为Attributed text指定文本范围添加圆角背景(无需改用UILabel)

嘿,我完全懂你不想大改项目的痛点!不用替换UILabel,咱们靠CoreText和自定义属性就能搞定带圆角的高亮背景——我之前做类似需求的时候就是这么解决的,改动量极小。

核心思路

系统默认的NSBackgroundColorAttributeName确实没有圆角配置,但我们可以:

  1. 给AttributedText添加自定义属性,存储圆角背景的配置(颜色、圆角半径、内边距)
  2. 在文本控件的绘制阶段,手动读取这些自定义属性,用UIBezierPath绘制带圆角的背景

具体实现步骤

1. 定义自定义属性和配置类

首先我们需要一个标识自定义圆角背景的属性键,以及存储配置的类:

// 自定义属性键,用来标记需要圆角背景的文本范围
static NSString *const CustomRoundedBackgroundAttributeName = @"CustomRoundedBackgroundAttributeName";

// 存储圆角背景的配置信息
@interface RoundedBackgroundConfig : NSObject
@property (nonatomic, strong) UIColor *backgroundColor; // 背景色
@property (nonatomic, assign) CGFloat cornerRadius;     // 圆角半径
@property (nonatomic, assign) UIEdgeInsets padding;     // 文本与背景的内边距
@end

@implementation RoundedBackgroundConfig
@end

2. 给目标文本添加圆角背景属性

接下来,给你需要高亮的文本范围添加这个自定义属性:

// 假设你已经有了基础的AttributedString
NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString:@"这里是需要高亮的文本,还有另一处需要圆角高亮的内容"];

// 配置第一处高亮的圆角背景
RoundedBackgroundConfig *config1 = [[RoundedBackgroundConfig alloc] init];
config1.backgroundColor = [UIColor blueColor];
config1.cornerRadius = 4.0;
config1.padding = UIEdgeInsetsMake(2, 4, 2, 4); // 上下各2pt,左右各4pt内边距
[attributedText addAttribute:CustomRoundedBackgroundAttributeName 
                       value:config1 
                       range:NSMakeRange(5, 8)]; // 替换成你的目标文本范围

// 配置第二处高亮的圆角背景
RoundedBackgroundConfig *config2 = [[RoundedBackgroundConfig alloc] init];
config2.backgroundColor = [UIColor blueColor];
config2.cornerRadius = 4.0;
config2.padding = UIEdgeInsetsMake(2, 4, 2, 4);
[attributedText addAttribute:CustomRoundedBackgroundAttributeName 
                       value:config2 
                       range:NSMakeRange(20, 6)]; // 替换成你的第二处文本范围

// 把配置好的AttributedText赋值给你的文本控件(UILabel/UITextView)
yourLabel.attributedText = attributedText;

3. 让文本控件识别并绘制圆角背景

这里分两种情况,对应UILabel和UITextView:

情况A:使用UILabel(无需替换现有实例)

给UILabel添加一个Category,重写绘制方法,在绘制文本前先画圆角背景:

@interface UILabel (RoundedBackground)
@end

@implementation UILabel (RoundedBackground)

- (void)drawTextInRect:(CGRect)rect {
    NSAttributedString *attributedText = self.attributedText;
    if (!attributedText) {
        [super drawTextInRect:rect];
        return;
    }

    // 遍历所有带自定义圆角属性的文本范围,绘制背景
    [attributedText enumerateAttribute:CustomRoundedBackgroundAttributeName 
                               inRange:NSMakeRange(0, attributedText.length) 
                               options:0 
                            usingBlock:^(id  _Nullable value, NSRange range, BOOL * _Nonnull stop) {
        if (![value isKindOfClass:[RoundedBackgroundConfig class]]) return;
        
        RoundedBackgroundConfig *config = value;
        
        // 用CoreText计算文本的实际绘制位置
        NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
        NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:self.bounds.size];
        textContainer.lineFragmentPadding = 0;
        textContainer.maximumNumberOfLines = self.numberOfLines;
        textContainer.lineBreakMode = self.lineBreakMode;
        [layoutManager addTextContainer:textContainer];

        NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedText];
        [textStorage addLayoutManager:layoutManager];

        // 获取文本范围对应的绘制矩形
        NSRange glyphRange = [layoutManager glyphRangeForCharacterRange:range actualCharacterRange:nil];
        NSArray *rects = [layoutManager boundingRectsForGlyphRange:glyphRange inTextContainer:textContainer];

        // 逐个绘制圆角背景(支持多行文本换行)
        for (CGRect r in rects) {
            CGRect drawRect = UIEdgeInsetsInsetRect(r, config.padding);
            // 调整垂直居中(适配UILabel的文本对齐)
            drawRect.origin.y += (self.bounds.size.height - self.font.lineHeight) / 2.0;
            UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:drawRect cornerRadius:config.cornerRadius];
            [config.backgroundColor setFill];
            [path fill];
        }
    }];

    // 最后绘制文本内容
    [super drawTextInRect:rect];
}

@end

情况B:使用UITextView

自定义一个UITextView子类,利用NSLayoutManagerDelegate的方法绘制背景:

@interface CustomTextView : UITextView <NSLayoutManagerDelegate>
@end

@implementation CustomTextView

- (instancetype)initWithFrame:(CGRect)frame textContainer:(NSTextContainer *)textContainer {
    self = [super initWithFrame:frame textContainer:textContainer];
    if (self) {
        self.layoutManager.delegate = self;
    }
    return self;
}

- (void)layoutManager:(NSLayoutManager *)layoutManager drawBackgroundForGlyphRange:(NSRange)glyphsToShow atPoint:(CGPoint)origin {
    // 先调用系统默认绘制,避免覆盖原有内容
    [layoutManager drawBackgroundForGlyphRange:glyphsToShow atPoint:origin];

    NSAttributedString *attributedText = self.attributedText;
    [attributedText enumerateAttribute:CustomRoundedBackgroundAttributeName 
                               inRange:NSMakeRange(0, attributedText.length) 
                               options:0 
                            usingBlock:^(id  _Nullable value, NSRange range, BOOL * _Nonnull stop) {
        if (![value isKindOfClass:[RoundedBackgroundConfig class]]) return;
        
        RoundedBackgroundConfig *config = value;
        // 转换字符范围为Glyph范围
        NSRange glyphRange = [layoutManager glyphRangeForCharacterRange:range actualCharacterRange:nil];
        if (NSIntersectionRange(glyphRange, glyphsToShow).length == 0) return;

        // 获取文本绘制矩形
        NSArray *rects = [layoutManager boundingRectsForGlyphRange:glyphRange inTextContainer:self.textContainer];
        if (rects.count == 0) return;

        // 逐个绘制圆角背景(支持多行)
        for (CGRect r in rects) {
            CGRect totalRect = UIEdgeInsetsInsetRect(r, config.padding);
            totalRect.origin.x += origin.x;
            totalRect.origin.y += origin.y;
            
            UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:totalRect cornerRadius:config.cornerRadius];
            [config.backgroundColor setFill];
            [path fill];
        }
    }];
}

@end

额外说明

  • 这个方案不需要大规模替换现有控件:UILabel只需引入Category即可生效,UITextView只需把当前使用的实例换成自定义子类(如果是xib/storyboard,改个类名就行)
  • 多行文本的高亮也能完美适配,每个换行的文本块都会单独绘制圆角背景
  • 内边距和圆角半径可以根据视觉需求灵活调整

内容的提问来源于stack exchange,提问作者Adil Malik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 09:38:47