iOS18中CoreText的CTRunDraw无法绘制下划线?是特性还是Bug?
iOS18中CTRunDraw不显示下划线的原因与解决方案
结论
这是iOS18对CoreText行为的规范修正(属于特性变更范畴),而非系统Bug。
原因分析
CoreText的API设计有明确的职责划分:
CTFrameDraw和CTLineDraw负责完整的文本块/文本行渲染,包含字形、文本装饰(下划线、删除线)、颜色等所有属性的处理。CTRunDraw是最低层级的API,仅负责绘制单个字形组(CTRun)的字形本身,原本就不应该处理下划线这类跨run的装饰属性。
iOS17及之前版本中,CTRunDraw意外渲染了下划线,属于不符合设计预期的行为;iOS18修正了这个逻辑,让CTRunDraw回归其本职功能,不再处理文本装饰属性。
解决方案
方案1:改用CTLineDraw或CTFrameDraw(推荐)
直接使用上层API,无需手动处理下划线,代码更简洁且符合CoreText设计规范:
// 替换CTRunDraw循环为CTLineDraw for (int i = 0; i < CFArrayGetCount(lines); i++) { CTLineRef line = CFArrayGetValueAtIndex(lines, i); CGContextSetTextPosition(context, lineOrigins[i].x, lineOrigins[i].y); CTLineDraw(line, context); // 此API会自动绘制下划线 }
方案2:手动绘制下划线(若必须使用CTRunDraw)
如果需要精细控制每个CTRun的渲染,可手动提取下划线属性并绘制:
// 在CTRunDraw之后添加手动绘制下划线的逻辑 CFArrayRef runs = CTLineGetGlyphRuns(line); for (int j = 0; j < CFArrayGetCount(runs); j++) { CTRunRef run = CFArrayGetValueAtIndex(runs, j); CTRunDraw(run, context, CFRangeMake(0, 0)); // 手动绘制下划线 CFDictionaryRef runAttrs = CTRunGetAttributes(run); NSNumber *underlineStyle = (__bridge NSNumber *)CFDictionaryGetValue(runAttrs, (__bridge const void *)NSUnderlineStyleAttributeName); if (!underlineStyle || underlineStyle.integerValue == NSUnderlineStyleNone) { continue; } UIColor *underlineColor = (__bridge UIColor *)CFDictionaryGetValue(runAttrs, (__bridge const void *)NSUnderlineColorAttributeName) ?: [UIColor blackColor]; UIFont *font = (__bridge UIFont *)CFDictionaryGetValue(runAttrs, (__bridge const void *)NSFontAttributeName) ?: [UIFont systemFontOfSize:20]; // 获取run的排版边界 CGFloat ascent, descent, leading; CGFloat runWidth = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, &leading); CGPoint runPos = CGContextGetTextPosition(context); // 计算下划线位置(基于字体的下划线偏移和粗细) CGFloat underlineY = runPos.y - descent + font.underlineOffset; CGFloat underlineThickness = font.underlineThickness; // 绘制下划线 CGContextSaveGState(context); CGContextSetStrokeColorWithColor(context, underlineColor.CGColor); CGContextSetLineWidth(context, underlineThickness); // 处理粗体下划线样式 if (underlineStyle.integerValue & NSUnderlineStyleThick) { CGContextSetLineWidth(context, underlineThickness * 2); } CGContextMoveToPoint(context, runPos.x, underlineY); CGContextAddLineToPoint(context, runPos.x + runWidth, underlineY); CGContextStrokePath(context); CGContextRestoreGState(context); // 更新文本位置,确保后续run的绘制位置正确 CGContextSetTextPosition(context, runPos.x + runWidth, runPos.y); }
内容的提问来源于stack exchange,提问作者ximmyxiao
相关产品推荐
相关产品推荐

