使用Text Kit将基线与大线高度中的字符对齐

时间:2016-11-17 14:03:33

标签: ios swift macos textkit

当我使用Text Kit绘制具有固定行高的属性字符串时,字符始终与行片段的底部对齐。虽然这在字符大小不同的一行上是有意义的,但这会打破多行文本的流动。基线由每条线的最大下降线决定。

我发现Sketch背后的人article更详细地解释了这个确切的问题,并展示了他们的解决方案的作用,但显然没有解释他们是如何实现这一目标的。

这基本上是我想要的: bad: shifted baseline, good: baselines remain constant

当显示两条线条高度较大的线条时,这个结果远非理想: characters not optically aligned

我正在使用的代码:

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)

1 个答案:

答案 0 :(得分:1)

我设法让这个工作,但不幸的是不得不放弃对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的当前行高,并在该值内对齐字体的行高。从那里可以计算基线偏移,知道基线位于字体ascenderdescender之间。使用baselineOffset更新baselineOffset.memory(newOffset)值,所有内容都应按照您的喜好进行对齐。

注意:我没有详细介绍用于实现此操作的实际代码,因为我不确定我是否在这些代码中使用正确的值计算。我可能会在不久的将来对整个方法进行尝试和验证时对此进行更新。

更新:调整基线的实施。每次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)