Scenorys, Kod, Wskazówki i kilka Gotch
Inne odpowiedzi są w porządku, ale ta uwypukla kilka dość ważnych problemów z animowaniem ograniczeń na ostatnim przykładzie. Przejrzałem wiele odmian, zanim zdałem sobie sprawę z następujących rzeczy:
Wprowadź ograniczenia, na które chcesz kierować, do zmiennych klasy, aby mieć mocne odniesienie. W Swift użyłem leniwych zmiennych:
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!
}()
Po niektórych eksperymentach zauważyłem, że MUSI uzyskać ograniczenie z widoku POWYŻEJ (czyli superview) dwa widoki, w których ograniczenie jest zdefiniowane. W poniższym przykładzie (zarówno MNGStarRating, jak i UIWebView to dwa typy elementów, między którymi tworzę ograniczenie, i są to widoki podrzędne w ramach self.view).
Łańcuch filtrów
Korzystam z metody filtrowania Swifta, aby oddzielić pożądane ograniczenie, które będzie służyć jako punkt przegięcia. Można się również komplikować, ale filtr robi tutaj niezłą robotę.
Animowanie ograniczeń za pomocą Swift
Nota Bene - ten przykład jest rozwiązaniem scenorysowym / kodowym i zakłada, że wprowadzono domyślne ograniczenia w scenorysie. Następnie można animować zmiany za pomocą kodu.
Zakładając, że utworzysz właściwość do filtrowania według dokładnych kryteriów i dojdziesz do określonego punktu przegięcia dla swojej animacji (oczywiście możesz również filtrować tablicę i przejść przez pętlę, jeśli potrzebujesz wielu ograniczeń):
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!
}()
…
Jakiś czas później...
@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
}
}
}
Wiele złych zwrotów
Te notatki to tak naprawdę zestaw wskazówek, które sam dla siebie napisałem. Zrobiłem wszystkie zakazy osobiście i boleśnie. Mam nadzieję, że ten przewodnik może oszczędzić innym.
Uważaj na zPozycjonowanie. Czasami, gdy najwyraźniej nic się nie dzieje, należy ukryć niektóre inne widoki lub użyć debugera widoków, aby zlokalizować widok animowany. Znalazłem nawet przypadki, w których atrybut Runtime User Defined został utracony w pliku XML scenariusza i doprowadził do zasłonięcia widoku animowanego (podczas pracy).
Zawsze poświęć chwilę na przeczytanie dokumentacji (nowej i starej), szybkiej pomocy i nagłówków. Apple ciągle wprowadza wiele zmian, aby lepiej zarządzać ograniczeniami AutoLayout (patrz widoki stosów). Lub przynajmniej książka kucharska AutoLayout . Pamiętaj, że czasami najlepsze rozwiązania znajdują się w starszej dokumentacji / filmach.
Baw się z wartościami w animacji i rozważ użycie innych wariantów animateWithDuration.
Nie wpisuj konkretnych wartości układu jako kryteriów określania zmian w innych stałych, zamiast tego używaj wartości, które pozwalają określić lokalizację widoku. CGRectContainsRect
jest jednym przykładem
- W razie potrzeby nie wahaj się użyć marginesów układu związanych z widokiem uczestniczącym w definicji ograniczenia
let viewMargins = self.webview.layoutMarginsGuide
: na przykład
- Nie wykonuj pracy, której nie musisz wykonywać, wszystkie widoki z ograniczeniami w serii ujęć mają ograniczenia związane z właściwością self.viewName.constraints
- Zmień swoje priorytety dla jakichkolwiek ograniczeń na mniej niż 1000. Ustawiam mój na 250 (niski) lub 750 (wysoki) na scenorysie; (jeśli spróbujesz zmienić priorytet 1000 na dowolny kod, aplikacja ulegnie awarii, ponieważ 1000 jest wymagane)
- Zastanów się, czy nie od razu próbujesz użyć aktywacji Ograniczeń i dezaktywacji Ograniczeń (mają swoje miejsce, ale podczas nauki lub jeśli używasz scenorysu, prawdopodobnie oznacza to, że robisz za dużo ~ mają miejsce, jak pokazano poniżej)
- Zastanów się, czy nie używać addConstraints / removeConstraints, chyba że naprawdę dodajesz nowe ograniczenie w kodzie. Odkryłem, że większość razy układam widoki w serii ujęć z pożądanymi ograniczeniami (umieszczając widok poza ekranem), a następnie w kodzie, animuję ograniczenia wcześniej utworzone w serii ujęć, aby przesuwać widok.
- Spędziłem dużo czasu na budowaniu ograniczeń dzięki nowej klasie i podklasom NSAnchorLayout. Działa to dobrze, ale zajęło mi trochę czasu, aby zdać sobie sprawę, że wszystkie ograniczenia, których potrzebowałem, istniały już w scenorysie. Jeśli budujesz ograniczenia w kodzie, z pewnością użyj tej metody do agregacji ograniczeń:
Szybka próbka rozwiązań, których należy UNIKAĆ podczas używania Storyboardów
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
}()
Jeśli zapomnisz jedną z tych wskazówek lub prostsze, na przykład, gdzie dodać układIfNeeded, najprawdopodobniej nic się nie wydarzy: W takim przypadku możesz mieć na wpół upieczone rozwiązanie:
NB - Poświęć chwilę, aby przeczytać sekcję AutoLayout poniżej i oryginalny przewodnik. Istnieje sposób na wykorzystanie tych technik w celu uzupełnienia dynamicznych animatorów.
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
}
}
Snippet z Przewodnika AutoLayout (zwróć uwagę, że drugi snippet dotyczy systemu OS X). BTW - O ile mi wiadomo, nie ma go w bieżącym przewodniku. Preferowane techniki nadal ewoluują.
Animowanie zmian dokonanych przez układ automatyczny
Jeśli potrzebujesz pełnej kontroli nad animowanymi zmianami wprowadzonymi przez Automatyczny układ, musisz wprowadzić zmiany ograniczenia programowo. Podstawowa koncepcja jest taka sama dla iOS i OS X, ale istnieje kilka drobnych różnic.
W aplikacji na iOS kod wyglądałby mniej więcej tak:
[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
}];
W systemie OS X użyj następującego kodu podczas korzystania z animacji z warstwami:
[containterView layoutSubtreeIfNeeded];
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
[context setAllowsImplicitAnimation: YES];
// Make all constraint changes here
[containerView layoutSubtreeIfNeeded];
}];
Jeśli nie korzystasz z animacji wspieranych warstwami, musisz animować stałą za pomocą animatora ograniczenia:
[[constraint animator] setConstant:42];
Dla tych, którzy lepiej się uczą, sprawdź ten wczesny film od Apple .
Zwrócić szczególną uwagę
Często w dokumentacji są małe notatki lub fragmenty kodu, które prowadzą do większych pomysłów. Na przykład dołączanie ograniczeń automatycznego układu do dynamicznych animatorów to świetny pomysł.
Powodzenia i niech Moc będzie z tobą.