如何在iOS 7上调整UITextView的内容?

时间:2013-09-27 11:24:38

标签: dynamic height uitextview ios7

我多年来一直在使用接受的答案here

在iOS 7上,无论文本内容如何,​​contentSize.height都会成为frame.height-8。

在iOS 7上调整高度的工作方法是什么?

14 个答案:

答案 0 :(得分:55)

我赞成这个最小的代码更改:只需在addSubview后添加这两行,然后再抓取height的{​​{1}}

frame

此向后测试与iOS 6兼容。注意它收缩包装宽度。如果你只是对高度感兴趣并且有一个固定的宽度,只需抓住新的高度但设置原始宽度,它就像以前一样在iOS 6和7上工作。

(推测:它的大小也适合iOS 7,但布局会在以后更新或在单独的线程中更新,这会立即强制布局,以便及时更新其框架以便使用它高度值在同一个线程中稍后几行。)

备注:

1)您可能已经或可能没有实现外部容器以这种方式调整大小。它似乎确实是一个常见的片段,我在我的项目中使用过它。

2)由于... [scrollView1 addSubview: myTextView]; [myTextView sizeToFit]; //added [myTextView layoutIfNeeded]; //added CGRect frame = myTextView.frame; ... 似乎在iOS 7上按预期工作,因此您可能不需要过早的addSubView。它是否仍然可以在iOS 6上运行,但我没有经过测试。

3)猜测:额外的sizeToFit中间线程可能代价高昂。我看到的替代方法是在布局回调上调整外部容器的大小(触发或不取决于操作系统是否需要布局),外部容器调整大小将导致另一个布局更新。两种更新都可能与其他布局更新相结合,以提高效率。如果你有这样的解决方案并且你可以证明它 更有效率,那就把它添加为答案,我一定会在这里提一下。

答案 1 :(得分:33)

由于我正在使用自动布局,因此我使用[textView sizeThatFits:CGSizeMake(textView.frame.size.width, CGFLOAT_MAX)].height的值来更新constant的高度textView的{​​{1}}。

答案 2 :(得分:17)

我使用了一个改编版本的madmik的答案,消除了软糖因素:

- (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;
    }
}

答案 3 :(得分:16)

基于其他答案,我使它成功(在Swift中)。 这解决了换行符的问题。

textView.sizeToFit()
textView.layoutIfNeeded()
let height = textView.sizeThatFits(CGSizeMake(textView.frame.size.width, CGFloat.max)).height
textView.contentSize.height = height

需要自动布局。

答案 4 :(得分:14)

如果您正在使用自动布局,则可以创建一个简单的UITextView子类,自定义文本视图高度以适合内容:

@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

当然,文本视图的宽度和位置必须由程序中其他位置配置的其他约束来定义。

如果在IB中创建此自定义文本视图,请为文本视图指定高度约束以满足Xcode;只需确保在IB中创建的高度约束仅仅是一个占位符(即勾选“在构建时删除”的框)。

enter image description here

实现UITextView子类的另一种方法如下(此实现可能符合最佳实践):

@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

答案 5 :(得分:4)

如果您使用自动布局,则可以使用以下添加内在高度的UITextView子类:

@implementation SelfSizingTextView

- (void)setText:(NSString *)text
{
    [super setText:text];
    [self invalidateIntrinsicContentSize];
}

- (void)setFont:(UIFont *)font
{
    [super setFont:font];
    [self invalidateIntrinsicContentSize];
}

- (CGSize)intrinsicContentSize
{
    CGFloat width = self.frame.size.width;
    CGSize size = [self sizeThatFits:CGSizeMake(width, MAXFLOAT)];
    return CGSizeMake(UIViewNoIntrinsicMetric, size.height);
}

@end

答案 6 :(得分:3)

这种方法似乎有效。

// 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;
}
}

答案 7 :(得分:1)

如果你正在使用iOS 7+,你可以打开自动布局,将文本视图的每一面都固定到其父视图的边缘,它可以正常工作。无需其他代码。

答案 8 :(得分:1)

不确定是否总是如此,但至少从 iOS 10 起以下是正确的。

UITextView 如果 intrinsicContentSize 实现 scrollEnabled == NO 属性。这意味着您只需要确保文本视图的宽度受到足够的约束,然后您就可以使用内在的内容高度(通过自动布局内容拥抱/压缩阻力优先级或在手动布局期间直接使用该值)。

不幸的是,此行为未记录在案。 Apple 可以轻松地为我们省去一些麻烦……无需额外的高度限制、子类化等。

答案 9 :(得分:0)

在iOS 8中,您将从父级继承一些内容,您需要将其除去。

子类示例

// 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

答案 10 :(得分:0)

在故事板中,如果使用约束,请确保在“标尺”中限制您的超级视图。 UITextView的xcode右侧窗格的选项卡。我的问题是我在'尾随空间的约束为-80分。

enter image description here

答案 11 :(得分:0)

使用自动布局的人和您的sizetofit不起作用,请检查您的宽度限制一次。如果您错过了宽度约束,那么高度将是准确的。

无需使用任何其他API。只需一行即可解决所有问题。

[_textView sizeToFit];

在这里,我只关心高度,保持宽度固定,并错过了故事板中TextView的宽度约束。

这是为了显示服务中的动态内容。

希望这可能有所帮助..

答案 12 :(得分:0)

我在UITextView上写了一个类别:

- (CGSize)intrinsicContentSize {
    return self.contentSize;
}

- (void)setContentSize:(CGSize)contentSize {
    [super setContentSize:contentSize];
    [self invalidateIntrinsicContentSize];
}

UIKit设置其contentSize时,UITextView会调整其intrinsic content size。这与autolayout非常吻合。

答案 13 :(得分:0)

bilobatum给出的答案与自动布局完美配合,即对textview进行子类化。

如果要限制文本视图的高度,请添加另一个约束(我使用故事板添加它,即高度< = 166(根据您的需要高度))

然后在子类内部将高度约束的优先级降低到750(self.heightConstraint.priority = 750),以避免在子类中添加的高度约束与在故事板上添加的高度约束之间发生冲突。