如何使用UitextView和NslayoutManager调整自定义下划线的垂直位置,并使用非零线条nsparagragraphStyle?
我想自定义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:
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:
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
通过
nsmutableParagragraphStyle
设置行间距。而不是通过
nsmutableParagragraphStyle
使用nslay> nslayoutmanagerdelegate
'slayoutmanager(linespacingafterterglyphat…)
作为自己的委托子类。疯了,我知道。我什至设置了我的
nslayoutmanager
然后,我调整了矩形以减去一半间距,这似乎很好。
Instead of setting the line spacing through a
NSMutableParagraphStyle
what seems to work is usingNSLayoutManagerDelegate
'slayoutManager(lineSpacingAfterGlyphAt…)
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.