初始化 TextKit 的正确方式

首发于公众号ui

iOS7 以后,苹果推出了用于解决文本排版问题的 TextKit 三件套:spa

  • NSTextStorage
  • NSLayoutManager
  • NSTextContainer

使用方法比较简单:code

[textStorage addLayoutManager:layoutManager];
[layoutManager addTextContainer:textContainer];
复制代码

而后在 view 的 bounds 发生变化的时候调整一下 textContainer 的 size 就能够了。component

可是,这里有一处坑,会致使诡异的问题,用下面的方法计算文本须要的 Rect 时,始终会获得 CGRectZero:orm

[layoutManager usedRectForTextContainer:textContainer];
复制代码

这彷佛是 TextKit 的一个 bug(或者是 feature?)。 写代码的时候,顺应着思惟,通常都会这么初始化 TextKit:cdn

- (void)initTextKit {
    textStorage = [[NSTextStorage alloc] init];
    layoutManager = [[NSLayoutManager alloc] init];
    [textStorage addLayoutManager:layoutManager];
    textContainer = [[NSTextContainer alloc] initWithSize:constrainedSize];
    [layoutManager addTextContainer:textContainer];
}
复制代码

嗯,根据它们的添加顺序一路写下来,看着很顺畅,这有啥问题? 这就是看不见的坑,textStorage 初始化的时机太早了,顺序应该放到最后,调整后的代码以下:blog

- (void)initTextKit {
    layoutManager = [[NSLayoutManager alloc] init];
    textContainer = [[NSTextContainer alloc] initWithSize:constrainedSize];
    [layoutManager addTextContainer:textContainer];
    textStorage = [[NSTextStorage alloc] init];
    [textStorage addLayoutManager:layoutManager];
}
复制代码

addLayoutManager 的调用顺序放在最后,完美解决 usedRectForTextContainer 没法计算的问题。字符串

BTWget

Facebook 开源库 ComponentKit 一样有这个问题,在 CKTextKitContext.mm 里:it

- (instancetype)initWithAttributedString:(NSAttributedString *)attributedString
                           lineBreakMode:(NSLineBreakMode)lineBreakMode
                    maximumNumberOfLines:(NSUInteger)maximumNumberOfLines
                         constrainedSize:(CGSize)constrainedSize
                    layoutManagerFactory:(NSLayoutManager*(*)(void))layoutManagerFactory
{
if (self = [super init]) {
// Concurrently initialising TextKit components crashes (rdar://18448377) so we use a global lock.
static std::mutex *__static_mutex = new std::mutex;
    std::lock_guard<std::mutex> l(*__static_mutex);
// Create the TextKit component stack with our default configuration.
    _textStorage = (attributedString ? [[NSTextStorage alloc] initWithAttributedString:attributedString] : [[NSTextStorage alloc] init]);
    _layoutManager = layoutManagerFactory ? layoutManagerFactory() : [[NSLayoutManager alloc] init];
    _layoutManager.usesFontLeading = NO;
    [_textStorage addLayoutManager:_layoutManager];
    _textContainer = [[NSTextContainer alloc] initWithSize:constrainedSize];
// We want the text laid out up to the very edges of the container.
    _textContainer.lineFragmentPadding = 0;
    _textContainer.lineBreakMode = lineBreakMode;
    _textContainer.maximumNumberOfLines = maximumNumberOfLines;
    [_layoutManager addTextContainer:_textContainer];
  }
return self;
}
复制代码

不信你能够试一下:

[context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
  CGRect usedRect = [layoutManager usedRectForTextContainer:textContainer];
}];
复制代码

无论字符串的内容是什么, usedRect 的值都是 CGRectZero。

img
相关文章
相关标签/搜索