在NSLayoutManager中使用boundingRectForGlyphRange计算字边界时如何消除前导空格

时间:2014-02-09 22:56:14

标签: ios textkit

我在iOS上将多行字符串分解为字边界。我的解决方案围绕NSLayoutManager的boundingRectForGlyphRange方法。它几乎可以工作,除了每个单词的矩形是右边几个像素。换句话说,NSLayoutManager似乎在每一行上添加了一个前导空格/缩进,我找不到任何方法来覆盖这种行为。

我尝试使用NSLayoutManager.usesFontLeading以及NSParagraphStyle.headIndent但没有任何结果:

 NSLayoutManager* layout = [NSLayoutManager new];
layout.usesFontLeading = NO;
NSMutableParagraphStyle* paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.headIndent = paragraphStyle.firstLineHeadIndent = 0;
NSTextStorage* textStorage = [[NSTextStorage alloc] initWithString:self attributes:@{NSFontAttributeName:font, NSParagraphStyleAttributeName:paragraphStyle}];
layout.textStorage = textStorage;
NSTextContainer* textContainer = [[NSTextContainer alloc] initWithSize:size];
[layout addTextContainer:textContainer];

// compute bounding rect for each word range
for (NSValue* wordRangeValue in wordRanges)
{
    NSRange wordRange = [wordRangeValue rangeValue];
    NSRange wordGlyphRange = [layout glyphRangeForCharacterRange:wordRange actualCharacterRange:NULL];
    CGRect wordBounds = [layout boundingRectForGlyphRange:wordGlyphRange inTextContainer:textContainer];
}

enter image description here

屏幕截图:灰色矩形表示标签边界,红色矩形表示每个标签的文本rect,以及上面[boundingRectForGlyphRange:]方法计算的单词边界。请注意,计算出的单词边界偏离了几个像素。

我也对其他计算单词边界的方法持开放态度,但boundingRectForGlyphRange对我来说似乎很方便。

2 个答案:

答案 0 :(得分:2)

要忽略左边距,请使用:

textContainer.lineFragmentPadding = 0;

答案 1 :(得分:0)

我无法强制NSLayoutManager省略左边距。如果有人知道如何让NSLayoutManager忽略左边距,请告诉我。

我的解决方法是使用Core Text代替。这更加困难和参与。我的解决方案不适合粘贴到单个代码摘录中,但如果您想要使用相同的路径,这应该会给您一个很好的参考:

- (NSArray*) coreTextLayoutsForCharacterRanges:(NSArray*)ranges withFont:(UIFont *)font constrainedToSize:(CGSize)size{

// initialization: make frame setter
NSMutableParagraphStyle* paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping; // watch out - need to specify line wrapping to use multi line layout!
paragraphStyle.minimumLineHeight = font.lineHeight; // watch out - custom fonts do not compute properly without this!
NSAttributedString* attributedString = [[NSAttributedString alloc] initWithString:self attributes:@{NSFontAttributeName:font, NSParagraphStyleAttributeName:paragraphStyle}];
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributedString);
CFRange wholeString = CFRangeMake(0, self.length);
CGRect bounds = CGRectMake(0, 0, size.width, size.height);
CGMutablePathRef boundsPath = CGPathCreateMutable();
CGPathAddRect(boundsPath, NULL, bounds);
CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, wholeString, boundsPath, NULL);
CFRelease(boundsPath);

// extract lines
CFArrayRef lines = CTFrameGetLines(frame);
int lineCount = CFArrayGetCount(lines);
CGPoint lineOrigins[lineCount];
CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), lineOrigins);

NSMutableArray* lineLayouts = [NSMutableArray arrayWithCapacity:lineCount];
CGFloat h = size.height;
for (int i = 0; i < lineCount; ++i){
    CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(lines, i);
    CGPoint lineOrigin = lineOrigins[i];  // in Core Graphics coordinates! let's convert.
    lineOrigin.y = h - lineOrigin.y;
    TextLayout* lineLayout = [[CTLineLayout alloc] initWithString:self line:line lineOrigin:lineOrigin];
    [lineLayouts addObject:lineLayout];
}

// got line layouts. now we iterate through the word ranges to find the appropriate line for each word and compute its layout using the corresponding CTLine.

另一个重要的部分是如何使用CTLine获取一行中单词的边界矩形。我把它考虑到CTLineLayout模块中,但要点就是这个('origin'变量指的是上面代码示例中计算的行原点):

    CGFloat ascent = 0.0f, descent = 0.0f, leading = 0.0f;
CGFloat width = CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
CGFloat top = origin.y - ascent;
CGFloat bottom = origin.y + descent;
CGRect result = CGRectMake(origin.x, top, width, bottom - top); // frame of entire line (for now)
CGFloat left = CTLineGetOffsetForStringIndex(line, stringRange.location, NULL);
CGFloat right = CTLineGetOffsetForStringIndex(line, NSMaxRange(stringRange), NULL);
result.origin.x = left;
result.size.width = right - left; // frame of target word in UIKit coordinates

上面是一个粗略的摘录 - 我考虑CTLine在初始化器中计算一次线的边界,然后在获取一个单词的框架时仅计算左+右端点。

呼!