【问题标题】:How do I size a UITextView to its content on iOS 7?如何在 iOS 7 上根据其内容调整 UITextView 的大小?
【发布时间】:2013-10-03 16:17:01
【问题描述】:

多年来,我一直在使用公认的答案here

在 iOS 7 上,contentSize.height 变为 frame.height-8,与文本内容无关。

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

【问题讨论】:

  • 我遇到了同样的问题。我看到他们将 textContainer 添加到具有大小的 UIView 中,但我目前认为它的大小不准确。

标签: dynamic height uitextview ios7


【解决方案1】:

这个方法似乎行得通。

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

【讨论】:

    【解决方案2】:

    我赞成这种最小的代码更改:只需在 addSubview 之后和抓住 frameheight 之前添加这两行

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

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

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

    注意事项:

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

    2) 由于 sizeToFit 在 iOS 7 上似乎可以正常工作,因此您可能不需要过早的 addSubView。它是否仍然可以在 iOS 6 上运行我还没有测试过。

    3) 推测:额外的layoutIfNeeded 中间线程可能代价高昂。我看到的另一种方法是在布局回调上调整外部容器的大小(根据操作系统是否决定是否需要布局来触发),其中外部容器调整大小将导致另一个布局更新。两种更新都可以与其他布局更新结合使用以提高效率。如果您确实有这样的解决方案,并且您可以证明它更有效率,请将其添加为答案,我一定会在此处提及。

    【讨论】:

    • 它调整了 textview 的大小,但我仍然没有设法在 iOS 7 中调整 textview 的父元素的大小,你能分享更多细节吗?
    • 不知道,信息太少。但是如果你用不起作用的代码写一个问题,我可以看看。
    • @JannieT 自从我发布答案以来,我在应用商店更新的每个应用中都使用了它,我猜大概有五个左右。有些文本视图是单行的,有些是多行的。我今天可能要更新另一个,可以看看最新的 OS 7.1.x。
    【解决方案3】:

    由于我使用的是自动布局,因此我使用[textView sizeThatFits:CGSizeMake(textView.frame.size.width, CGFLOAT_MAX)].height 的值来更新textView 的高度constantUILayoutConstraint

    【讨论】:

    • 谢谢,这很有用。希望在我输入时动态调整大小。我想我能要求的最好的是将大小调整逻辑放在委托的 textFieldDidEndEditing 中?
    • MattDiPasquale,我应该把这些代码放在那里吗?进viewDidLoad、layoutSubviews还是别的?
    • @AlexanderVolkov layoutSubviews 或 viewWillAppear.
    • 这个 NSLayoutConstraint 被配置了一个超出内部限制的常量。将替换一个较小的值,但应该解决此问题。中断 void _NSLayoutConstraintNumberExceedsLimit() 进行调试。这将只记录一次。这可能会在未来打破。 -[<_uitexttiledlayer:> display]:忽略虚假层大小(375.000000、1000000000.000000)、contentsScale 2.000000、后备存储大小(750.000000、2000000000.000000)
    • @mattdipasquale:不知道为什么这没有被标记为已接受的答案 :) 但是你先生 :) 用这段代码拯救了我的一天 :) 因此投票赞成 :)
    【解决方案4】:

    我使用了 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;
        }
    }
    

    【讨论】:

    • 我可以从驯服代码中解决 UITextView 的内容高度是 iOS 7。谢谢。
    • 当用户点击返回时不适应。在您输入文本之前剪掉新行。
    • 非常适合我,谢谢 :) 为避免线剪断,当检测到不同的线高时只需使用 [self.textView scrollRangeToVisible:NSMakeRange(0,0)];
    【解决方案5】:

    如果您使用自动布局,您可以创建一个简单的 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 中创建的高度约束只是一个占位符(即,勾选“在构建时删除”框)。

    实现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
    

    【讨论】:

    • 非常好!而且很有帮助! :D 谢谢!!
    • 我认为子类化 UITextView 并使用高度约束是处理自动布局时的最佳解决方案。谢谢。
    【解决方案6】:

    在 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
    

    【讨论】:

      【解决方案7】:

      在情节提要中,如果使用约束,请确保您在 UITextView 的 xcode 右侧窗格的“标尺”选项卡中被约束到您的超级视图。我的问题是我对“尾随空格”的限制为 -80 分。

      【讨论】:

        【解决方案8】:

        如果您使用自动布局,则可以使用以下 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
        

        【讨论】:

        • 它在 iOS 7.0 上对我有用。但没有检查 iOS > 7.0。谢谢@phatmann。
        • intrinsicContentSize 的文档说:This intrinsic size must be independent of the content frame, because there’s no way to dynamically communicate a changed width to the layout system based on a changed height. 所以我的代码并不是真正的 kosher。在 iOS 7 上它可以工作,但在 iOS 8 上不可靠。如果您在 iOS 8 上使用自调整单元格,那么您可以使用 preferredLayoutAttributesFittingAttributes:
        【解决方案9】:

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

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

        [_textView sizeToFit];
        

        在这里,我只关心高度,保持宽度固定,并错过了我的 TextView 在情节提要中的宽度限制。

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

        希望这可能会有所帮助..

        【讨论】:

          【解决方案10】:

          根据其他答案,我使它工作(在 Swift 中)。 这解决了换行符的问题。

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

          需要自动布局。

          【讨论】:

            【解决方案11】:

            如果您使用的是 iOS 7+,您只需打开自动布局,将文本视图的每一侧固定到其父视图的边缘,就可以正常工作。不需要额外的代码。

            【讨论】:

              【解决方案12】:

              我在UITextView上写了一个分类:

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

              UIKit 设置它的contentSize 时,UITextView 调整它的intrinsic content size。这与autolayout 配合得很好。

              【讨论】:

                【解决方案13】:

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

                如果您想限制文本视图的高度,请添加另一个约束(我使用情节提要添加了它,即高度

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

                【讨论】:

                  【解决方案14】:

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

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

                  很遗憾,这种行为没有记录在案。 Apple 可以轻松为我们省去一些麻烦……无需额外的高度限制、子类化等。

                  【讨论】:

                    猜你喜欢
                    • 2010-09-08
                    • 2014-03-30
                    • 2013-11-19
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多