Mike S
Mike S

Reputation: 4122

get UITextView contents as array of lines

Is there any way I retrieve the contents of a UITextView as an array of lines as they wrap?

I could do it the dumb way and measure word by word with sizeWithFont and see where it may wrap but I'm hoping there's a smarter way out there?

Cheers

Upvotes: 3

Views: 794

Answers (4)

Antzi
Antzi

Reputation: 13444

Using an attributed string, I made this function:

extension NSAttributedString {
    func lines(in size: CGSize) -> [NSAttributedString] {
        let textStorage = NSTextStorage(attributedString: self)
        let layoutManager = NSLayoutManager()
        textStorage.addLayoutManager(layoutManager)

        let textContainer = NSTextContainer(size: size)
        textContainer.lineFragmentPadding = 0.0
        layoutManager.addTextContainer(textContainer)

        var lines = [NSAttributedString]()
        layoutManager.enumerateLineFragments(forGlyphRange: layoutManager.glyphRange(forBoundingRect: CGRect(origin: .zero, size: size), in: textContainer)) { (_, _, _, range, _) in
            let charactersRange = layoutManager.characterRange(forGlyphRange: range, actualGlyphRange: nil)
            lines.append(self.attributedSubstring(from: charactersRange))
        }
        return lines
    }
}

So for exemple you can run it like this:

NSAttributedString(string: "J'aime le fromage. Donnez moi du fromage. Munster, Reblochon, ...\n Ou bien du saucisson", attributes: [
    .font: UIFont.systemFont(ofSize: 12, weight: .semibold)
]).lines(in: CGSize(width: 130, height: .max))

Will output

["J'aime le fromage.", "Donnez moi du", "fromage. Munster,", "Reblochon, ...", "Ou bien du saucisson"]

Upvotes: 0

santhu
santhu

Reputation: 4792

You can use the textviews layout manager to get those.But tis available from ios 7. you can use layout manager's method - ( enumerateLineFragmentsForGlyphRange:usingBlock: )

The below code prints result as shown:

_textView.text = @"abcdsakabsbdkjflk sadjlkasjdlk asjkdasdklj asdpaandjs bajkhdb hasdskjbnas kdbnkja sbnkasj dbkjasd kjk aj";
NSLog(@"%d", _textView.text.length); // length here is 104.

[_textView.layoutManager enumerateLineFragmentsForGlyphRange: NSMakeRange(0, 104) usingBlock: ^(CGRect rect, CGRect usedRect, NSTextContainer *textContainer, NSRange glyphRange, BOOL *stop) {  
    NSLog(@"rect %@ - usedRect %@ - glyphRange %d %d -", NSStringFromCGRect(rect), NSStringFromCGRect(usedRect), glyphRange.location, glyphRange.length);
}];

print result:

2013-12-17 12:48:40.250 testEmpty[675:a0b] rect {{0, 0}, {200, 13.8}} - usedRect {{0, 0}, {176.08398, 13.8}} - glyphRange 0 31 -
2013-12-17 12:48:40.251 testEmpty[675:a0b] rect {{0, 13.8}, {200, 13.8}} - usedRect {{0, 13.8}, {182.11328, 13.8}} - glyphRange 31 31 -
2013-12-17 12:48:40.251 testEmpty[675:a0b] rect {{0, 27.6}, {200, 13.8}} - usedRect {{0, 27.6}, {168.75977, 13.8}} - glyphRange 62 28 -
2013-12-17 12:48:40.252 testEmpty[675:a0b] rect {{0, 41.400002}, {200, 13.8}} - usedRect {{0, 41.400002}, {82.035156, 13.8}} - glyphRange 90 14 -

So in each run of block, you will get the glyphRange.length as length of string used in that line.

Upvotes: 3

Mike S
Mike S

Reputation: 4122

I did it this way and it seems to be fast enough.

-(void) _printCharactersAndPositionsForLines:(UITextView*) textView{
    NSString * text = textView.text;
    if (text != nil){
        float currentLineY = -1;
        int currentLineIndex = -1;
        for (int charIndex = 0; charIndex < [text length]; charIndex++){
            UITextPosition *charTextPositionStart = [textView positionFromPosition:textView.beginningOfDocument offset:charIndex];
            UITextPosition *charTextPositionEnd = [textView positionFromPosition:charTextPositionStart offset:+1];
            UITextRange *range = [textView textRangeFromPosition:charTextPositionStart toPosition:charTextPositionEnd];
            CGRect rectOfChar = [textView firstRectForRange:range];
            if (rectOfChar.origin.y > currentLineY){
                currentLineY = rectOfChar.origin.y;
                currentLineIndex++;
                // new line
                NSLog(@"line [%i] starts with '%@' at character index %i",currentLineIndex,[text substringWithRange:NSMakeRange(charIndex, 1)],charIndex);
            }
        }
    }
}

Upvotes: 0

Charan Giri
Charan Giri

Reputation: 1097

i guess you can use attributeString. Using this you can set color for a range of characters or words. get the length of text in textview and do attributeString work

Upvotes: 1

Related Questions