NSLayoutManager enumerateLineFragmentsForGlyphRange

好的,我们来 详细拆解 一下这个方法:

[layoutManager enumerateLineFragmentsForGlyphRange:glyphRange
       usingBlock:^(CGRect lineRect,
                    CGRect usedRect,
                    NSTextContainer *textContainer,
                    NSRange lineGlyphRange,
                    BOOL *stop) {
    // 这里是你逐行处理的代码
}];

这个方法是 NSLayoutManager 的强大工具之一,用于遍历特定 glyph 范围内的每一行文本,并返回每一行的详细布局信息。下面我们按参数和原理逐个解释。


🔍 方法的作用

这个方法会按行(Line)遍历给定 glyph 范围内的内容,并针对每一行调用一次 block,block 里会告诉你这行的:

  • 布局区域 (lineRect)
  • 实际占用区域 (usedRect)
  • 所属的容器 (textContainer)
  • 对应的 glyph 范围 (lineGlyphRange)

🧩 每个参数详解

glyphRange

  • 你传入的 glyph 范围(比如通过 [layoutManager glyphRangeForCharacterRange:...] 获取)。
  • 它告诉系统:“我要处理这些文字对应的 glyph,帮我一行一行地处理它们”。

lineRect: CGRect

  • 这一行的 整个布局矩形区域(不管这行有没有文字),也就是它“可以”放东西的地方。
  • usedRect 不同,lineRect 可能包含边距、空白、对齐空间。

🧠 想象这是整行你能画背景色或下划线的区域。


usedRect: CGRect

  • 这一行中,真正被文字使用到的部分的矩形
  • 如果是左对齐文字,那 usedRect.origin.x = 0;如果是右对齐,就会偏移。

🧠 你可以用这个来高亮、响应点击等 —— 因为它反映了文字真实出现的位置


textContainer: NSTextContainer *

  • 这一行属于哪个 NSTextContainer(通常就一个,但复杂场景下可能多个)。
  • 比如用于页面排版、列布局时,这很有用。

lineGlyphRange: NSRange

  • 这一行中包含了哪些 glyph。
  • 你可以基于这个范围进行进一步分析,比如把 glyph 转换为字符范围。

stop: BOOL *

  • 如果你不想继续遍历,设置 *stop = YES; 就可以中止遍历。

✅ 举个实际的用法场景

[layoutManager enumerateLineFragmentsForGlyphRange:glyphRange usingBlock:^(CGRect lineRect, CGRect usedRect, NSTextContainer *container, NSRange lineGlyphRange, BOOL *stop) {

    // 获取当前行的字符范围
    NSRange charRange = [layoutManager characterRangeForGlyphRange:lineGlyphRange actualGlyphRange:NULL];

    // 打印一下当前行实际文字的位置
    NSLog(@"Line text rect = %@", NSStringFromCGRect(usedRect));
    
    // 判断点击是否落在这行中
    if (CGRectContainsPoint(usedRect, tapPoint)) {
        NSLog(@"用户点中了这一行:%@", NSStringFromRange(charRange));
    }
}];

💡 延伸提示:为什么是 glyph,不是 character?

TextKit 中:

  • character:用户眼中的字符
  • glyph:屏幕上显示的图形(比如合并的 emoji、组合音符)

所以 layout 和绘制都基于 glyph


✅ 总结表格

参数名 类型 含义
lineRect CGRect 当前行完整布局区域(含空白)
usedRect CGRect 当前行文字实际占用区域
textContainer NSTextContainer * 所属容器
lineGlyphRange NSRange 当前行包含的 glyph 范围
stop BOOL * 设置为 YES 可终止遍历

这个问题问得好!很多人刚开始接触 enumerateLineFragmentsForGlyphRange: 时都会被 lineRectusedRect 搞混,我来用最简单的例子和图来解释一下它俩的区别👇


📦 lineRect vs usedRect 的区别

属性名 意义
lineRect 整行的排版区域(包含整行的可用空间
usedRect 实际文字在这一行中占用的区域(不包含空白边距

✅ 举个例子

假设我们有一个 UILabel,宽度是 300pt,文字是左对齐的三行文本:

行 1:你好世界
行 2:这是第二行
行 3:喵

TextKit 会这样处理它👇

📐 每一行的 lineRect 是整个 label 的宽度:

lineRect (300pt)
+------------------------------------+
| 你好世界                           |
+------------------------------------+
| 这是第二行                        |
+------------------------------------+
| 喵                                |
+------------------------------------+

🧩 每一行的 usedRect 是文字的实际宽度,比如:

  • 行 1 的 usedRect:宽度 60pt;
  • 行 2 的 usedRect:宽度 80pt;
  • 行 3 的 usedRect:宽度 20pt;

比如这张图直观理解 👇:

lineRect: [--------------------------]
usedRect: [你好世界]

🤔 为什么需要两个 rect?

这是为了支持 文字对齐布局精确性

  • lineRect:你用来做背景色、下划线、行高控制;
  • usedRect:你用来计算点击位置、高亮、动画等实际“内容”相关操作;

✅ 代码对比演示(简化版)

[layoutManager enumerateLineFragmentsForGlyphRange:glyphRange
    usingBlock:^(CGRect lineRect, CGRect usedRect, NSTextContainer *container, NSRange range, BOOL *stop) {

    NSLog(@"lineRect: %@", NSStringFromCGRect(lineRect));
    NSLog(@"usedRect: %@", NSStringFromCGRect(usedRect));
}];

你会看到:

  • lineRect.origin.x == 0(始终从左边开始);
  • usedRect.origin.x 会随 textAlignment 变化。

🚀 小结

区别点 lineRect usedRect
用途 整行的布局区域 实际文字内容区域
受 alignment 影响 是(右对齐时会右移)
通常用于 布局排版、背景色 文字点击判断、高亮、动画

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容