Chr*_*orr 21 iphone typography objective-c uilabel ios
对于我实际想要实现的目标,存在很多困惑.所以请让我重新提出我的问题.它很简单:
如何苹果内部中心文本
UILabel
的-drawTextInRect:
?
以下是我为什么要寻找Apple中心文本方法的背景:
但是,有些地方我不能注入这个算法,甚至没有方法调整.了解他们使用的确切算法(或相当的算法)将解决我的个人问题.
这是一个可以试验的演示项目.如果您对Apple的实现问题感兴趣,请查看左侧(基本UILabel):文本在使用滑块增加字体大小时上下跳动,而不是逐渐向下移动.
同样,我正在寻找的只是一个实现-drawTextInRect:
与基数相同UILabel
- 但没有调用super
.
- (void)drawTextInRect:(CGRect)rect
{
CGRect const centeredRect = ...;
[self.attributedText drawInRect:centeredRect];
}
Run Code Online (Sandbox Code Playgroud)
我没有答案,但我做了一些研究,并认为我会分享它.我使用了Xtrace,它可以让你跟踪Objective-C方法调用,试图弄清楚UILabel在做什么.我添加Xtrace.h
并Xtrace.mm
从X跟踪git仓库基督教的的UILabel示范项目.在RootViewController.m
,我补充说#import "Xtrace.h"
,然后尝试各种跟踪过滤器.将以下内容添加到RootViewController中loadView
:
[Xtrace includeMethods:@"drawWithRect|drawInRect|drawTextInRect"];
[Xtrace traceClassPattern:@"String|UILabel|AppleLabel" excluding:nil];
Run Code Online (Sandbox Code Playgroud)
给我这个输出:
| [<AppleLabel 0x7fedf141b520> drawTextInRect:{{0, 0}, {187.5, 333.5}}]
| [<NSConcreteMutableAttributedString 0x7fedf160ec30>/NSAttributedString drawInRect:{{0, 156.5}, {187.5, 333.5}}]
| [<UILabel 0x7fedf1418dc0> drawTextInRect:{{0, 0}, {187.5, 333.5}}]
| [<UILabel 0x7fedf1418dc0> _drawTextInRect:{{0, 0}, {187.5, 333.5}} baselineCalculationOnly:0]
| [<__NSCFConstantString 0x1051d1db0>/NSString drawWithRect:{{0, 156.3134765625}, {187.5, 20.287109375}} options:1L attributes:<__NSDictionaryM 0x7fedf141ae00> context:<NSStringDrawingContext 0x7fedf15503c0>]
Run Code Online (Sandbox Code Playgroud)
(我正在使用Xcode 7.0 beta,iOS 9.0模拟器运行它.)
我们可以看到Apple的实现不会发送drawInRect:
到NSAttributedString,而是发送到drawWithRect:options:attributes:
NSString.我猜这些最终会做同样的事情.
我们还可以准确地看到计算出的Y偏移量.在滑块的这个初始位置,它是156.3134765625.有趣的是,它不是落在某个舍入的整数值上,或0.25或类似的倍数,否则将成为跳跃的解释.
我们还看到UILabel发送20.287109375作为高度,这与计算的值相同self.attributedText.size.height
.所以它似乎正在使用它,虽然我无法跟踪该调用(也许它直接使用Core Text?).
所以这就是我被困住的地方.一直试图看看UILabel计算的内容和明显的公式"labelYOffset =(boundsHeight - labelHeight)/ 2.0"之间的区别,但我发现没有一致的模式.
更新1. 因此我们可以将这个难以捉摸的Y偏移建模为
labelYOffset =(boundsHeight - labelHeight)/ 2.0 +错误
这error
是什么,是我们想要弄清楚的.让我们试着像科学家一样研究这个问题. 当我将幻灯片拖到结尾然后到开头时,这是Xtrace输出.我还在每次更改滑块时添加了一个NSLog,这有助于分隔条目.我写了一个Python脚本来绘制错误与标签高度.
有趣.我们看到错误有一些线性,但是如果你理解我的意思,它们会在某些点之间奇怪地跳线.这里发生了什么?在这个问题解决之前我会失眠.:)
在 WWDC 两年零一周后,我终于明白到底发生了什么。
至于为什么UILabel
会这样:看起来苹果试图将ascender 和 Descender之间的任何内容居中。然而,实际的文本绘制引擎总是将基线对齐到像素边界。如果在计算绘制文本的矩形时不注意这一点,基线可能会看似任意地上下跳跃。
主要问题是找到苹果公司的基线在哪里。AutoLayout 提供了firstBaselineAnchor,但遗憾的是无法从代码中读取它的值。只要标签的高度四舍五入为像素,以下代码片段似乎就可以正确预测所有屏幕上的基线scale
。contentScaleFactor
extension UILabel {
public var predictedBaselineOffset: CGFloat {
let scale = self.window?.screen.scale ?? UIScreen.main.scale
let availablePixels = round(self.bounds.height * scale)
let preferredPixels = ceil(self.font.lineHeight * scale)
let ascenderPixels = round(self.font.ascender * scale)
let offsetPixels = ceil((availablePixels - preferredPixels) / 2)
return (ascenderPixels + offsetPixels) / scale
}
}
Run Code Online (Sandbox Code Playgroud)
当标签的高度未舍入为像素时,预测基线通常会偏离一个像素,例如,对于 2x 设备上 40.1pt 容器中的 13.0pt 字体或 3x 设备上 40.1pt 容器中的 16.0pt 字体。
请注意,我们必须在这里进行像素级别的工作。由于浮点不准确,使用点(即立即除以屏幕比例而不是最后除以)会导致 @3x 屏幕上的差一错误。
例如,将 47px (15.667pt) 的内容居中于 121px 容器 (40.333pt) 中,会产生 12.333pt 的偏移量,由于浮点错误,该偏移量上限为 12.667pt。
为了回答我最初提出的问题,我们可以-drawTextInRect:
使用(希望是正确的)预测基线来实现,以产生与UILabel
之前相同的结果。
public class AppleImitatingLabel: UILabel {
public override func drawText(in rect: CGRect) {
let offset = self.predictedBaselineOffset
let frame = rect.offsetBy(dx: 0, dy: offset)
self.attributedText!.draw(with: frame, options: [], context: nil)
}
}
Run Code Online (Sandbox Code Playgroud)
请参阅GitHub 上的项目以获取有效的实现。
归档时间: |
|
查看次数: |
1580 次 |
最近记录: |