How to Efficiently Find Cgrects for Visible Words in Uitextview

How to efficiently find CGRects for visible words in UITextView?

Since UITextView is a subclass of UIScrollView, its bounds property reflects the visible part of its coordinate system. So something like this should work:

- (NSRange)visibleRangeOfTextView:(UITextView *)textView {
CGRect bounds = textView.bounds;
UITextPosition *start = [textView characterRangeAtPoint:bounds.origin].start;
UITextPosition *end = [textView characterRangeAtPoint:CGPointMake(CGRectGetMaxX(bounds), CGRectGetMaxY(bounds))].end;
return NSMakeRange([textView offsetFromPosition:textView.beginningOfDocument toPosition:start],
[textView offsetFromPosition:start toPosition:end]);
}

This assumes a top-to-bottom, left-to-right text layout. If you want to make it work for other layout directions, you will have to work harder. :)

ios - how to find what is the visible range of text in UITextView?

The way i would do it is to compute all the sizes of each paragraph. With sizeWithFont:constrainedToSize:lineBreakMode:

you will then be able to work out which paragraph is visible, from the [textView contentOffset].

to scroll, dont use scrollRangeToVisible, just use setContentOffset: The CGPoint y parameter for this should either be the sum of all the height sizes to the next paragraph, or just add the textView.frame.size.height, if that is closer than the beginning of the next paragraph.

This make sense?

in answer to comment requst code bellow (untested):

  CGFloat paragraphOffset[MAX_PARAGRAPHS];

CGSize constraint = CGSizeMake(widthOfTextView, 999999 /*arbitrarily large number*/);
NSInteger paragraphNo = 0;
CGFloat offset = 0;

for (NSString* paragraph in paragraphs) {
paragraphOffset[paragraphNo++] = offset;
CGSize paragraphSize = [paragraph sizeWithFont:textView.font constrainedToSize:constraint lineBreakMode:UILineBreakModeWordWrap];
offset += paragraphSize.height;
}

// find visible paragraph
NSInteger visibleParagraph = 0;
while (paragraphOffset[visibleParagraph++] < textView.contentOffset.y);

// scroll to paragraph 6
[textView setContentOffset:CGPointMake(0, paragraphOffset[6]) animated:YES];

CGRect for selected UITextRange adjustment for multiline text?

I think all your problems are because of incorrect order of instructions.

You have to

  1. Set text aligment
  2. Find required substrings and add specific attributes to them
  3. And only then highlight strings with subviews.

Also you will not need to use "a workaround for getting the correct width of the string since I'm always using the monospaced Menlo font" in such a case.

I have simplified your code a little to make it more understandable.

Result:
Sample Image

- (void)viewDidLoad
{
[super viewDidLoad];

NSDictionary *basicAttributes = @{ NSFontAttributeName : [UIFont boldSystemFontOfSize:18],
NSForegroundColorAttributeName : [UIColor blackColor] };
NSDictionary *attributes = @{ NSFontAttributeName : [UIFont systemFontOfSize:15],
NSForegroundColorAttributeName : [UIColor darkGrayColor]};

_textView.attributedText = [[NSAttributedString alloc] initWithString:
@"*This* is **awesome** @mention `code` more \n `code and code` #hashtag [markdown](http://google.com) __and__ @mention2 {#FFFFFF|colored text} This**will also** work but ** will not ** **work** Also, some `more code for you to see`" attributes:attributes];
_textView.textAlignment = NSTextAlignmentCenter;

[self formatMarkdownCodeBlockWithAttributes:basicAttributes];
}

- (void)formatMarkdownCodeBlockWithAttributes:(NSDictionary *)attributesDict
{
NSMutableString *theString = [_textView.attributedText.string mutableCopy];
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"`.+?`" options:NO error:nil];
NSArray *matchesArray = [regex matchesInString:theString options:NO range:NSMakeRange(0, theString.length)];

NSMutableAttributedString *theAttributedString = [_textView.attributedText mutableCopy];
for (NSTextCheckingResult *match in matchesArray)
{
NSRange range = [match range];
if (range.location != NSNotFound) {
[theAttributedString addAttributes:attributesDict range:range];
}
}

_textView.attributedText = theAttributedString;

for (NSTextCheckingResult *match in matchesArray)
{
NSRange range = [match range];
if (range.location != NSNotFound) {

CGRect codeRect = [self frameOfTextRange:range];
UIView *highlightView = [[UIView alloc] initWithFrame:codeRect];
highlightView.layer.cornerRadius = 4;
highlightView.layer.borderWidth = 1;
highlightView.backgroundColor = [UIColor yellowColor];
highlightView.layer.borderColor = [[UIColor redColor] CGColor];
[_textView insertSubview:highlightView atIndex:0];
}
}
}

- (CGRect)frameOfTextRange:(NSRange)range
{
self.textView.selectedRange = range;
UITextRange *textRange = [self.textView selectedTextRange];
CGRect rect = [self.textView firstRectForRange:textRange];
return rect;
}

How To Separate Strings For UIScrollView/UITextView based on the size of the frame

As of iOS 7, there's a much more elegant solution to this using TextKit that I've included in sample code below. The idea is to let TextKit's layout manager handle separating the glyphs and lay everything out that way properly. This prevents cutting off words mid way and a ton of flexibility:

class BookView: UIScrollView {

var bookMarkup: NSAttributedString!
private let layoutManager = NSLayoutManager()

override func layoutSubviews() {
super.layoutSubviews()

if layoutManager.textContainers.count == 0 {
buildFrames()
}
}

func buildFrames() {
let textStorage = NSTextStorage(attributedString: bookMarkup)

textStorage.addLayoutManager(layoutManager)

var range = NSMakeRange(0, 0)
var containerIndex = 0

while NSMaxRange(range) < layoutManager.numberOfGlyphs {
let textViewRect = frameForViewAtIndex(containerIndex)
let containerSize = CGSizeMake(CGRectGetWidth(textViewRect), CGRectGetHeight(textViewRect) - 16) //UITextView adds an 8 margin above and below the container so we take that into consideration here with the 16. heightTracksTextView causes a performance hit when adding multiple containers... so we don't do that instead
let textContainer = NSTextContainer(size: containerSize)
layoutManager.addTextContainer(textContainer)

let textView = UITextView(frame: textViewRect, textContainer: textContainer)

addSubview(textView)

containerIndex++

range = layoutManager.glyphRangeForTextContainer(textContainer)
}

contentSize = CGSize(width: CGRectGetWidth(bounds) / 2 * CGFloat(containerIndex), height: CGRectGetHeight(bounds))

pagingEnabled = true
}

private func frameForViewAtIndex(index: Int) -> CGRect {

var textViewRect = CGRect(origin: CGPointZero, size: CGSize(width: CGRectGetWidth(bounds)/2, height: CGRectGetHeight(bounds)))
textViewRect = CGRectInset(textViewRect, 10, 20)
textViewRect = CGRectOffset(textViewRect, CGRectGetWidth(bounds) / 2 * CGFloat(index), 0)
return textViewRect
}
}

How do I locate the CGRect for a substring of text in a UILabel?

Following Joshua's answer in code, I came up with the following which seems to work well:

- (CGRect)boundingRectForCharacterRange:(NSRange)range
{
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:[self attributedText]];
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
[textStorage addLayoutManager:layoutManager];
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:[self bounds].size];
textContainer.lineFragmentPadding = 0;
[layoutManager addTextContainer:textContainer];

NSRange glyphRange;

// Convert the range for glyphs.
[layoutManager characterRangeForGlyphRange:range actualGlyphRange:&glyphRange];

return [layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textContainer];
}


Related Topics



Leave a reply



Submit