Обновите ограничения для дополнительного представления UICollectionView при изменении ориентации

Я использую заголовок (дополнительный вид) в моем файле UICollectionView. На заголовке у меня есть ярлык, который находится на определенном расстоянии от левой стороны. Я делаю свой расчет и устанавливаю константу ограничения. Все работает так, как ожидалось.

Если ориентация теперь изменена, метки занимают старое положение. Теперь мне нужно обновить ограничения для всех моих заголовков. Вызов invalidateLayout не обновляет ограничения. Как я могу вручную запустить пересчет?

Изменить:

Вот так выглядит мой layoutSubviews, где происходит пересчет:

public override void LayoutSubviews ()
{
    this.width = this.collectionViewSize.Width;
    this.itemWidth  = (nfloat)Math.Round(this.width / numberOfItemsInRow);

    leftSpacing.Constant =  this.itemWidth * this.referencePoint;
    if (leftSpacing.Constant == 0)
        leftSpacing.Constant = SectionHeader.MIN_SPACING;

    base.LayoutSubviews ();
}

Как видите, это не Objective-C, но вы должны видеть, что я делаю. Я беру ширину представления коллекции (устанавливается при создании экземпляра дополнительного представления), а затем вычисляю ширину ячейки, которая почти соответствует реальному размеру ячеек. С помощью referencePoint я определяю позицию. Здесь вы можете увидеть настройку моих ограничений:

public SectionHeader (CGRect frame) : base (frame)
{
    this.width = frame.Size.Width;
    this.itemWidth  = (nfloat)Math.Round(width / numberOfItemsInRow);

    label = new UILabel (){
        BackgroundColor = UIColor.White,
        TextAlignment = UITextAlignment.Left
    };

    label.TranslatesAutoresizingMaskIntoConstraints = false;
    AddSubview (label);

    NSMutableDictionary viewsDictionary = new NSMutableDictionary();
    viewsDictionary["label"] = label;

    this.AddConstraints(NSLayoutConstraint.FromVisualFormat("V:|[label]|",(NSLayoutFormatOptions)0,null,viewsDictionary));

    leftSpacing = NSLayoutConstraint.Create(label, NSLayoutAttribute.Left, NSLayoutRelation.Equal, this, NSLayoutAttribute.Left, 1, SectionHeader.MIN_SPACING);
    leftSpacing.Priority = 250;
    this.AddConstraint(leftSpacing);
    this.AddConstraint(NSLayoutConstraint.Create(label, NSLayoutAttribute.Right, NSLayoutRelation.Equal, this, NSLayoutAttribute.Right, 1, 0));
}

В этом коде вычисление расстояния слева почти такое же, за исключением того, что frame имеет высоту множества headerReferenceSize. Ширина должна быть в порядке для инициализации. Я использую полную высоту для метки, устанавливаю начальное пространство и закрепляю ее справа.


person testing    schedule 18.03.2015    source источник


Ответы (1)


Вам нужно вызвать layoutIfNeeded в главном представлении в контроллере представления, чтобы обновить ограничения. Что-то вроде этого должно работать:

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
    // Update the constraints here for the new orientation - toOrientation
    // ...

    [UIView animateWithDuration:duration animations: ^{
        [self.view layoutIfNeeded];
    }];
}

Это анимирует изменения макета при изменении ориентации.

Вы можете добавить точку останова после вызова layoutIfNeeded, чтобы увидеть, что кадры соответствуют вашим ожиданиям.

Если ваши ограничения находятся не в контроллере представления, а в подклассе UIView, вы можете сделать следующее:

- (void)awakeFromNib {
    [super awakeFromNib];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateIntrinsicConstraints) name:UIDeviceOrientationDidChangeNotification object:nil];
}

- (void)updateIntrinsicConstraints {
    // Update your constraints here
    [self.constraint setConstant:newValue];

    // You might need to call [self layoutIfNeeded] here, if you don't call it in another place
}
- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

Дайте мне знать, если это сработало для вас!


Обновить

Вы не должны изменять рамку SectionHeader вручную. Вы должны сделать это, реализуя метод протокола:

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section;

Вы можете вызвать layoutIfNeeded в методе инициализации SectionHeader после добавления ограничений к метке, добавить точку останова после, чтобы увидеть, правильный ли кадр.

Дайте мне знать, если это сработает для вас!

person Catalina T.    schedule 18.03.2015
comment
Спасибо за ваш ответ. Если я вызову layoutIfNeeded, мой текущий макет будет уничтожен (ширина ячеек не обновляется). Что здесь работает, так это то, что я вызываю invalidateLayout после layoutIfNeeded. Моя следующая проблема заключается в том, что у меня нет доступа к ограничениям, потому что они находятся в подклассе UICollectionReusableView. У меня есть доступ только к дополнительному виду при создании (в collectionView:viewForSupplementaryElementOfKind:atIndexPath:). - person testing; 18.03.2015
comment
Я обновил свой ответ, чтобы ответить на ваш вопрос. Самый простой способ - расширить UICollectionReusableView и в подклассе иметь IBOutlet для ограничений, которые вам нужно изменить, и сделать это в методе updateIntrinsicConstraints - person Catalina T.; 18.03.2015
comment
Я тоже что-то думал об этом, но добавил уведомление только в контроллер представления, а не в подпредставление. Приведет ли это к штрафам за производительность? Я попробовал ваш подход, и он действительно работает! Поскольку ограничения адаптируются после изменения ориентации, я получаю ошибки автоматической компоновки (старая ширина из альбомной ориентации используется в портретной ориентации и выходит за пределы экрана). Теперь смотрю, смогу ли я поймать уведомление до того, как произойдет переход на новую ориентацию... - person testing; 18.03.2015
comment
При изменении ориентации следует вызвать layoutSubviews для вашего пользовательского вида. Может быть, вы могли бы обновить там ограничения в зависимости от текущей ориентации устройства, чтобы они больше не вылетали. Вам просто нужно сначала изменить ограничения и вызвать [super layoutSubviews] последним. - person Catalina T.; 18.03.2015
comment
Спасибо за совет. Теперь я все вставил в layoutSubviews, который работает без подписки на уведомления. Я все еще получаю сообщение об ошибке автоматического макета Невозможно одновременно удовлетворить ограничения, поскольку позиционирование метки (345) превышает размер экрана (UIView-Encapsulated-Layout-Width равен 320). У вас есть идея, почему это все еще происходит? Я наконец звоню [super layoutSubviews], и расчет должен быть правильным. Инкапсулированное представление, кажется, обновляется после того, как я изменил свои ограничения, но в этот момент он жалуется, что я изменяю ограничение. - person testing; 18.03.2015
comment
Не могли бы вы опубликовать расчет ограничения в layoutSubviews? Вы должны убедиться, что значение ограничения никогда не превышает ширину представления. Другим вариантом может быть установка приоритета одного из ваших ограничений (например, конечного пробела для супервизора) на Низкий (250). Таким образом, ваши ограничения больше не будут падать, и если кадр установлен правильно для ориентации, заголовок View также будет выглядеть правильно. - person Catalina T.; 18.03.2015
comment
Я отредактировал свой вопрос и опубликовал настройку ограничения. Я знаю, что значение ограничения не должно быть больше, чем представление, но когда тогда следует обновлять ограничение? Теперь я установил низкий приоритет, как вы сказали, и он работает как шарм! - person testing; 19.03.2015
comment
Я больше не получаю ошибку автоматического макета. Но это не совсем правильно. Дополнительный вид, который виден на экране, не находится в правильном положении после изменения ориентации. Все невидимые дополнительные представления получают текущий размер представления коллекции, установленный при инициализации. У вас есть идея для этого? ОК, я видел, что вы отредактировали свой вопрос. Я сделал это и для себя, чтобы отразить текущее состояние. Я посмотрю на это. Еще одна вещь: в iOS 7 я должен установить приоритет на 750. В противном случае, если leftSpacing почти равен нулю, он будет закреплен с правой стороны. - person testing; 19.03.2015
comment
Я не меняю рамку для SectionHeader. Фрейм установлен в UICollectionViewFlowLayout с шириной = 0. Использование другой ширины на самом деле ничего не меняет. Единственное, что мне не подходит, это то, что дополнительный вид, который в данный момент отображается на экране, не обновляется. Итак, здесь мне понадобится логика, которая обнаруживает изменение ориентации и изменяет только расчет для просмотра на экране. Не знаю, как будет выглядеть такое решение. Из-за других недостатков моего дизайна я полностью изменил свой подход. Теперь я получил то, что хотел. Я очень ценю ваши усилия в помощи мне! - person testing; 19.03.2015