Раскадровка, код, советы и несколько ошибок
Другие ответы просто хороши, но этот выдвигает на первый план несколько довольно важных моментов анимации ограничений. Я прошел через множество вариаций, прежде чем понял следующее:
Установите ограничения, на которые вы хотите ориентироваться, в переменные класса, чтобы они содержали сильную ссылку. В Swift я использовал ленивые переменные:
lazy var centerYInflection:NSLayoutConstraint = {
let temp = self.view.constraints.filter({ $0.firstItem is MNGStarRating }).filter ( { $0.secondItem is UIWebView }).filter({ $0.firstAttribute == .CenterY }).first
return temp!
}()
После некоторого эксперимента я заметил, что один ДОЛЖЕН получить ограничение из представления НАД (или суперпредставление) двух представлений, где ограничение определено. В приведенном ниже примере (MNGStarRating и UIWebView - это два типа элементов, между которыми я создаю ограничение, и они являются подпредставлениями в self.view).
Цепочка фильтров
Я использую метод фильтра Свифта, чтобы отделить желаемое ограничение, которое будет служить точкой перегиба. Можно также сделать намного более сложным, но фильтр делает хорошую работу здесь.
Анимация ограничений с помощью Swift
Nota Bene - Этот пример является решением раскадровки / кода и предполагает, что в раскадровке были установлены ограничения по умолчанию. Затем можно анимировать изменения с помощью кода.
Предполагая, что вы создаете свойство для фильтрации с точными критериями и получаете конкретную точку перегиба для вашей анимации (конечно, вы также можете фильтровать массив и проходить через него, если вам нужно несколько ограничений):
lazy var centerYInflection:NSLayoutConstraint = {
let temp = self.view.constraints.filter({ $0.firstItem is MNGStarRating }).filter ( { $0.secondItem is UIWebView }).filter({ $0.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
}
}
}
Много неправильных поворотов
Эти заметки действительно набор советов, которые я написал для себя. Я сделал все, что не делал лично и мучительно. Надеюсь, это руководство может пощадить других.
Остерегайтесь zPosition. Иногда, когда ничего не происходит, вы должны скрыть некоторые другие виды или использовать отладчик, чтобы найти анимированный вид. Я даже обнаружил случаи, когда определяемый пользователем атрибут времени выполнения терялся в xml раскадровки и приводил к покрытию анимированного представления (во время работы).
Всегда уделяйте минуту чтению документации (новой и старой), быстрой справки и заголовков. Apple продолжает вносить множество изменений, чтобы лучше управлять ограничениями AutoLayout (см. Стековые представления). Или, по крайней мере, поваренная книга AutoLayout . Имейте в виду, что иногда лучшие решения находятся в старой документации / видео.
Поиграйте со значениями в анимации и рассмотрите возможность использования других вариантов animateWithDuration.
Не указывайте конкретные значения макета в качестве критерия для определения изменений других констант, вместо этого используйте значения, которые позволяют вам определить местоположение представления. CGRectContainsRect
это один пример
- При необходимости не стесняйтесь использовать поля макета, связанные с представлением, участвующим в определении ограничения
let viewMargins = self.webview.layoutMarginsGuide
: это на примере
- Не выполняйте работу, которую вам не нужно делать, все представления с ограничениями на раскадровке имеют ограничения, связанные с свойством self.viewName.constraints
- Измените ваши приоритеты для любых ограничений на менее чем 1000. Я установил мой на 250 (низкий) или 750 (высокий) на раскадровке; (если вы попытаетесь изменить приоритет 1000 на что-либо в коде, приложение завершится сбоем, потому что требуется 1000)
- Не стоит сразу пытаться использовать activConstraints и deactivateConstraints (они имеют свое место, но когда вы просто учитесь или если вы используете раскадровку, используя их, вероятно, это означает, что вы делаете слишком много - у них действительно есть место, хотя, как показано ниже)
- Не используйте addConstraints / removeConstraints, если вы действительно не добавляете новое ограничение в коде. Я обнаружил, что в большинстве случаев я размещаю представления в раскадровке с желаемыми ограничениями (помещая представление за пределы экрана), затем в коде я анимирую ограничения, ранее созданные в раскадровке, для перемещения представления.
- Я потратил много времени на создание ограничений с новым классом и подклассами 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({
($0.firstItem is UIButton || $0.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, вы должны внести свои ограничения программно. Основная концепция одинакова как для 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 .
Обратить особое внимание
Часто в документации есть небольшие заметки или фрагменты кода, которые приводят к большим идеям. Например, присоединение ограничений автоматического размещения к динамическим аниматорам - большая идея.
Удачи и да пребудет с тобой Сила.