使用NSAttributedString设置空格红色下划线时行尾空格无下划线问题
问题根因
iOS 富文本默认渲染规则下,当段落使用 .byWordWrapping 按单词换行时,会自动隐藏行尾空白字符的装饰属性(包括下划线、删除线、背景色等),仅保留字符的占位空间,所以会出现空格可选中、但下划线不显示的情况。
解决方案
方案1:空格后追加零宽不换行字符(优先推荐)
零宽不换行字符 \u{2060} 无视觉宽度也不会触发换行,添加在普通空格后,系统就不会将该空格判定为行尾可忽略的空白,下划线就能正常显示,不需要修改其他排版配置,对原文本的排版、字符统计几乎无影响。
仅需修改富文本生成逻辑即可:
let textView = UITextView() let text = "word1 word2 word3 word4 word5 word6 word7 word8 word9 word10 word11 word12" let attributedText = NSMutableAttributedString() for char in text { if char == " " { // 空格后追加零宽不换行字符 let content = String(char) + "\u{2060}" attributedText.append(NSAttributedString(string: content, attributes: [ .font: UIFont.systemFont(ofSize: 30, weight: .bold), .underlineColor: UIColor.red, .underlineStyle: NSUnderlineStyle.thick.rawValue ])) } else { attributedText.append(NSAttributedString(string: String(char), attributes: [ .font: UIFont.systemFont(ofSize: 30, weight: .bold) ])) } } let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.lineSpacing = 8 paragraphStyle.lineBreakMode = .byWordWrapping attributedText.addAttribute(.paragraphStyle, value: paragraphStyle, range: NSMakeRange(0, attributedText.length)) textView.attributedText = attributedText textView.isEditable = false textView.textContainerInset = .zero
方案2:自定义NSLayoutManager强制绘制
如果不能修改原文本内容,可以自定义布局管理器,重写下划线绘制逻辑,主动识别行尾空格并补充绘制下划线:
// 自定义布局管理器 class UnderlineLayoutManager: NSLayoutManager { override func drawUnderline(forGlyphRange glyphRange: NSRange, underlineType underlineVal: NSUnderlineStyle, baselineOffset: CGFloat, lineFragmentRect lineRect: CGRect, lineFragmentGlyphRange lineGlyphRange: NSRange, containerOrigin: CGPoint) { super.drawUnderline(forGlyphRange: glyphRange, underlineType: underlineVal, baselineOffset: baselineOffset, lineFragmentRect: lineRect, lineFragmentGlyphRange: lineGlyphRange, containerOrigin: containerOrigin) // 检查当前glyph范围是否是行尾空格,手动绘制下划线 let charRange = self.characterRange(forGlyphRange: glyphRange, actualGlyphRange: nil) guard let text = self.textStorage?.string as NSString? else { return } let char = text.character(at: charRange.location) if char == 32, NSLocationMaxRange(glyphRange) == NSLocationMaxRange(lineGlyphRange) { // 是行尾空格,获取位置手动绘制红色粗下划线 let glyphRect = self.boundingRect(forGlyphRange: glyphRange, in: self.textContainers[0]) let underlineRect = CGRect(x: glyphRect.minX + containerOrigin.x, y: glyphRect.maxY - 2 + containerOrigin.y, width: glyphRect.width, height: 2) UIColor.red.setFill() UIRectFill(underlineRect) } } } // 使用自定义布局管理器初始化UITextView let textStorage = NSTextStorage() let layoutManager = UnderlineLayoutManager() textStorage.addLayoutManager(layoutManager) let textContainer = NSTextContainer(size: CGSize(width: 期望的textView宽度, height: CGFloat.greatestFiniteMagnitude)) layoutManager.addTextContainer(textContainer) let textView = UITextView(frame: .zero, textContainer: textContainer) // 后续富文本赋值、其他属性配置和原来一致即可
该方案完全不改动原文本内容,但需要额外处理绘制逻辑,适合不允许修改原始文本的场景。
内容的提问来源于stack exchange,提问作者user2547460
相关产品推荐
相关产品推荐

