Search code examples
iosobjective-cuikituitextviewnsattributedstring

UITextView NSAttributedString Size


I'm working on an app which uses a UITextView.

The UITextView should grow or shrink to fit its text, both vertically and horizontally. To do this, I'm overriding sizeToFit in a subclass, and I set the bounds like so:

- (void)sizeToFit {
    [self setBounds:(CGRect){.size = self.attributedText.size}];
}

The problem is that this size just doesn't reflect the correct size of the string, as the UITextView clips the text. I'm setting the edge insets to zero, so that shouldn't be an issue right?

At this point, I think it's a bug with NSAttributedString's size property, but the same thing happens if I use boundingRectWithSize:options:context:

[self setBounds:(CGRect){.size = [self.attributedText boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:0 context:nil].size}];

So maybe whatever code is doing layout calculations for NSAttributedString doesn't play nicely with UITextView's layout calculations.

Here is example project which demonstrates the issue.

Any ideas are welcome!

EDIT: I should also point out that the following doesn't work either:

- (void)sizeToFit {
    [self setBounds:(CGRect){.size = [self sizeThatFits:self.attributedText.size]}];
}

Solution

  • Though not perfect, I ended up using:

    - (void)sizeToFit {
    
        CGSize textSize = self.attributedText.size;
        CGSize viewSize = CGSizeMake(textSize.width + self.firstCharacterOrigin.x * 2,
                                     textSize.height + self.firstCharacterOrigin.y * 2);
    
        [self setBounds:(CGRect){.size = [self sizeThatFits:viewSize]}];
    }
    
    - (CGPoint)firstCharacterOrigin {
    
        if (!self.text.length) return CGPointZero;
    
        UITextRange * range = [self textRangeFromPosition:[self positionFromPosition:self.beginningOfDocument offset:0]
                                               toPosition:[self positionFromPosition:self.beginningOfDocument offset:1]];
        return [self firstRectForRange:range].origin;
    }