IOS Autolayout: проблема с UILabels при изменении размера родительского представления

У меня есть представление, содержащее только UILabel. Этот ярлык содержит многострочный текст. Родитель имеет переменную ширину, которая может быть изменена с помощью жесты панорамирования. Моя проблема в том, что когда я делаю это изменение размера, UILabel не пересчитывает его высоту, так что весь контент все еще виден, он просто отключает его.

Мне удалось исправить это с помощью взлома, но это ужасно медленно запускать:

- (void)layoutSubviews {

    CGSize labelSize = [self.labelDescription sizeThatFits:CGSizeMake(self.frame.size.width, FLT_MAX)];

    if (self.constraintHeight) {
        [self removeConstraint:self.constraintHeight];
    }

    self.constraintHeight = [NSLayoutConstraint constraintWithItem:self.labelDescription attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:labelSize.height];

    [self addConstraint:self.constraintHeight];

    [super layoutSubviews];
}

Не должно ли это происходить автоматически с автозапуском?

ИЗМЕНИТЬ

Структура моего представления:

UIScrollView
---> UIView
     ---> UILabel

Вот ограничения на UIScrollView:

<NSLayoutConstraint:0x120c4860 H:|-(>=32)-[DescriptionView:0x85c81c0]   (Names: '|':UIScrollView:0x85db650 )>,
<NSLayoutConstraint:0x120c48a0 H:|-([email protected])-[DescriptionView:0x85c81c0] priority:900   (Names: '|':UIScrollView:0x85db650 )>,
<NSLayoutConstraint:0x120c48e0 H:[DescriptionView:0x85c81c0(<=576)]>,
<NSLayoutConstraint:0x120c4920 H:[DescriptionView:0x85c81c0]-(>=32)-|   (Names: '|':UIScrollView:0x85db650 )>,
<NSLayoutConstraint:0x120c4960 H:[DescriptionView:0x85c81c0]-([email protected])-| priority:900   (Names: '|':UIScrollView:0x85db650 )>,
<NSLayoutConstraint:0x8301450 DescriptionView:0x85c81c0.centerX == UIScrollView:0x85db650.centerX>,

Вот ограничения на UIView:

<NSLayoutConstraint:0x85c4580 V:|-(0)-[UILabel:0x85bc7b0]   (Names: '|':DescriptionView:0x85c81c0 )>,
<NSLayoutConstraint:0x85c45c0 H:|-(0)-[UILabel:0x85bc7b0]   (Names: '|':DescriptionView:0x85c81c0 )>,
<NSLayoutConstraint:0x85c9f80 UILabel:0x85bc7b0.trailing == DescriptionView:0x85c81c0.trailing>,
<NSLayoutConstraint:0x85c9fc0 UILabel:0x85bc7b0.centerY == DescriptionView:0x85c81c0.centerY>

UILabel сам по себе не имеет ограничений, кроме того, что он привязывает его к краям его родительского

Ответ 1

Я исправил эту проблему после поднятия ошибки с Apple. Проблема в том, что многострочный текст требует двухпроходного подхода к компоновке, и все это зависит от свойства preferredMaxLayoutWidth

Вот соответствующий код, который необходимо добавить в контроллер просмотра, содержащий многострочную метку:

- (void)viewWillLayoutSubviews
{
    // Clear the preferred max layout width in case the text of the label is a single line taking less width than what would be taken from the constraints of the left and right edges to the label superview
    [self.label setPreferredMaxLayoutWidth:0.];
}

- (void)viewDidLayoutSubviews
{
    // Now that you know what the constraints gave you for the label width, use that for the preferredMaxLayoutWidth—so you get the correct height for the layout
    [self.label setPreferredMaxLayoutWidth:[self.label bounds].size.width];

    // And then layout again with the label correct height.
    [self.view layoutSubviews];
}

Ответ 2

Хорошо, я, наконец, прибил его. Решение состоит в том, чтобы установить preferredMaxLayoutWidth в viewDidLayoutSubviews, но только после первого раунда макета. Вы можете организовать это просто, отправив асинхронно обратно в основной поток. Итак:

- (void)viewDidLayoutSubviews {
    dispatch_async(dispatch_get_main_queue(), ^{
        self.theLabel.preferredMaxLayoutWidth = self.theLabel.bounds.size.width;
    });
}

Таким образом, вы не устанавливаете preferredMaxLayoutWidth до тех пор, пока ширина метки не будет правильно установлена ​​с помощью ограничений, связанных с супервизором.

Рабочий пример проекта здесь:

https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/ch23p673selfSizingLabel4/p565p579selfSizingLabel/ViewController.m

EDIT: Другой подход! Подкласс UILabel и переопределение layoutSubviews:

- (void) layoutSubviews {
    [super layoutSubviews];
    self.preferredMaxLayoutWidth = self.bounds.size.width;
}

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

Ответ 3

В Xcode 6.1 для iOS 7/8 я смог заставить это работать, просто установив preferredMaxLayoutWidth в методе setter, который вызвал мое представление, чтобы отобразить текст для метки. Я предполагаю, что он был установлен в 0 для начала. Пример ниже, где self.teachPieceLabel - это метка. Метка соединена с ограничениями рядом с другими метками в представлении в Interface Builder.

- (void)setTeachPieceText:(NSString *)teachPieceText {
      self.teachPieceLabel.text = teachPieceText;
      [self.teachPieceLabel setPreferredMaxLayoutWidth:[self.teachPieceLabel bounds].size.width];
}