How do I size a UITextView to its content on iOS 7?

前端 未结 13 1699
伪装坚强ぢ
伪装坚强ぢ 2020-11-28 03:04

I\'ve been using the accepted answer here for years.

On iOS 7, the contentSize.height becomes the frame.height-8, regardless of text content.

What\'s a worki

相关标签:
13条回答
  • 2020-11-28 03:36

    In iOS 8 you'll in inherit some content offset from the parent, which you need to get rid of as well.

    A subclass example

    // Originally from https://github.com/Nikita2k/resizableTextView
    
    #import "ResizableTextView.h"
    
    @implementation ResizableTextView
    
    - (void) updateConstraints {
    
        // calculate contentSize manually
        // ios7 doesn't calculate it before viewDidAppear and we'll get here before
        CGSize contentSize = [self sizeThatFits:CGSizeMake(self.frame.size.width, FLT_MAX)];
    
        // set the height constraint to change textView height
        [self.constraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *constraint, NSUInteger idx, BOOL *stop) {
            if (constraint.firstAttribute == NSLayoutAttributeHeight) {
                constraint.constant = contentSize.height;
                *stop = YES;
            }
        }];
    
        [super updateConstraints];
    }
    
    - (void)setContentOffset:(CGPoint)contentOffset
    {
        // In iOS 8 we seem to be inheriting the content offset from the parent.
        // I'm not interested
    }
    
    @end
    
    0 讨论(0)
  • 2020-11-28 03:40

    I use an adapted version of madmik's answer that eliminates the fudge factor:

    - (CGFloat)measureHeightOfUITextView:(UITextView *)textView
    {
        if ([textView respondsToSelector:@selector(snapshotViewAfterScreenUpdates:)])
        {
            // This is the code for iOS 7. contentSize no longer returns the correct value, so
            // we have to calculate it.
            //
            // This is partly borrowed from HPGrowingTextView, but I've replaced the
            // magic fudge factors with the calculated values (having worked out where
            // they came from)
    
            CGRect frame = textView.bounds;
    
            // Take account of the padding added around the text.
    
            UIEdgeInsets textContainerInsets = textView.textContainerInset;
            UIEdgeInsets contentInsets = textView.contentInset;
    
            CGFloat leftRightPadding = textContainerInsets.left + textContainerInsets.right + textView.textContainer.lineFragmentPadding * 2 + contentInsets.left + contentInsets.right;
            CGFloat topBottomPadding = textContainerInsets.top + textContainerInsets.bottom + contentInsets.top + contentInsets.bottom;
    
            frame.size.width -= leftRightPadding;
            frame.size.height -= topBottomPadding;
    
            NSString *textToMeasure = textView.text;
            if ([textToMeasure hasSuffix:@"\n"])
            {
                textToMeasure = [NSString stringWithFormat:@"%@-", textView.text];
            }
    
            // NSString class method: boundingRectWithSize:options:attributes:context is
            // available only on ios7.0 sdk.
    
            NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
            [paragraphStyle setLineBreakMode:NSLineBreakByWordWrapping];
    
            NSDictionary *attributes = @{ NSFontAttributeName: textView.font, NSParagraphStyleAttributeName : paragraphStyle };
    
            CGRect size = [textToMeasure boundingRectWithSize:CGSizeMake(CGRectGetWidth(frame), MAXFLOAT)
                                                      options:NSStringDrawingUsesLineFragmentOrigin
                                                   attributes:attributes
                                                      context:nil];
    
            CGFloat measuredHeight = ceilf(CGRectGetHeight(size) + topBottomPadding);
            return measuredHeight;
        }
        else
        {
            return textView.contentSize.height;
        }
    }
    
    0 讨论(0)
  • 2020-11-28 03:42

    this method seems to work.

    // Code from apple developer forum - @Steve Krulewitz, @Mark Marszal, @Eric Silverberg
    - (CGFloat)measureHeight
    {
    if ([self respondsToSelector:@selector(snapshotViewAfterScreenUpdates:)])
    {
        CGRect frame = internalTextView.bounds;
        CGSize fudgeFactor;
        // The padding added around the text on iOS6 and iOS7 is different.
        fudgeFactor = CGSizeMake(10.0, 16.0);
    
        frame.size.height -= fudgeFactor.height;
        frame.size.width -= fudgeFactor.width;
    
        NSMutableAttributedString* textToMeasure;
        if(internalTextView.attributedText && internalTextView.attributedText.length > 0){
            textToMeasure = [[NSMutableAttributedString alloc] initWithAttributedString:internalTextView.attributedText];
        }
        else{
            textToMeasure = [[NSMutableAttributedString alloc] initWithString:internalTextView.text];
            [textToMeasure addAttribute:NSFontAttributeName value:internalTextView.font range:NSMakeRange(0, textToMeasure.length)];
        }
    
        if ([textToMeasure.string hasSuffix:@"\n"])
        {
            [textToMeasure appendAttributedString:[[NSAttributedString alloc] initWithString:@"-" attributes:@{NSFontAttributeName: internalTextView.font}]];
        }
    
        // NSAttributedString class method: boundingRectWithSize:options:context is
        // available only on ios7.0 sdk.
        CGRect size = [textToMeasure boundingRectWithSize:CGSizeMake(CGRectGetWidth(frame), MAXFLOAT)
                                                  options:NSStringDrawingUsesLineFragmentOrigin
                                                  context:nil];
    
        return CGRectGetHeight(size) + fudgeFactor.height;
    }
    else
    {
        return self.internalTextView.contentSize.height;
    }
    }
    
    0 讨论(0)
  • 2020-11-28 03:43

    I favor this minimal code change: Just add these two lines after addSubview and before grabbing the height of the frame

    ...
    [scrollView1 addSubview: myTextView];
    
        [myTextView sizeToFit]; //added
        [myTextView layoutIfNeeded]; //added
    
    CGRect frame = myTextView.frame;
    ...
    

    This is tested backwards compatible with iOS 6. NOTE that it shrink-wraps the width. If you're just interested in the height and have a fixed width, just grab the new height but set the original width, and it works just as before on both iOS 6 and 7.

    (Speculation: it does size to fit on iOS 7 as well, but the layout is updated later or in a separate thread, and that this forces the layout immediately so that its frame is updated in time for using its height value a few lines later in the same thread.)

    NOTES:

    1) You might or might not have implemented the outer container resize this way. It does seem to be a common snippet, though, and I've used it in my projects.

    2) Since sizeToFit seems to work as expected on iOS 7, you likely don't need the premature addSubView. Whether it will still work on iOS 6 then is untested by me.

    3) Speculation: The extra layoutIfNeeded mid-thread might be costly. The alternative as I see it is to resize the outer container on the layout callback (fired or not depending on if the OS decides whether layout is needed or not) where the outer container resize will cause another layout update. Both updates might be combined with other layout updates to be more efficient. If you do have such a solution and you can show that it is more efficient, add it as answer and I'll be sure to mention it here.

    0 讨论(0)
  • 2020-11-28 03:44

    If you're using Auto Layout, you could create a trivial UITextView subclass that self-sizes the text view height to fit the content:

    @interface ContentHeightTextView : UITextView
    
    @end
    
    @interface ContentHeightTextView ()
    @property (nonatomic, strong) NSLayoutConstraint *heightConstraint;
    @end
    
    @implementation ContentHeightTextView
    
    - (void)layoutSubviews
    {
        [super layoutSubviews];
    
        CGSize size = [self sizeThatFits:CGSizeMake(self.bounds.size.width, FLT_MAX)];
    
        if (!self.heightConstraint) {
            self.heightConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:1.0f constant:size.height];
            [self addConstraint:self.heightConstraint];
        }
    
        self.heightConstraint.constant = size.height;
    
        [super layoutSubviews];
    }
    
    @end
    

    Of course, the text view's width and position must be defined by additional constraints configured elsewhere in the program.

    If you create this custom text view in IB, give the text view a height constraint in order to satisfy Xcode; just make sure the height constraint created in IB is merely a placeholder (i.e., tick the box that says "Remove at build time").

    enter image description here

    An alternative way to implement the UITextView subclass is as follows (this implementation might qualify as best practice):

    @interface ContentHeightTextView ()
    @property (nonatomic, strong) NSLayoutConstraint *heightConstraint;
    @end
    
    @implementation ContentHeightTextView
    
    - (void)layoutSubviews
    {
        [super layoutSubviews];
    
        [self setNeedsUpdateConstraints];
    }
    
    - (void)updateConstraints
    {
        CGSize size = [self sizeThatFits:CGSizeMake(self.bounds.size.width, FLT_MAX)];
    
        if (!self.heightConstraint) {
            self.heightConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:1.0f constant:size.height];
            [self addConstraint:self.heightConstraint];
        }
    
        self.heightConstraint.constant = size.height;
        [super updateConstraints];
    }
    
    @end
    
    0 讨论(0)
  • 2020-11-28 03:45

    Since I'm using Auto Layout, I use the value of [textView sizeThatFits:CGSizeMake(textView.frame.size.width, CGFLOAT_MAX)].height to update the constant of the textView's height UILayoutConstraint.

    0 讨论(0)
提交回复
热议问题