非零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
相关产品推荐
相关产品推荐

