iOS 7 breaks UITextView + NSAttributedString pretty badly that it’s almost unusable.
Submitted this Radar today.
The UITextView calculates the contentSize as the user scrolls. As a result the scroll indicators jump randomly.
Because the text view doesn’t know about the complete size of content, methods like
don’t return the right values on iOS 7.
Steps to Reproduce:
1. Download the sample app attached here.
2. Run it on your iPhone running iOS 7 using Xcode 5+
3. The app displays a static HTML file converted to NSAttributedString
4. The app also adds a Key Value Observer to watch changes on contentSize
5. Scroll the text view.
6. Search for a text (Apple Computer, Inc) in the attributed text and get its range. Convert the NSRange to UITextRange.
NSRange range = [attributedText.string rangeOfString:@"Apple Computer, Inc"]; UITextPosition *start = [self.textView positionFromPosition:self.textView.beginningOfDocument offset:range.location]; UITextPosition *end = [self.textView positionFromPosition:start offset:range.length]; UITextRange *textRange = [self.textView textRangeFromPosition:start toPosition:end];
7. Get the rectangle for this textRange using the method firstRectForRange and add a subview at this rectangle
1. The contentSize should be calculated once when the attributedText is loaded.
2. The subview added in Step 7 should be exactly on top of the keyword we searched
1. The contentSize is recalculated when the user scrolls. Consequently, you will notice that the contentSize is recalculated. As the contentSize gets recalculated the scroll indicator jumps back a little.
2. The subview added in Step 7 is not on the keyword we searched
iOS 7.0 and iOS 7.0.2
This bug occurs only on iOS 7 and above
On iOS 6, the behaviour is as expected
If anyone knows a solution, hit me on Twitter (@mugunthkumar)
I opened a radar and raised a support ticket.
Apple replied that, by turning off non contiguous layout and using the following code snippet, you should be able to get the correct height/contentOffset.
self.textView.layoutManager.allowsNonContiguousLayout = NO; NSRange actualGlyphRange; [self.textView.layoutManager glyphRangeForCharacterRange:range actualCharacterRange:&actualGlyphRange]; CGRect boundingRect = [self.textView.layoutManager boundingRectForGlyphRange:actualGlyphRange inTextContainer:self.textView.textContainer]; UIEdgeInsets insets = [self.textView textContainerInset]; UIView *view = [[UIView alloc] initWithFrame:CGRectMake(boundingRect.origin.x + insets.left, boundingRect.origin.y + insets.top, boundingRect.size.width, boundingRect.size.height)]; //UIView *view = [[UIView alloc] initWithFrame:rect];
This fixed the problem for me. Note that, these methods are iOS 7 only.
Follow me on Twitter