从UITextView和NSLayoutManager获取行信息

12
为了支持UIAccessibilityReadingContent协议,我需要我的UITextView回答关于它的行的问题。这些是我需要实现的协议方法:
- accessibilityLineNumberForPoint:给定一个坐标,返回行号。 - accessibilityContentForLineNumber:返回给定行的文本。 - accessibilityFrameForLineNumber:给定行号,返回其框架。 - accessibilityPageContent:整个文本内容。我有它。:)
我想NSLayoutManager可以帮助我,但我对它不是很有经验。我已经弄清楚了一些(我想),但仍然需要一些帮助。
苹果有一些示例代码(here)可以让我得到文本视图中的行数:
NSLayoutManager *layoutManager = [textView layoutManager];
unsigned numberOfLines, index, numberOfGlyphs =
        [layoutManager numberOfGlyphs];
NSRange lineRange;
for (numberOfLines = 0, index = 0; index < numberOfGlyphs; numberOfLines++){
    (void) [layoutManager lineFragmentRectForGlyphAtIndex:index
            effectiveRange:&lineRange];
    index = NSMaxRange(lineRange);
}

我认为通过上面的lineRange,我可以使用NSLayoutManager上的这种方法来计算行矩形:

- (NSRect)boundingRectForGlyphRange:(NSRange)glyphRange inTextContainer:(NSTextContainer *)container

根据给定的lineRanges,我应该能够通过查找包含字形索引的lineRange来计算点的行号:

- (NSUInteger)glyphIndexForPoint:(CGPoint)point inTextContainer:(NSTextContainer *)container fractionOfDistanceThroughGlyph:(CGFloat *)partialFraction

那么现在问题是,如何在给定行号的情况下获取一行的内容(作为 NSString)?


你确定需要那个协议吗?通常情况下,你只需设置NSTextStorage、NSLayoutManager和NSTextContainer即可一切正常。我看过一个很棒的关于这方面的主题文章:http://www.raywenderlich.com/50151/text-kit-tutorial。 - kaspartus
1
很不幸,UITextView在启用VoiceOver时似乎不能直接提供逐行阅读的功能。至少在我的实现中(UITextViews嵌入在UITableViewCells中)是这样的。 - Mikkel Selsøe
1
NSLayoutManager 提供了一些用于处理字形范围的方法,同时也提供了一些用于字符范围的方法。最后,还有两个方法可以让你在这两种范围之间进行转换:-[NSLayoutManager glyphRangeForCharacterRange:actualCharacterRange:]-[NSLayoutManager characterRangeForGlyphRange:actualGlyphRange:]。要检索字符串,只需将感兴趣的 glyphRange 转换为 characterRange,然后就可以使用 UITextViewtextStorage 中的 attributedSubstringFromRange:stringsubstringWithRange: 检索子字符串。 - Dalzhim
1个回答

7

考虑到简单解决方案,这里提供了一小段代码,可以复制你所需的功能。

- (void)analyse:(UITextView *)textView
{
    NSLayoutManager *layoutManager = [textView layoutManager];
    NSString *string = textView.text;
    unsigned numberOfLines, index, stringLength = [string length];

    NSMutableArray *ranges = [NSMutableArray new];
    NSMutableArray *frames = [NSMutableArray new];
    for (index = 0, numberOfLines = 0; index < stringLength; numberOfLines++)
    {
        NSRange tmprange;
        NSRange range = [string lineRangeForRange:NSMakeRange(index, 0)];
        CGRect rect = [layoutManager lineFragmentRectForGlyphAtIndex:index
                                           effectiveRange:&tmprange];

        [ranges addObject:[NSValue valueWithRange:range]];

        [frames addObject:[NSValue valueWithCGRect:rect]];
        index = NSMaxRange(tmpRange);
    }
    self.ranges = ranges;
    self.frames = frames;
    self.numberOfLines = numberOfLines;
}

请查看以下属性:

self.ranges = ranges;
self.frames = frames;
self.numberOfLines = numberOfLines;

你可以在你的类中使用以下内容来创建这些属性:
@property (nonatomic) NSInteger numberOfLines;
@property (strong, nonatomic) NSArray *ranges;
@property (strong, nonatomic) NSArray *frames;

我建议你将分析调用添加到以下委托中:

- (void)textViewDidChange:(UITextView *)textView

例如,您可以在分析后使用以下代码获取第2行的帧:

self.frames[1]

或者获取第二行的文本:

[textView.text substringWithRange:[self.ranges[1] rangeValue]]

示例如下:

if (self.numberOfLines > 1)
{
    NSRange range = [self.ranges[1] rangeValue];
    NSLog(@"2nd line = %@", [textView.text substringWithRange:range]);
    NSLog(@"2nd line frame = %@", self.frames[1]);
}

我认为,只要在 self.frames 中拥有所有帧,就可以轻松地使用坐标猜测行号。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接