初始化 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
相關文章
相關標籤/搜索