Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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]}];
}
like image 274
Tom Irving Avatar asked Jan 27 '13 06:01

Tom Irving


2 Answers

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;
}
like image 160
Tom Irving Avatar answered Oct 04 '22 16:10

Tom Irving


I think you're right--I couldn't get -[NSAttributedString boundingRectWithSize:options:context: ] to return a value that worked correctly for all font sizes...

I did get a UILabel to correctly size and draw attributed strings however:

  • change your text view class to UILabel
  • use -setAttributedText: on the label
  • set label.numberOfLines = 0 (multiline)
  • in -layoutSubviews of your label's superview call -[ label sizeThatFits: ] to get the correct size for the label....

Worked for me...

EDIT: Here's my ViewController.m file:

@interface View : UIView
@property ( nonatomic, strong ) UITextView * textView ;
@property ( nonatomic, strong ) UISlider * fontSizeSlider ;
@end

@implementation View

-(void)layoutSubviews
{
    CGRect bounds = self.bounds ;

    self.textView.layer.anchorPoint = (CGPoint){ 0.0f, 0.5f } ;
    self.textView.layer.position = (CGPoint){ CGRectGetMinX( bounds ), CGRectGetMidY( bounds ) } ;
    self.textView.bounds = (CGRect){ .size = [ self.textView sizeThatFits:bounds.size ] } ;

    self.fontSizeSlider.frame = CGRectMake(5, CGRectGetMaxY(bounds) - 30, bounds.size.width - 10, 25) ;
}

@end

@interface ViewController ()

@end

@implementation ViewController

-(void)loadView
{
    self.view = [[ View alloc ] initWithFrame:CGRectZero ] ;
}

- (void)viewDidLoad {
    [super viewDidLoad];

    NSMutableAttributedString * aString = [[ NSMutableAttributedString alloc ] initWithString:@"Some test text in a scaling text view" ] ;
    NSDictionary * attributes = @{ NSForegroundColorAttributeName : [ UIColor redColor ] } ;
    [ aString setAttributes:attributes range:(NSRange){ 5, 4 } ] ;

    textView = [[UITextView alloc] initWithFrame:CGRectZero];
    [textView setAttributedText:aString ];
    [textView setFont:[UIFont systemFontOfSize:18]];
    [textView setCenter:CGPointMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds))];

    ((View*)self.view).textView = textView ;
    [self.view addSubview:textView];
    [textView release];

    UISlider * fontSizeSlider = [[UISlider alloc] initWithFrame:CGRectMake(5, CGRectGetMaxY(self.view.bounds) - 30, self.view.bounds.size.width - 10, 25)];
    [fontSizeSlider addTarget:self action:@selector(fontSizeSliderDidChange:) forControlEvents:UIControlEventValueChanged];
    [fontSizeSlider setMinimumValue:5];
    [fontSizeSlider setMaximumValue:100];
    [fontSizeSlider setValue:textView.font.pointSize];
    ((View*)self.view).fontSizeSlider = fontSizeSlider ;
    [self.view addSubview:fontSizeSlider];
    [fontSizeSlider release];
}

- (void)fontSizeSliderDidChange:(UISlider *)sender {
    [ textView setFont:[textView.font fontWithSize:sender.value]];
    [ self.view setNeedsLayout ] ;
}

@end
like image 44
nielsbot Avatar answered Oct 04 '22 16:10

nielsbot