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
- Set text aligment
- Find required substrings and add specific attributes to them
- 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:
- (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
How Do Set a Width and Height of an Image in Swift
Installed App from Testflight Crashes Due to Alleged Uisearchdisplaycontroller
Workaround for Rounded Corners of Grouped Uitableview, iOS7
How to Pass Data to Another Controller on Dismiss Viewcontroller
Watchkit: Unable to Find Interface Controller Class
Using Apple's Reachability Class in Swift
Uicontroleventeditingchanged Doesn't Get Fired When Using Settext of Uitextfield
iOS 8+ Framework with Nested Embedded Framework
Info.Plist File for React Native iOS App Using Expo Sdk
Where to Highlight Uicollectionviewcell: Delegate or Cell
Xcode 11.4. Navigation's Title Color Gone Black from Storyboard
Drawing Gradient Over Image in iOS
Ios/Swift - Hide/Show Uitabbarcontroller When Scrolling Down/Up
Sfspeechrecognizer - Detect End of Utterance
Nsuserdefaultsdidchangenotification and Today Extensions
How to Play a Video from Either a Local or a Server Url in iOS