Как мне определить размер UITextView для его содержимого на iOS 7?

Я использовал принятый ответ здесь в течение многих лет.

На iOS 7 contentSize.height становится frame.height-8 независимо от текстового содержимого.

Какой рабочий метод для регулировки высоты на iOS 7?

14 ответов

Решение

Я поддерживаю это минимальное изменение кода: просто добавьте эти две строки после addSubview и прежде чем схватить height из frame

...
[scrollView1 addSubview: myTextView];

    [myTextView sizeToFit]; //added
    [myTextView layoutIfNeeded]; //added

CGRect frame = myTextView.frame;
...

Это проверено на обратную совместимость с iOS 6. Обратите внимание, что он сокращает ширину. Если вас интересует только высота и фиксированная ширина, просто возьмите новую высоту, но установите оригинальную ширину, и она работает так же, как и раньше, на iOS 6 и 7.

(Предположение: он также подходит и для iOS 7, но макет обновляется позже или в отдельном потоке, и это сразу вынуждает макет, чтобы его кадр обновлялся вовремя для использования значения высоты несколькими строками позже в той же теме.)

ЗАМЕТКИ:

1) Вы могли или не могли бы реализовать изменение размера внешнего контейнера таким образом. Хотя, похоже, это обычный фрагмент кода, и я использовал его в своих проектах.

2) С sizeToFit Кажется, работает на iOS 7, как и ожидалось, скорее всего, вам не нужен преждевременный addSubView. Будет ли он работать на iOS 6, тогда я не проверял.

3) Спекуляция: экстра layoutIfNeeded середина нити может быть дорогостоящей. Альтернатива, как я вижу, заключается в изменении размера внешнего контейнера при обратном вызове макета (запускается или нет в зависимости от того, решает ли ОС, нужен ли макет), когда изменение размера внешнего контейнера вызовет другое обновление макета. Оба обновления могут быть объединены с другими обновлениями макета, чтобы быть более эффективными. Если у вас есть такое решение, и вы можете показать, что оно более эффективно, добавьте его в качестве ответа, и я обязательно упомяну его здесь.

Поскольку я использую Auto Layout, я использую значение [textView sizeThatFits:CGSizeMake(textView.frame.size.width, CGFLOAT_MAX)].height обновить constant из textViewвысота UILayoutConstraint,

Я использую адаптированную версию ответа 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;
    }
}

Основываясь на других ответах, я заставил это работать (в Swift). Это решает проблему с символом новой строки.

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

Авто макет нужен.

Если вы используете Auto Layout, вы можете создать тривиальный 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

Если вы используете автоматическое размещение, вы можете использовать следующий подкласс 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

Этот метод, кажется, работает.

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

Если вы используете iOS 7+, вы можете просто включить автоматическую разметку, прикрепить каждую из сторон текстового представления к краю родительского представления, и оно будет работать нормально. Дополнительный код не требуется.

Не уверен, что так было всегда, но верно следующее, по крайней мере, с iOS 10.

реализует собственность, если . Это означает, что вам просто нужно убедиться, что ширина текстового представления достаточно ограничена, а затем вы можете использовать внутреннюю высоту содержимого (либо с помощью приоритетов сопротивления обниманию/сжатию содержимого Auto Layout, либо напрямую используя значение во время ручного макета).

К сожалению, такое поведение не задокументировано. Apple могла бы легко избавить нас всех от головной боли… нет необходимости в дополнительных ограничениях по высоте, подклассах и т. д.

Ребята, использующие autolayout, и ваш sizetofit не работает, тогда, пожалуйста, проверьте ограничение ширины один раз. Если вы пропустили ограничение ширины, высота будет точной.

Нет необходимости использовать любой другой API. только одна строка решит все проблемы.

[_textView sizeToFit];

Здесь я интересовался только высотой, сохраняя фиксированную ширину и пропустив ограничение ширины моего TextView в раскадровке.

И это должно было показать динамический контент от сервисов.

Надеюсь, что это может помочь..

Я написал категорию более UITextView:

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

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

когда UIKit устанавливает его contentSize, UITextView регулирует его intrinsic content size, Это хорошо играет с autolayout,

Ответ, данный bilobatum, отлично работал с автоматическим макетом, то есть подклассом textview.

Если вы хотите ограничить высоту текстового представления, добавьте еще одно ограничение (я добавил его, используя раскадровку, т.е. высоту <= 166 (высота в соответствии с вашими потребностями))

Затем внутри подкласса уменьшите приоритет ограничения высоты до 750 (self.heightConstraint.priority = 750), чтобы избежать конфликта между ограничением высоты, добавленным в подкласс, и ограничением высоты, добавленным в раскадровку.

В раскадровке, если используются ограничения, убедитесь, что вы ограничены своим суперпредставлением на вкладке "Линейка" в правой панели xcode для UITextView. Моя проблема заключалась в том, что у меня было ограничение -80 баллов на "Trailing space to".

В 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
Другие вопросы по тегам