如何使用UitextView和NslayoutManager调整自定义下划线的垂直位置,并使用非零线条nsparagragraphStyle?

发布于 2025-02-08 03:12:46 字数 3079 浏览 2 评论 0原文

我想自定义nsSattributtedString的下划线样式,因为statersDot实际上只是短dashes,我实际上想要圆形的点(例如,请参见 nsattributtedstring(ios7+)中的自定义下划线模式)。

我已经走了很远,但是我的下划线遇到了不一致的一致性问题,这显然是由于定制线间距。如果下划线不在最后一行的文本中,那么下划线就太远了,但是如果它在最后一行的文本上,则太远了。大概是因为最后一行没有任何线间距。我想如果有办法,在我的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 <- adding this helps a bit with an underline on the last line but messes up other lines even more
    )

    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 // any non-zero value

textView.attributedText = NSAttributedString(
    string: myText,
    attributes: [
        .paragraphStyle: paragraphStyle
        .underlineStyle: NSUnderlineStyle.patternCircularDot.union(.single).rawValue
    ]
)

编辑:自定义字体实际上并不重要,仅在段落样式上的线间距就相应地简化了问题:

I want to customize the underline style for NSSAttributedString because patternDot is actually just short dashes, and I want actually circular dots (e.g. see Customize underline pattern in NSAttributedString (iOS7+)).

I got pretty far but I'm having an inconsistent alignment issue with the resulting underline, apparently because of custom line spacing. The underline is too far down if it's not on the last line of text but it's too far up if it's on the last line of text. Presumably it's because the last line doesn't have any line spacing underneath it. I suppose if there was a way, inside my drawUnderline to figure out what line of text I was on, I could adjust the y offset accordingly, but is there a simpler way?

Example:

example underline

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 <- adding this helps a bit with an underline on the last line but messes up other lines even more
    )

    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 // any non-zero value

textView.attributedText = NSAttributedString(
    string: myText,
    attributes: [
        .paragraphStyle: paragraphStyle
        .underlineStyle: NSUnderlineStyle.patternCircularDot.union(.single).rawValue
    ]
)

EDIT: the custom font doesn't actually matter, just the line spacing on the paragraph style, have simplified the question accordingly:

system font

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

蓝礼 2025-02-15 03:12:46

通过nsmutableParagragraphStyle设置行间距。

func layoutManager(_ layoutManager: NSLayoutManager, lineSpacingAfterGlyphAt glyphIndex: Int, withProposedLineFragmentRect rect: CGRect) -> CGFloat {
    // set line spacing as needed here
}

而不是通过nsmutableParagragraphStyle使用nslay> nslayoutmanagerdelegate's layoutmanager(linespacingafterterglyphat…)我什至设置了我的nslayoutmanager作为自己的委托子类。疯了,我知道。

然后,我调整了矩形以减去一半间距,这似乎很好。

Instead of setting the line spacing through a NSMutableParagraphStyle what seems to work is using NSLayoutManagerDelegate's layoutManager(lineSpacingAfterGlyphAt…)

func layoutManager(_ layoutManager: NSLayoutManager, lineSpacingAfterGlyphAt glyphIndex: Int, withProposedLineFragmentRect rect: CGRect) -> CGFloat {
    // set line spacing as needed here
}

I even set my NSLayoutManager subclass to be its own delegate. Crazy, I know.

Then I adjusted my rect offset to subtract half the line spacing, which seems to work well.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文