iOS 7 breaks UITextView + NSAttributedString pretty badly that it’s almost unusable.
Submitted this Radar today.

Issue 1.

The UITextView calculates the contentSize as the user scrolls. As a result the scroll indicators jump randomly.

Issue 2.

Because the text view doesn’t know about the complete size of content, methods like

firstRectForRange:
caretRectForPosition:

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

Expected Results:

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

Actual Results:

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

Version:

iOS 7.0 and iOS 7.0.2

Configuration:

This bug occurs only on iOS 7 and above
On iOS 6, the behaviour is as expected

Code attachments

https://github.com/MugunthKumar/UITextViewAttributedStringBug

If anyone knows a solution, hit me on Twitter (@mugunthkumar)

Solution

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.


Mugunth

Follow me on Twitter