how to find what text is visible in a scrollable, non-ediable UITextView?
for example i may need to show next paragraph, then i want to find the current visible text range and use it to calculate the appropriate range and use scrollRangeToVisible:
to scroll the text view
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];
I find another solution here.
It's a better way to solve this problem in my eyes.
https://stackoverflow.com/a/9283311/889892
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. :)
If you want a Swift solution I use this:
Swift 2
public extension UITextView {
public var visibleRange: NSRange? {
if let start = closestPositionToPoint(contentOffset) {
if let end = characterRangeAtPoint(CGPointMake(contentOffset.x + CGRectGetMaxX(bounds), contentOffset.y + CGRectGetMaxY(bounds)))?.end {
return NSMakeRange(offsetFromPosition(beginningOfDocument, toPosition: start), offsetFromPosition(start, toPosition: end))
}
}
return nil
}
}
Swift 3
public extension UITextView {
public var visibleRange: NSRange? {
guard let start = closestPosition(to: contentOffset),
end = characterRange(at: CGPoint(x: contentOffset.x + bounds.maxX,
y: contentOffset.y + bounds.maxY))?.end
else { return nil }
return NSMakeRange(offset(from: beginningOfDocument, to: start), offset(from: start, to: end))
}
}
One option you have is to use a UIWebView instead of a UITextView. You can then use anchors and javascript to scroll to the appropriate places in the text. You can probably insert the anchors programmatically at the start of each paragraph to make this easier.
Swift 3.0/3.1 solution based on "Noodle of Death" answer.
public extension UITextView {
public var visibleRange: NSRange? {
if let start = closestPosition(to: contentOffset) {
if let end = characterRange(at: CGPoint(x: contentOffset.x + bounds.maxX, y: contentOffset.y + bounds.maxY))?.end {
return NSMakeRange(offset(from: beginningOfDocument, to: start), offset(from: start, to: end))
}
}
return nil
}
}