好的,我们来 详细拆解 一下这个方法:
[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: 时都会被 lineRect 和 usedRect 搞混,我来用最简单的例子和图来解释一下它俩的区别👇
📦 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 影响 | 否 | 是(右对齐时会右移) |
| 通常用于 | 布局排版、背景色 | 文字点击判断、高亮、动画 |