UILabel如何将其文本垂直居中?

时间:2015-06-10 14:39:10

标签: ios objective-c iphone uilabel typography

对于我实际想要实现的目标存在很多困惑。所以请让我重新提出我的问题。它很简单:

  

Apple如何在UILabel的{​​{1}}内找到文字?

以下是我为什么要寻找Apple中心文字方法的背景信息:

  • 我不满意他们如何在这个方法中居中文本
  • 我有自己的算法,按照我想要的方式完成工作

然而,有些地方我不能注入这个算法,甚至没有方法调整。了解他们使用的确切算法(或相当于该算法)将解决我的个人问题。

这是您可以试验的demo project。如果您对Apple的实现问题感兴趣,请查看左侧(基本UILabel):文本在使用滑块增加字体大小时上下跳动,而不是逐渐向下移动。

同样,我正在寻找的是-drawTextInRect:的实现与基础-drawTextInRect:完全相同 - 但不调用UILabel

super

3 个答案:

答案 0 :(得分:8)

我没有答案,但我做了一些研究,并认为我会分享它。我使用了Xtrace,它可以让你跟踪Objective-C方法调用,试图弄清楚UILabel在做什么。我将Xtrace git存储库中的Xtrace.hXtrace.mm添加到了Christian的UILabel演示项目中。在RootViewController.m中,我添加了#import "Xtrace.h",然后尝试了各种跟踪过滤器。将以下内容添加到RootViewController的loadView

[Xtrace includeMethods:@"drawWithRect|drawInRect|drawTextInRect"];
[Xtrace traceClassPattern:@"String|UILabel|AppleLabel" excluding:nil];

给我这个输出:

| [<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>]

(我正在使用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是什么,是我们想要弄清楚的。让我们试着像科学家一样研究这个问题。当我将幻灯片拖到结尾然后到开头时,Here是Xtrace输出。我还在每次更改滑块时添加了一个NSLog,这有助于分隔条目。我写了a Python script来根据标签高度绘制错误。

Plot or UILabel error in vertical centering against label height

有趣。我们看到错误有一些线性,但是如果你理解我的意思,它们会在某些点之间奇怪地跳线。这里发生了什么?在这个问题解决之前我会失眠。 :)

答案 1 :(得分:2)

在WWDC工作了两年零一周后,我终于弄明白到底是怎么回事。

至于UILabel行为方式的原因:看起来Apple试图将ascender and descender之间的任何谎言置于中心位置。但是,实际的文本绘制引擎始终将基线捕捉到像素边界。如果在计算绘制文本的矩形时没有注意到这一点,那么基线可能会随意上下跳动。

主要问题是找到Apple放置基线的位置。 AutoLayout提供了firstBaselineAnchor,但遗憾的是,它的值不能从代码中读取。只要标签的高度四舍五入为像素,以下代码段就会在所有屏幕scalecontentScaleFactor处正确预测基线。

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
    }
}

当标签的高度没有四舍五入到像素时,预测的基线是经常关闭的像素,例如,对于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)
    }
}

请参阅project on GitHub了解有效的实施方案。

答案 2 :(得分:-2)

我在一个贴心的应用程序中处理了很多这个问题。

我认为UILabel的问题可能与包括上升器和下降器在内的字体不同。我的假设是UILabel在定位文本正文时会考虑这些值,这会对明显的垂直位置产生影响。大量字体的问题在于这些值完全搞砸了TTF / OTF文件,因此您可以获得使用.sizeToFit(),重叠线条,不稳定的垂直位置等剪切下降器。

有三种方法可以解决这个问题:

1)使用NSAttributedString和更多基线与NSBaselineOffsetAttributeName。 TTTAttributedLabel是一个很好的捷径。

2)使用CoreText和CoreGraphics在自定义UIView子类中的CGLayer中呈现自己的文本布局,并有效地创建自己的UILabel等效项。这很难!

3)修复字体文件,使基线,上升和下降都正确。