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

非零lineSpacing下如何通过NSLayoutManager调整UITextView自定义下划线垂直位置

问题背景

我需要自定义NSAttributedString的下划线样式:系统提供的patternDot样式实际渲染为短横线效果,我需要实现真正的圆形点状下划线。
目前大部分实现已经完成,但出现了下划线对齐不一致的问题,该问题由自定义行间距触发:非文本最后一行的下划线位置偏下,最后一行的下划线位置偏上,推测原因是最后一行下方不会渲染行间距。我原本考虑在drawUnderline方法中判断当前绘制的下划线所属文本行,以此调整y轴偏移量,但想了解是否存在更简便的实现方案。

效果示例:
下划线效果示例

核心实现代码
class DotUnderlineLayoutManager: NSLayoutManager {
  let color: UIColor

  init(color: UIColor = .black) {
    self.swatch = swatch
    super.init()
  }

  override func drawUnderline(forGlyphRange glyphRange: NSRange, underlineType _: NSUnderlineStyle, baselineOffset: CGFloat, lineFragmentRect: CGRect, lineFragmentGlyphRange _: NSRange, containerOrigin: CGPoint) {
    guard let container = textContainer(
      forGlyphAt: glyphRange.location,
      effectiveRange: nil
    ) else { return }

    let rect = boundingRect(forGlyphRange: glyphRange, in: container)

    let offsetRect = rect.offsetBy(
      dx: containerOrigin.x,
      dy: containerOrigin.y // + baselineOffset <- 加上该值对最后一行下划线位置有一定修正效果,但会让其他行的下划线错位更严重
    )

    let path = UIBezierPath()
    path.strokeDottedLine(under: offsetRect, color: color)    
  }
}

private extension UIBezierPath {
  func strokeDottedLine(under rect: CGRect, color: UIColor) {
    lineWidth = 2
    lineCapStyle = .round
    setLineDash([0.1, 5], count: 2, phase: 0)
    move(to: .init(x: rect.minX, y: rect.maxY))
    addLine(to: .init(x: rect.maxX, y: rect.maxY))
    color.setStroke()
    stroke()
  }
}



extension NSUnderlineStyle {
  static var patternCircularDot: NSUnderlineStyle {
    NSUnderlineStyle(rawValue: 0x11)
  }
}
let textView: UITextView = {
    let layout = DotUnderlineLayoutManager(swatch: swatch)
    let storage = NSTextStorage()
    storage.addLayoutManager(layout)
    let initialSize = CGSize(width: 0, height: CGFloat.greatestFiniteMagnitude)
    let container = NSTextContainer(size: initialSize)
    container.widthTracksTextView = true
    layout.addTextContainer(container)

    let textView = UITextView(frame: .zero, textContainer: container)
    textView.isUserInteractionEnabled = false
    textView.isEditable = false
    textView.isScrollEnabled = false
    textView.backgroundColor = .clear
    return textView
}()

let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineBreakMode = .byWordWrapping
paragraphStyle.lineSpacing = 10 // 任意非零值都会触发问题

textView.attributedText = NSAttributedString(
    string: myText,
    attributes: [
        .paragraphStyle: paragraphStyle
        .underlineStyle: NSUnderlineStyle.patternCircularDot.union(.single).rawValue
    ]
)
补充说明
  • 自定义字体不会影响该问题,仅段落样式中设置的行间距会触发该异常,我已据此简化问题场景
  • 系统字体下的效果如下:
    系统字体下的效果

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 06:09:24