Как анимировать изменения ограничений?

Я обновляю старое приложение с помощью AdBannerView и когда нет объявления, он скользит с экрана. Когда есть объявление, оно скользит по экрану. Основные вещи.

старый стиль, я устанавливаю кадр в блоке анимации. Новый стиль, у меня есть IBOutlet к ограничению, которое определяет позицию Y, в этом случае это расстояние от нижней части супервизора и изменяет константу.

- (void)moveBannerOffScreen {
    [UIView animateWithDuration:5
             animations:^{
                          _addBannerDistanceFromBottomConstraint.constant = -32;
                     }];
    bannerIsVisible = FALSE;
}

- (void)moveBannerOnScreen {
    [UIView animateWithDuration:5
             animations:^{
                         _addBannerDistanceFromBottomConstraint.constant = 0;
             }];
    bannerIsVisible = TRUE;
}

и баннер движется, как и ожидалось, но нет анимация.

обновление: Я повторно смотрел WWDC12 видео"лучшие практики для освоения Auto Layout", который охватывает анимации. В нем обсуждается, как обновить ограничения с помощью CoreAnimation.

enter image description hereenter image description here

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

- (void)moveBannerOffScreen {
    _addBannerDistanceFromBottomConstraint.constant = -32;
    [UIView animateWithDuration:2
                     animations:^{
                         [self.view setNeedsLayout];
                     }];
    bannerIsVisible = FALSE;
}

- (void)moveBannerOnScreen {
    _addBannerDistanceFromBottomConstraint.constant = 0;
    [UIView animateWithDuration:2
                     animations:^{
                         [self.view setNeedsLayout];
                     }];
    bannerIsVisible = TRUE;
}

на боковой ноте я проверял много раз, и это выполняется в основном потоке.

13 ответов


две важные заметки:

  1. нужно позвонить layoutIfNeeded в блоке анимации. Apple фактически рекомендует вам вызвать его один раз перед блоком анимации, чтобы убедиться, что все ожидающие операции макета были завершены

  2. вам нужно вызвать его специально на Родительский вид (например,self.view), а не дочернее представление с привязанными к нему ограничениями. Это приведет к обновлению все ограничены взгляды, в том числе анимация и другие мнения, которые могут быть ограничены мнение о том, что вы изменили ограничение (например, вид б крепится к днищу зрения и вы только что изменили просмотра лучших офсетной и вы хотите посмотреть Б анимировать с ним)

попробуйте это:

С

- (void)moveBannerOffScreen {
    [self.view layoutIfNeeded];

    [UIView animateWithDuration:5
        animations:^{
            self._addBannerDistanceFromBottomConstraint.constant = -32;
            [self.view layoutIfNeeded]; // Called on parent view
        }];
    bannerIsVisible = FALSE;
}

- (void)moveBannerOnScreen { 
    [self.view layoutIfNeeded];

    [UIView animateWithDuration:5
        animations:^{
            self._addBannerDistanceFromBottomConstraint.constant = 0;
            [self.view layoutIfNeeded]; // Called on parent view
        }];
    bannerIsVisible = TRUE;
}

Swift 3

UIView.animate(withDuration: 5) {
    self._addBannerDistanceFromBottomConstraint.constant = 0
    self.view.layoutIfNeeded()
}

Я ценю предоставленный ответ, но я думаю, что было бы неплохо пойти немного дальше.

основная анимация блока из документации

[containerView layoutIfNeeded]; // Ensures that all pending layout operations have been completed
[UIView animateWithDuration:1.0 animations:^{
     // Make all constraint changes here
     [containerView layoutIfNeeded]; // Forces the layout of the subtree animation block and then captures all of the frame changes
}];

но на самом деле это очень упрощенный сценарий. Что делать, если я хочу анимировать ограничения подвида через updateConstraints способ?

блок анимации, который вызывает метод updateConstraints subviews

[self.view layoutIfNeeded];
[self.subView setNeedsUpdateConstraints];
[self.subView updateConstraintsIfNeeded];
[UIView animateWithDuration:1.0f delay:0.0f options:UIViewAnimationOptionLayoutSubviews animations:^{
    [self.view layoutIfNeeded];
} completion:nil];

метод updateConstraints переопределяется в подклассе UIView и должен вызовите super в конце метода.

- (void)updateConstraints
{
    // Update some constraints

    [super updateConstraints];
}

Руководство AutoLayout оставляет желать лучшего, но это стоит почитать. Я сам использую это как часть UISwitch это переключает подвида с парой UITextFields с простой и тонкой анимацией коллапса (длиной 0,2 секунды). Ограничения для подвида обрабатываются в методах updateConstraints подклассов UIView, как описано выше.


как правило, вам просто нужно обновить ограничения и вызвать layoutIfNeeded внутри блока анимации. Это может быть либо изменение .constant свойства NSLayoutConstraint, добавление ограничений удаления (iOS 7) или изменение .active свойство ограничений (iOS 8 & 9).

Пример Кода:

[UIView animateWithDuration:0.3 animations:^{
    // Move to right
    self.leadingConstraint.active = false;
    self.trailingConstraint.active = true;

    // Move to bottom
    self.topConstraint.active = false;
    self.bottomConstraint.active = true;

    // Make the animation happen
    [self.view setNeedsLayout];
    [self.view layoutIfNeeded];
}];

Пример Установки:

Xcode Project so sample animation project.

споры

есть некоторые вопросы о том, Должно ли ограничение быть изменено до блок анимации, или внутри it (см. предыдущие ответы).

ниже приведен разговор в Twitter между Мартином Пилкингтоном, который учит iOS, и Кеном Ферри, который написал Auto Layout. Кен объясняет, что, хотя изменение констант вне блока анимации в настоящее время может работа, это небезопасно, и они действительно должны быть изменены внутри анимация блок. https://twitter.com/kongtomorrow/status/440627401018466305

анимация:

Пример Проекта

вот простой проект, показывающий, как вид может быть анимирован. Он использует Objective C и анимирует представление, изменяя .active свойства нескольких ограничений. https://github.com/shepting/SampleAutoLayoutAnimation


// Step 1, update your constraint
self.myOutletToConstraint.constant = 50; // New height (for example)

// Step 2, trigger animation
[UIView animateWithDuration:2.0 animations:^{

    // Step 3, call layoutIfNeeded on your animated view's parent
    [self.view layoutIfNeeded];
}];

раскадровка, код, советы и несколько Gotchas

другие ответы просто прекрасны, но этот выделяет несколько довольно важных gotchas анимационных ограничений, используя недавний пример. Я прошел через множество вариаций, прежде чем понял следующее:

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

lazy var centerYInflection:NSLayoutConstraint = {
       let temp =  self.view.constraints.filter({ .firstItem is MNGStarRating }).filter ( { .secondItem is UIWebView }).filter({ .firstAttribute == .CenterY }).first
        return temp!
}()

после некоторых экспериментов я отметил, что нужно получить ограничение из представления выше (он же superview) два представления, в которых определено ограничение. В приведенном ниже примере (как MNGStarRating, так и UIWebView-это два типа элементов, между которыми я создаю ограничение, и они являются подвидами внутри self.вид.)

Фильтр Сцепления

я использую метод фильтра Swift для разделения желаемого ограничения, которое будет служить точкой перегиба. Можно сделать гораздо сложнее, но фильтр делает хорошую работу здесь.

Анимация Ограничений С Помощью Swift

Nota Bene-этот пример является раскадровкой / решением кода и предполагает один из них сделал ограничения по умолчанию в раскадровке. Тогда можно анимация изменений с помощью кода.

предполагая, что вы создаете свойство для фильтрации с точными критериями и добираетесь до определенной точки перегиба для вашей анимации (конечно, вы могли бы также фильтр для массива и цикл, Если вам нужно несколько ограничений):

lazy var centerYInflection:NSLayoutConstraint = {
    let temp =  self.view.constraints.filter({ .firstItem is MNGStarRating }).filter ( { .secondItem is UIWebView }).filter({ .firstAttribute == .CenterY }).first
    return temp!
}()

....

когда-нибудь потом...

@IBAction func toggleRatingView (sender:AnyObject){

    let aPointAboveScene = -(max(UIScreen.mainScreen().bounds.width,UIScreen.mainScreen().bounds.height) * 2.0)

    self.view.layoutIfNeeded()


    //Use any animation you want, I like the bounce in springVelocity...
    UIView.animateWithDuration(1.0, delay: 0.0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.75, options: [.CurveEaseOut], animations: { () -> Void in

        //I use the frames to determine if the view is on-screen
        if CGRectContainsRect(self.view.frame, self.ratingView.frame) {

            //in frame ~ animate away
            //I play a sound to give the animation some life

            self.centerYInflection.constant = aPointAboveScene
            self.centerYInflection.priority = UILayoutPriority(950)

        } else {

            //I play a different sound just to keep the user engaged
            //out of frame ~ animate into scene
            self.centerYInflection.constant = 0
            self.centerYInflection.priority = UILayoutPriority(950)
            self.view.setNeedsLayout()
            self.view.layoutIfNeeded()
         }) { (success) -> Void in

            //do something else

        }
    }
}

много неправильных поворотов

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

  1. следите за zPositioning. Иногда, когда ничего не очевидно происходит, ты должен спрятаться. некоторые из других представлений или использовать представление отладчик для поиска анимированного представления. Я даже нашел случаи, когда пользовательская среда выполнения Атрибут был потерян в xml раскадровки и привел к анимированным просмотр покрывается (во время работы).

  2. всегда уделяйте минуту, чтобы прочитать документацию (новую и старую), быстро Помощь и заголовки. Компания Apple продолжает делать много изменений к лучшему управление ограничения Autolayout (см. Вид стека). Или хотя бы режим Поваренной. Имейте в виду, что иногда лучшие решения в старой документации/видео.

  3. поиграйте со значениями в анимации и рассмотреть возможность использования другие варианты animateWithDuration.

  4. не hardcode конкретные значения макета в качестве критериев для определения вместо изменений других констант используйте значения, которые позволяют определите расположение представления. CGRectContainsRect один пример

  5. если необходимо, не стесняйтесь использовать поля макета, связанные с представление, участвующее в определении ограничения let viewMargins = self.webview.layoutMarginsGuide: на примере
  6. не делайте работу, которую вам не нужно делать, все представления с ограничениями на раскадровка имеет ограничения, прикрепленные к свойству личность.viewName.ограничения
  7. измените приоритеты для любых ограничений на менее 1000. Я mine to 250 (low) или 750 (high) на раскадровке; (если вы попытаетесь изменить приоритет 1000 на что-либо в код, то приложение будет сбой, потому что 1000 требуется)
  8. рассмотрите не сразу попытку использовать activateConstraints и deactivateConstraints (у них есть свое место, но когда вы только учитесь или используете раскадровку, используя их, вероятно, означает, что вы делаете слишком много ~ у них есть место, как показано ниже)
  9. не используйте addConstraints / removeConstraints, если вы не действительно добавление нового ограничения в коде. Я обнаружил, что в большинстве случаев я компоновка мнений в раскадровке с желаемыми ограничениями (размещение вид за кадром), затем в коде я анимирую ограничения, ранее созданные в раскадровке, чтобы переместить вид.
  10. я потратил много впустую времени на создание ограничений с новым Класс и подклассы NSAnchorLayout. Они работают просто отлично, но это мне потребовалось время, чтобы понять, что все ограничения, которые мне были нужны. уже существовал в раскадровке. Если вы создаете ограничения в коде тогда, безусловно, используйте этот метод, чтобы агрегируйте свои ограничения:

быстрый образец решений, чтобы избежать при использовании раскадровки

private var _nc:[NSLayoutConstraint] = []
    lazy var newConstraints:[NSLayoutConstraint] = {

        if !(self._nc.isEmpty) {
            return self._nc
        }

        let viewMargins = self.webview.layoutMarginsGuide
        let minimumScreenWidth = min(UIScreen.mainScreen().bounds.width,UIScreen.mainScreen().bounds.height)

        let centerY = self.ratingView.centerYAnchor.constraintEqualToAnchor(self.webview.centerYAnchor)
        centerY.constant = -1000.0
        centerY.priority = (950)
        let centerX =  self.ratingView.centerXAnchor.constraintEqualToAnchor(self.webview.centerXAnchor)
        centerX.priority = (950)

        if let buttonConstraints = self.originalRatingViewConstraints?.filter({

            (.firstItem is UIButton || .secondItem is UIButton )
        }) {
            self._nc.appendContentsOf(buttonConstraints)

        }

        self._nc.append( centerY)
        self._nc.append( centerX)

        self._nc.append (self.ratingView.leadingAnchor.constraintEqualToAnchor(viewMargins.leadingAnchor, constant: 10.0))
        self._nc.append (self.ratingView.trailingAnchor.constraintEqualToAnchor(viewMargins.trailingAnchor, constant: 10.0))
        self._nc.append (self.ratingView.widthAnchor.constraintEqualToConstant((minimumScreenWidth - 20.0)))
        self._nc.append (self.ratingView.heightAnchor.constraintEqualToConstant(200.0))

        return self._nc
    }()

если вы забудете один из этих советов или более простые, такие как, где добавить layoutIfNeeded, скорее всего, ничего не произойдет: в этом случае у вас может быть наполовину испеченное решение, как это:

NB - найдите время, чтобы прочитать раздел AutoLayout ниже и оригинальное руководство. Есть способ использовать их методы дополнения ваши динамические аниматоры.

UIView.animateWithDuration(1.0, delay: 0.0, usingSpringWithDamping: 0.3, initialSpringVelocity: 1.0, options: [.CurveEaseOut], animations: { () -> Void in

            //
            if self.starTopInflectionPoint.constant < 0  {
                //-3000
                //offscreen
                self.starTopInflectionPoint.constant = self.navigationController?.navigationBar.bounds.height ?? 0
                self.changeConstraintPriority([self.starTopInflectionPoint], value: UILayoutPriority(950), forView: self.ratingView)

            } else {

                self.starTopInflectionPoint.constant = -3000
                 self.changeConstraintPriority([self.starTopInflectionPoint], value: UILayoutPriority(950), forView: self.ratingView)
            }

        }) { (success) -> Void in

            //do something else
        }

    }

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

анимация изменений, внесенных Auto Layout

Если вам нужен полный контроль над анимацией изменений, сделанных Auto Layout, вы должны сделать свое ограничение изменения программно. Основная концепция одинакова для iOS и OS X, но есть несколько незначительных различий.

в приложении iOS ваш код будет выглядеть примерно так:

[containerView layoutIfNeeded]; // Ensures that all pending layout operations have been completed
[UIView animateWithDuration:1.0 animations:^{
     // Make all constraint changes here
     [containerView layoutIfNeeded]; // Forces the layout of the subtree animation block and then captures all of the frame changes
}];

в OS X используйте следующий код при использовании анимации с поддержкой слоев:

[containterView layoutSubtreeIfNeeded];
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
     [context setAllowsImplicitAnimation: YES];
     // Make all constraint changes here
     [containerView layoutSubtreeIfNeeded];
}];

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

[[constraint animator] setConstant:42];

для тех, кто учится лучше визуально проверить это рано видео от Apple.

Обратите Пристальное Внимание

часто в документации есть небольшие заметки или фрагменты кода, которые приводят к большим идеям. Например, прикрепление ограничений автоматической компоновки к динамическим аниматорам-большая идея.

удачи и пусть сила будет с вами.


Swift 4 решение

наследник UIView.одушевить

три простых шага:

  1. изменить ограничения, например:

    heightAnchor.constant = 50
    
  2. рассказать, содержащей view что его макет загрязнен и что автозапуск должен пересчитать макет:

    self.view.setNeedsLayout()
    
  3. в блоке анимации скажите макету пересчитать макет, который эквивалентно установке кадров напрямую (в этом случае autolayout установит кадры):

    UIView.animate(withDuration: 0.5) {
        self.view.layoutIfNeeded()
    }
    

выполнить простейший пример:

heightAnchor.constant = 50
self.view.setNeedsLayout()
UIView.animate(withDuration: 0.5) {
    self.view.layoutIfNeeded()
}

Sidenote

существует дополнительный 0-й шаг-перед изменением ограничений, которые вы можете вызвать self.view.layoutIfNeeded() чтобы убедиться, что отправной точкой для анимации является состояние со старыми ограничениями (в случае изменения некоторых других ограничений это не должно быть включено в анимацию):

otherConstraint.constant = 30
// this will make sure that otherConstraint won't be animated but will take effect immediately
self.view.layoutIfNeeded()

heightAnchor.constant = 50
self.view.setNeedsLayout()
UIView.animate(withDuration: 0.5) {
    self.view.layoutIfNeeded()
}

UIViewPropertyAnimator

так как с iOS 10 мы получили новый анимационный механизм -UIViewPropertyAnimator, мы должны знать, что в основном тот же механизм применяется к нему. Шаги в основном одинаковы:

heightAnchor.constant = 50
self.view.setNeedsLayout()
let animator = UIViewPropertyAnimator(duration: 0.5, timingParameters: UICubicTimingParameters(animationCurve: .linear))
animator.addAnimations {
    self.view.layoutIfNeeded()
}
animator.startAnimation()

С animator является инкапсуляцией анимации, мы можем сохранить ссылку на нее и вызвать ее позже. Однако, так как в блоке анимации мы просто говорим autolayout чтобы пересчитать кадры, мы должны изменить ограничения перед вызовом startAnimation. Поэтому возможно нечто подобное:

// prepare the animator first and keep a reference to it
let animator = UIViewPropertyAnimator(duration: 0.5, timingParameters: UICubicTimingParameters(animationCurve: .linear))
animator.addAnimations {
    self.view.layoutIfNeeded()
}

// at some other point in time we change the constraints and call the animator
heightAnchor.constant = 50
self.view.setNeedsLayout()
animator.startAnimation()

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

кроме того, помните, что один аниматор не многоразовые - один раз вы запускаете его, вы не можете "перезапустить" его. Так что я думаю, что на самом деле нет веской причины держать аниматора рядом, если мы не используем его для управления интерактивной анимацией.


быстрое решение:

yourConstraint.constant = 50
UIView.animateWithDuration(1, animations: yourView.layoutIfNeeded)

Рабочее Решение 100% Swift 3.1

Я прочитал все ответы и хочу поделиться кодом и иерархией строк, которые я использовал во всех моих приложениях, чтобы правильно их анимировать, некоторые решения здесь не работают, вы должны проверить их на более медленных устройствах e.g iPhone 5 на данный момент.

self.view.layoutIfNeeded() // Force lays of all subviews on root view
UIView.animate(withDuration: 0.5) { [weak self] in // allowing to ARC to deallocate it properly
       self?.tbConstraint.constant = 158 // my constraint constant change
       self?.view.layoutIfNeeded() // Force lays of all subviews on root view again.
}

Я пытался оживить ограничения и было не очень легко найти хорошее объяснение.

то, что говорят другие ответы, совершенно верно: вам нужно позвонить [self.view layoutIfNeeded]; внутри animateWithDuration: animations:. Однако другим важным моментом является наличие указателей для каждого NSLayoutConstraint вы хотите анимировать.

Я создал пример в GitHub.


рабочее и только что протестированное решение для Swift 3 с Xcode 8.3.3:

self.view.layoutIfNeeded()
self.calendarViewHeight.constant = 56.0

UIView.animate(withDuration: 0.5, delay: 0.0, options: UIViewAnimationOptions.curveEaseIn, animations: {
        self.view.layoutIfNeeded()
    }, completion: nil)

просто имейте в виду, что само.calendarViewHeight это ограничение относится к пользовательские (CalendarView). Я позвонил .layoutIfNeeded() на себя.вид и не на себя.calendarView

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


есть статья об этом: http://weblog.invasivecode.com/post/42362079291/auto-layout-and-core-animation-auto-layout-was

в котором он закодировал так:

- (void)handleTapFrom:(UIGestureRecognizer *)gesture {
    if (_isVisible) {
        _isVisible = NO;
        self.topConstraint.constant = -44.;    // 1
        [self.navbar setNeedsUpdateConstraints];  // 2
        [UIView animateWithDuration:.3 animations:^{
            [self.navbar layoutIfNeeded]; // 3
        }];
    } else {
        _isVisible = YES;
        self.topConstraint.constant = 0.;
        [self.navbar setNeedsUpdateConstraints];
        [UIView animateWithDuration:.3 animations:^{
            [self.navbar layoutIfNeeded];
        }];
    }
}

надеюсь, что это помогает.


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

ограничение определило верхнее пространство от текстового поля до верхней части контейнера. При открытии клавиатуры я просто делю константу на 2.

Я не смог добиться conistent плавное ограничение анимации непосредственно в уведомлении клавиатуры. Примерно в половине случаев view просто прыгнул бы на его новая позиция-без анимации.

мне пришло в голову, что в результате открытия клавиатуры может произойти дополнительная разметка. Добавление простого блока dispatch_after с задержкой 10 мс заставило анимацию работать каждый раз-без прыжков.


для других Xamarians, вот Xamarin.версия iOS / c#:

UIView.Animate(5, () =>
{
    _addBannerDistanceFromBottomConstraint.Constant = 0;
    View.LayoutIfNeeded();
});