Дошка розкадрувань, кодекс, підказки та декілька Gotchas
Інші відповіді просто чудові, але цей підкреслює декілька досить важливих проблем анімаційних обмежень, використовуючи останній приклад. Я пережив багато варіацій, перш ніж зрозумів наступне:
Зробіть обмеження, які ви хочете націлити на змінні Class, щоб мати чітке посилання. У 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!
}()
Після декількох експериментів я зазначив , що необхідно отримати обмеження з точки зору ВИЩЕ (інакше SuperView) дві точки зору , де визначено обмеження. У наведеному нижче прикладі (як MNGStarRating, так і UIWebView - це два типи елементів, між якими я створюю обмеження, і вони є підпоглядом у межах самоперегляду).
Ланцюжок фільтрів
Я використовую метод фільтра Свіфта, щоб розділити бажане обмеження, яке буде служити точкою перегину. Можна також отримати набагато складніше, але фільтр робить хорошу роботу тут.
Обмеження анімації за допомогою 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. Іноді, коли нічого, мабуть, не відбувається, слід приховати деякі інші погляди або скористатися налагоджувачем виду, щоб знайти анімований погляд. Я навіть виявив випадки, коли визначений користувачем атрибут Runtime втрачався у xml розкадровки та призводив до того, що анімований вигляд охоплювався (під час роботи).
Завжди знайдіть хвилину, щоб прочитати документацію (нову та стару), швидку допомогу та заголовки. Apple продовжує вносити багато змін, щоб краще керувати обмеженнями AutoLayout (див. Подання стека). Або хоча б кулінарну книжку AutoLayout . Майте на увазі, що іноді найкращі рішення є у більш старій документації / відео.
Пограйте зі значеннями в анімації та подумайте про використання інших варіантів animateWithDuration.
Не жорстко кодуйте конкретні значення макета як критерії для визначення змін до інших констант, натомість використовуйте значення, що дозволяють визначити місце перегляду. CGRectContainsRect
є одним із прикладів
- Якщо потрібно, не використовуйте поля макета, пов’язані з представленням, який бере участь у визначенні обмеження
let viewMargins = self.webview.layoutMarginsGuide
: є, наприклад
- Не робіть роботи, якої вам не потрібно робити, усі перегляди з обмеженнями на аркуші розповідей мають обмеження, приєднані до властивості self.viewName.constraints
- Змініть свої пріоритети для будь-яких обмежень на менше 1000. Я встановив мій на 250 (низький) або 750 (високий) на дошці розкадрувань; (якщо ви спробуєте змінити пріоритет 1000 на що-небудь у коді, програма перестане працювати, оскільки потрібно 1000)
- Подумайте не відразу намагатися використовувати activateConstraints та deaktivictConstraints (вони мають своє місце, але коли ви просто навчаєтесь, або якщо ви використовуєте табло, використовуючи це, мабуть, означає, що ви робите занадто багато, у них є місце, хоча, як показано нижче)
- Не можна використовувати 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, швидше за все, нічого не станеться: у такому випадку у вас може бути напівзапечене рішення, як це:
Примітка - Знайдіть хвилину, щоб прочитати розділ Автоматичне розміщення нижче та оригінальний посібник. Існує спосіб використовувати ці методи, щоб доповнити свої динамічні аніматори.
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
}
}
Фрагмент з Посібника з автоматичного розкладу (зверніть увагу, другий фрагмент призначений для використання ОС X). BTW - Настільки, наскільки я бачу, це не в поточному посібнику. Кращі методи продовжують розвиватися.
Анімаційні зміни, зроблені автоматичною компоновкою
Якщо вам потрібен повний контроль над анімацією змін, внесених автоматичним розкладом, ви повинні змінити свої обмеження програмно. Основна концепція однакова і для 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 .
Зверніть пильну увагу
Часто в документації зустрічаються невеликі замітки або фрагменти коду, які призводять до більшої ідеї. Наприклад, прикріплення обмежень щодо автоматичного розміщення динамічних аніматорів є великою ідеєю.
Удачі і нехай Сила буде з тобою.