如何为Attributed text指定文本范围添加圆角背景(无需改用UILabel)
嘿,我完全懂你不想大改项目的痛点!不用替换UILabel,咱们靠CoreText和自定义属性就能搞定带圆角的高亮背景——我之前做类似需求的时候就是这么解决的,改动量极小。
核心思路
系统默认的NSBackgroundColorAttributeName确实没有圆角配置,但我们可以:
- 给AttributedText添加自定义属性,存储圆角背景的配置(颜色、圆角半径、内边距)
- 在文本控件的绘制阶段,手动读取这些自定义属性,用
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
相关产品推荐
相关产品推荐

