Являются ли NSLayoutConstraints анимированными?

Я пытаюсь оживить некоторые представления, чтобы они были заблокированы гигантской клавиатурой в ландшафте. Он работает хорошо, если я просто анимации кадров, но другие предположили, что это контрпродуктивно, и я должен обновлять NSLayoutConstraints. Однако они не кажутся анимативными. Кто-нибудь получил их для работы с успехом?

//heightFromTop is an NSLayoutConstraint referenced from IB
[UIView animateWithDuration:0.25 animations:^{
    self.heightFromTop.constant= 550.f;
}];

Результат - мгновенный переход к рассматриваемой высоте.

Ответ 1

Просто следуйте этому точному шаблону:

self.heightFromTop.constant = 550.0f;
[myView setNeedsUpdateConstraints];

[UIView animateWithDuration:0.25f animations:^{
   [myView layoutIfNeeded];
}];

где myView - это вид, в который был добавлен self.heightFromTop. Ваше представление "прыгает", потому что единственное, что вы делали в блоке анимации, - это установить ограничение, которое не вызывает макеты сразу. В вашем коде макет происходит в следующем цикле запуска после установки heightFromTop.constant, и к тому времени вы уже выходите за рамки блока анимации.

В Swift 2:

self.heightFromTop.constant = 550
myView.setNeedsUpdateConstraints()

UIView.animateWithDuration(0.25, animations: {
   myView.layoutIfNeeded()
})

Ответ 2

Предлагаемый Apple способ немного отличается (См. пример в разделе "Анимация изменений, сделанных с помощью автоматической компоновки" ). Сначала вам нужно вызвать layoutIfNeeded перед анимацией. Затем добавьте свои анимационные материалы внутри блока анимации, а затем снова вызовите layoutIfNeeded. Для таких парней, как я, которые переходят на автозапуск, это более похоже на предыдущие анимации, которые мы делали с кадрами внутри блоков анимации. Нам просто нужно вызвать layoutIfNeeded дважды - перед анимацией и после анимации:

[self.view layoutIfNeeded]; // Ensures that all pending layout operations have been completed

[UIView animateWithDuration:1.0f animations:^{

  // Make all constraint changes here
  self.heightFromTop.constant= 550.f;

  [self.view layoutIfNeeded]; // Forces the layout of the subtree animation block and then captures all of the frame changes

}];

Ответ 3

Я попробовал подход @Centurion, но каким-то образом мой взгляд оживил бы неправильный фрейм, если он будет загружен из раскадровки. Проблема исчезнет, ​​если я заменил первый layoutIfNeeded на updateConstraintsIfNeeded, хотя я понятия не имею, почему. Если кто-нибудь может дать объяснение, это будет очень полезно.

[self.view updateConstraintsIfNeeded];
[UIView animateWithDuration:1.0 animations:^{
    self.myConstraint.constant= 100;
    [self.view layoutIfNeeded];
}];

Ответ 4

У меня была аналогичная проблема, и этот поток был большой помощью, чтобы пройти мимо него.

Ответ от erurainon поставил меня на правильный путь, но я хотел бы предложить немного другой ответ. Предлагаемый код от erurainon не работал у меня, поскольку у меня все же был прыжок, а не анимированный переход. Ссылка, предоставленная cnotethegr8, дала мне рабочий ответ:

Руководство по автоматическому компоновке https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/AutoLayoutbyExample/AutoLayoutbyExample.html (вплоть до нижней части страницы).

Несколько отличий от ответа erurainon:

  • Вызов layoutIfNeeded в представлении контейнера перед вызовом метода анимации (а вместо setNeedsUpdateConstraints в myView).
  • Задайте новое ограничение в блоке анимации.
  • Вызов layoutIfNeeded в представлении контейнера в методе анимации (после установки ограничения), а не в myView.

Это будет соответствовать шаблону, предложенному Apple в приведенной выше ссылке.

Пример

Я хотел анимировать определенный вид, закрывая или расширяя его одним нажатием кнопки. Поскольку я использую автозапуск и не хочу жестко кодировать любые размеры (в моем случае высоты) в коде, я решил захватить высоту в viewDidLayoutSubviews. Вам нужно использовать этот метод, а не viewWillAppear при использовании автозапуска. Поскольку viewDidLayoutSubviews можно вызвать много раз, я использовал BOOL, чтобы сообщить мне о первом выполнении для моей инициализации.

// Code snippets

@property (weak, nonatomic) IBOutlet UIView *topView; // Container for minimalView
@property (weak, nonatomic) IBOutlet UIView *minimalView; // View to animate

@property (nonatomic) CGFloat minimalViewFullHeight; // Original height of minimalView

@property (weak, nonatomic) IBOutlet NSLayoutConstraint *minimalViewHeightConstraint;

@property (nonatomic) BOOL executedViewDidLayoutSubviews;


- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];


    // First execution of viewDidLayoutSubviews?
    if(!self.executedViewDidLayoutSubviews){
        self.executedViewDidLayoutSubviews = YES;


        // Record some original dimensions
        self.minimalViewFullHeight = self.minimalView.bounds.size.height;


        // Setup our initial view configuration & let system know that 
        // constraints need to be updated.
        self.minimalViewHeightConstraint.constant = 0.0;
        [self.minimalView setNeedsUpdateConstraints];

        [self.topView layoutIfNeeded];
    }
}

Обрезать полный фрагмент действия

// An action to close our minimal view and show our normal (full) view
- (IBAction)resizeFullAction:(UIButton *)sender {

    [self.topView layoutIfNeeded];

    [UIView transitionWithView:self.minimalView
                  duration:1.0
                   options:UIViewAnimationOptionTransitionCrossDissolve
                animations:^{
                    self.minimalViewHeightConstraint.constant = 0.0;

                    // Following call to setNeedsUpdateConstraints may not be necessary
                    [self.minimalView setNeedsUpdateConstraints];

                    [self.topView layoutIfNeeded];

                } completion:^(BOOL finished) {
                    ;
                }];

    // Other code to show full view
    // ...
}

Уменьшить фрагмент небольшого действия

// An action to open our minimal view and hide our normal (full) view
- (IBAction)resizeSmallAction:(UIButton *)sender {

    [self.topView layoutIfNeeded];

    [UIView transitionWithView:self.minimalView
                  duration:1.0
                   options:UIViewAnimationOptionTransitionCrossDissolve
                animations:^{
                    self.minimalViewHeightConstraint.constant = self.minimalViewFullHeight;
                    [self.minimalView setNeedsUpdateConstraints];

                    [self.topView layoutIfNeeded];

                } completion:^(BOOL finished) {
                    ;
                }];

        // Other code to hide full view
        // ...
    }

Вы можете использовать animateWithDuration вместо transitionWithView, если хотите.

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