UIView animateWithDuration Анимация представлений Auto-Layout за пределами блока анимации

В разное время в своем приложении я замечал, что когда я вызываю UIView.animateWithDuration, если у меня есть какие-либо другие запросы автоматического макета или даже что-то столь же простое, как изменение константы NSLayoutConstraint, эти изменения также будут анимированы, даже если они НЕ находятся внутри блока анимации. Например: у меня есть UIViewController, который сразу после вызова viewDidAppear добавит изображение, выбранное пользователем, к UIScrollView, чтобы его можно было изменить и отредактировать. Я работаю над добавлением небольшого демонстрационного представления с несколькими GIF-файлами, чтобы помочь пользователю при первом использовании приложения. Как только представление отобразится, оно должно добавить изображение (сначала я установил его так, чтобы оно соответствовало их экрану) в scrollView, а затем анимировать demonstrationView alpha от нуля до 1.0. Проблема, с которой я сталкиваюсь, заключается в том, что хотя моя функция добавления и изменения размера изображения assignImageChosen вызывается перед animateWithDuration и находится за пределами блока анимации, изменения ограничений размера также анимируются.

override func viewDidAppear(animated: Bool) {

    if let image = startImage {
        self.assignImageChosen(image)
        currentlyEditing = true
    }

    if preLoadGame {

        UIView.animateWithDuration(1.0, delay: 0.5, options: UIViewAnimationOptions.CurveEaseInOut, animations: {


            self.demonstrationView.alpha = 1.0
            self.view.layoutIfNeeded()

            }, completion: nil)

    }

}

Вот функция assignImageChosen:

func assignImageChosen(image: UIImage) {

    var ratio:CGFloat!
    let screen:CGFloat = max(self.scrollView.frame.height, self.scrollView.frame.width)
    var maxImage:CGFloat!
    if UIDevice.currentDevice().orientation.isPortrait {
        if image.size.height >= image.size.width {
            maxImage = max(image.size.height, image.size.width)
        }
        else {
            maxImage = min(image.size.height, image.size.width)
        }
    }
    else {
        if image.size.width >= image.size.height {
            maxImage = max(image.size.height, image.size.width)
        }
        else {
            maxImage = min(image.size.height, image.size.width)
        }
    }

    if maxImage > screen {
        ratio = screen/maxImage
    }
    else {
        ratio = 1.0
    }

    self.imageView.image = image
    self.imageHeight.constant = image.size.height * ratio
    self.scrollView.contentSize.height = image.size.height * ratio
    self.imageWidth.constant = image.size.width * ratio
    self.scrollView.contentSize.width = image.size.width * ratio

}

Переменные imageHeight и imageWidth являются переменными NSLayoutConstraint, а imageView — это переменные UIImageView, для которых они установлены. Поэтому, как только я изменяю изображение, я изменяю ограничения, чтобы изображение внутри UIScrollView подходило для экрана. Проблема в том, что когда я делаю это таким образом, даже если изменения вызываются вне блока анимации, эти изменения все равно анимируются.

Вот пример GIF того, что я пытаюсь (плохо) передать словами:

проблема

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

override func viewDidAppear(animated: Bool) {

    if let image = startImage {
        self.assignImageChosen(image)
        currentlyEditing = true
    }

    if preLoadGame {

        let delay = 0.5 * Double(NSEC_PER_SEC)
        let popTime = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
        dispatch_after(popTime, dispatch_get_main_queue(), {
            UIView.animateWithDuration(1.0, delay: 0, options: UIViewAnimationOptions.CurveEaseInOut, animations: {


                self.demonstrationView.alpha = 1.0
                self.view.layoutIfNeeded()

                }, completion: nil)
        })

    }

}

Поэтому вместо того, чтобы использовать переменную delay внутри функции animateWithDuration, я задержу выполнение функции. Это работает, но я думаю, что должен быть лучший способ сделать это! Почему эти изменения макета анимируются, если они не находятся внутри блока анимации? Как я могу обойти это надлежащим и эффективным образом?

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

с хаком

Я ценю любую помощь с этой неприятной проблемой!


person Pierce    schedule 19.10.2016    source источник


Ответы (1)


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

К счастью, решение кажется таким простым, как можно было бы надеяться, вызвать layoutIfNeeded перед выполнением анимации, прежде чем вызывать его снова в блоке. Согласно этому ответу и другим, это на самом деле официальная рекомендация Apple, хотя ссылка, которую они предоставляют на apple.com, не работает.

(Кстати, что касается вашего хака, есть несколько случаев, когда вам нужно разнести изменения после того, как UIKit завершит свой макет макета, например, чтобы сделать что-то в UITableView после завершения reloadData. «Правильный хак» - это сделать UIViewAnimation, затем в его блоке завершения выполните вторую анимацию, сохранив ее последовательность в UIKit.)

person Mike Sand    schedule 26.10.2016
comment
Вау спасибо! Я не могу поверить, насколько невероятно простым было решение. Эта проблема периодически беспокоила меня в течение нескольких месяцев! Всегда горько-сладко, когда ответ такой простой: приятно знать, что это так легко исправить, и неприятно осознавать, что ты потратил столько времени впустую на что-то такое простое! Спасибо еще раз - person Pierce; 27.10.2016