Раскадровка, код, советы и несколько замечаний
Другие ответы хороши, но этот подчеркивает несколько довольно важных подводных камней анимации ограничений на недавнем примере. Я прошел через множество вариантов, прежде чем понял следующее:
Сделайте ограничения, на которые вы хотите нацелить, в переменные класса, чтобы они содержали сильную ссылку. В 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, чтобы отделить желаемое ограничение, которое будет точкой перегиба. Можно было бы также сделать намного более сложным, но фильтр здесь неплохо справляется.
Анимация ограничений с помощью 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
}
}
}
Много неправильных поворотов
Эти заметки на самом деле представляют собой набор советов, которые я написал для себя. Я лично и болезненно все делал. Надеюсь, это руководство пощадит других.
Остерегайтесь zPositioning. Иногда, когда очевидно, что ничего не происходит, вам следует скрыть некоторые другие представления или использовать отладчик представлений, чтобы найти ваше анимированное представление. Я даже встречал случаи, когда определяемый пользователем атрибут времени выполнения терялся в xml раскадровки и приводил к закрытию анимированного представления (во время работы).
Всегда найдите минутку, чтобы прочитать документацию (новую и старую), Быструю помощь и заголовки. Apple продолжает вносить множество изменений, чтобы лучше управлять ограничениями AutoLayout (см. Представления стека). Или, по крайней мере, Поваренная книга AutoLayout. Имейте в виду, что иногда лучшие решения находятся в более старой документации / видео.
Поиграйте со значениями в анимации и рассмотрите возможность использования других вариантов animateWithDuration.
Не указывайте жестко определенные значения макета в качестве критериев для определения изменений других констант, вместо этого используйте значения, которые позволяют вам определять местоположение представления. CGRectContainsRect
- один из примеров
- Если необходимо, не стесняйтесь использовать поля макета, связанные с представлением, участвующим в определении ограничения
let viewMargins = self.webview.layoutMarginsGuide
: в примере
- Не выполняйте ненужную работу, все представления с ограничениями на раскадровке имеют ограничения, привязанные к свойству self.viewName.constraints
- Измените свои приоритеты для любых ограничений на менее 1000. Я установил свои приоритеты на 250 (низкий) или 750 (высокий) на раскадровке; (если вы попытаетесь изменить приоритет 1000 на что-либо в коде, приложение выйдет из строя, потому что требуется 1000)
- Не пытайтесь сразу использовать activateConstraints и 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.
Обратить особое внимание
Часто в документации есть небольшие примечания или фрагменты кода, которые приводят к более масштабным идеям. Например, прикрепление ограничений автоматической компоновки к динамическим аниматорам - отличная идея.
Удачи и да пребудет с вами Сила.
person
Tommie C.
schedule
11.12.2015