使用 Text Kit 将基线与大行高的字符对齐
Align baselines with characters in large line heights with Text Kit
当我使用 Text Kit 绘制具有固定行高的属性字符串时,字符总是与行片段的底部对齐。虽然这在字符大小不同的一行上是有意义的,但这会打断多行文本的流动。基线的出现由每行的最大下降端决定。
我从 Sketch 背后的人那里找到了 article,他们更详细地解释了这个确切的问题并展示了他们的解决方案,但显然没有解释 如何 他们做到了。
这基本上就是我想要的:
当显示行高较大的两行时,这个结果很不理想:
我使用的代码:
let smallFont = UIFont.systemFont(ofSize: 15)
let bigFont = UIFont.systemFont(ofSize: 25)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.minimumLineHeight = 22
paragraphStyle.maximumLineHeight = 22
var attributes = [
NSFontAttributeName: smallFont,
NSParagraphStyleAttributeName: paragraphStyle
]
let textStorage = NSTextStorage()
let textContainer = NSTextContainer(size: CGSize(width: 250, height: 500))
let layoutManager = NSLayoutManager()
textStorage.append(NSAttributedString(string: "It is a long established fact that a reader will be ", attributes:attributes))
attributes[NSFontAttributeName] = bigFont
textStorage.append(NSAttributedString(string: "distracted", attributes:attributes))
attributes[NSFontAttributeName] = smallFont
textStorage.append(NSAttributedString(string: " by the readable content of a page when looking at its layout.", attributes:attributes))
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)
let textView = UITextView(frame: self.view.bounds, textContainer:textContainer)
view.addSubview(textView)
我设法让它工作,但不幸的是不得不放弃对 iOS 8 和 macOS 10.10 的支持。
如果您实施 NSLayoutManager
的以下委托调用,您可以决定如何处理每个行片段的 baselineOffset
:
optional func layoutManager(_ layoutManager: NSLayoutManager,
shouldSetLineFragmentRect lineFragmentRect: UnsafeMutablePointer<CGRect>,
lineFragmentUsedRect: UnsafeMutablePointer<CGRect>,
baselineOffset: UnsafeMutablePointer<CGFloat>,
in textContainer: NSTextContainer,
forGlyphRange glyphRange: NSRange) -> Bool
创建 NSTextStorage
后,对于每次后续更改,我都会枚举所有使用的字体,计算其默认行高 (NSLayoutManager.defaultLineHeightForFont()
) 并存储最大行高。在上述委托方法的实现中,我检查了所提供行片段 NSParagraphStyle
的当前行高,并在该值内对齐字体的行高。从那里可以计算出基线偏移,知道基线位于字体的 ascender
和 descender
之间。用 baselineOffset.memory(newOffset)
更新 baselineOffset
值,一切都应该按照您的意愿对齐。
注意:我不会详细介绍用于实现它的实际代码,因为我不确定我在这些计算中是否使用了正确的值.我可能会在不久的将来对整个方法进行尝试和验证后进行更新。
更新:调整基线的实施。每次 textContainer 更改时,我都会重新计算最大的行高和最大的下行。然后我基本上在布局管理器的委托函数中这样做:
var baseline: CGFloat = (lineFragmentRect.pointee.height - biggestLineHeight) / 2
baseline += biggestLineHeight
baseline -= biggestDescender
baseline = min(max(baseline, 0), lineFragmentRect.pointee.height)
baselineOffset.pointee = floor(baseline)
当我使用 Text Kit 绘制具有固定行高的属性字符串时,字符总是与行片段的底部对齐。虽然这在字符大小不同的一行上是有意义的,但这会打断多行文本的流动。基线的出现由每行的最大下降端决定。
我从 Sketch 背后的人那里找到了 article,他们更详细地解释了这个确切的问题并展示了他们的解决方案,但显然没有解释 如何 他们做到了。
这基本上就是我想要的:
当显示行高较大的两行时,这个结果很不理想:
我使用的代码:
let smallFont = UIFont.systemFont(ofSize: 15)
let bigFont = UIFont.systemFont(ofSize: 25)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.minimumLineHeight = 22
paragraphStyle.maximumLineHeight = 22
var attributes = [
NSFontAttributeName: smallFont,
NSParagraphStyleAttributeName: paragraphStyle
]
let textStorage = NSTextStorage()
let textContainer = NSTextContainer(size: CGSize(width: 250, height: 500))
let layoutManager = NSLayoutManager()
textStorage.append(NSAttributedString(string: "It is a long established fact that a reader will be ", attributes:attributes))
attributes[NSFontAttributeName] = bigFont
textStorage.append(NSAttributedString(string: "distracted", attributes:attributes))
attributes[NSFontAttributeName] = smallFont
textStorage.append(NSAttributedString(string: " by the readable content of a page when looking at its layout.", attributes:attributes))
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)
let textView = UITextView(frame: self.view.bounds, textContainer:textContainer)
view.addSubview(textView)
我设法让它工作,但不幸的是不得不放弃对 iOS 8 和 macOS 10.10 的支持。
如果您实施 NSLayoutManager
的以下委托调用,您可以决定如何处理每个行片段的 baselineOffset
:
optional func layoutManager(_ layoutManager: NSLayoutManager,
shouldSetLineFragmentRect lineFragmentRect: UnsafeMutablePointer<CGRect>,
lineFragmentUsedRect: UnsafeMutablePointer<CGRect>,
baselineOffset: UnsafeMutablePointer<CGFloat>,
in textContainer: NSTextContainer,
forGlyphRange glyphRange: NSRange) -> Bool
创建 NSTextStorage
后,对于每次后续更改,我都会枚举所有使用的字体,计算其默认行高 (NSLayoutManager.defaultLineHeightForFont()
) 并存储最大行高。在上述委托方法的实现中,我检查了所提供行片段 NSParagraphStyle
的当前行高,并在该值内对齐字体的行高。从那里可以计算出基线偏移,知道基线位于字体的 ascender
和 descender
之间。用 baselineOffset.memory(newOffset)
更新 baselineOffset
值,一切都应该按照您的意愿对齐。
注意:我不会详细介绍用于实现它的实际代码,因为我不确定我在这些计算中是否使用了正确的值.我可能会在不久的将来对整个方法进行尝试和验证后进行更新。
更新:调整基线的实施。每次 textContainer 更改时,我都会重新计算最大的行高和最大的下行。然后我基本上在布局管理器的委托函数中这样做:
var baseline: CGFloat = (lineFragmentRect.pointee.height - biggestLineHeight) / 2
baseline += biggestLineHeight
baseline -= biggestDescender
baseline = min(max(baseline, 0), lineFragmentRect.pointee.height)
baselineOffset.pointee = floor(baseline)