Jak animować zmiany wiązań?

959

Aktualizuję starą aplikację, AdBannerViewa gdy nie ma reklamy, zsuwa się z ekranu. Gdy pojawia się reklama, przesuwa się po ekranie. Podstawowe rzeczy.

W starym stylu ustawiłem ramkę w bloku animacji. W nowym stylu mam ograniczenie IBOutletdo automatycznego układu, które określa Ypozycję, w tym przypadku jest to odległość od dolnej części superview i modyfikuję stałą:

- (void)moveBannerOffScreen {
    [UIView animateWithDuration:5 animations:^{
        _addBannerDistanceFromBottomConstraint.constant = -32;
    }];
    bannerIsVisible = FALSE;
}

- (void)moveBannerOnScreen {
    [UIView animateWithDuration:5 animations:^{
        _addBannerDistanceFromBottomConstraint.constant = 0;
    }];
    bannerIsVisible = TRUE;
}

Baner przesuwa się dokładnie zgodnie z oczekiwaniami, ale nie ma animacji.


AKTUALIZACJA: Ponownie obejrzałem wykład WWDC 12 Najlepsze praktyki w zakresie masteringu auto-układu, który obejmuje animację. Omówiono, jak zaktualizować ograniczenia za pomocą CoreAnimation :

Próbowałem z następującym kodem, ale uzyskałem dokładnie takie same wyniki:

- (void)moveBannerOffScreen {
    _addBannerDistanceFromBottomConstraint.constant = -32;
    [UIView animateWithDuration:2 animations:^{
        [self.view setNeedsLayout];
    }];
    bannerIsVisible = FALSE;
}

- (void)moveBannerOnScreen {
    _addBannerDistanceFromBottomConstraint.constant = 0;
    [UIView animateWithDuration:2 animations:^{
        [self.view setNeedsLayout];
    }];
    bannerIsVisible = TRUE;
}

Na marginesie, sprawdzałem wiele razy i jest to wykonywane w głównym wątku.

DBD
źródło
11
Nigdy wcześniej nie widziałem tylu głosów na pytanie i odpowiedź na literówkę w SO
abbood
3
Jeśli w odpowiedzi jest literówka, należy edytować odpowiedź. Dlatego można je edytować.
i_am_jorf
@jeffamaphone - Przydałoby się literówka, więc wiedziałbym, gdzie jest błąd. Możesz samodzielnie edytować odpowiedź i naprawić literówkę, oszczędzając wszystkim innym naszą diatrybę. Właśnie go edytowałem, aby usunąć stałą z bloku animacji, jeśli o to ci chodziło.
DBD
1
Nie wiem, co to jest literówka. Odpowiedziałem na powyższe komentarze.
i_am_jorf
9
Zatem literówka jest pytaniem. Głupio pisałem „setNeedsLayout” zamiast „layoutIfNeeded”. Jest to wyraźnie widoczne w moim pytaniu, gdy wycinam i wklejam mój kod z błędem, a zrzuty ekranu z prawidłowym poleceniem. Jednak nie mógł tego zauważyć, dopóki ktoś tego nie zauważył.
DBD

Odpowiedzi:

1696

Dwie ważne uwagi:

  1. Musisz zadzwonić layoutIfNeededw obrębie bloku animacji. Apple zaleca, aby wywołać go raz przed blokiem animacji, aby upewnić się, że wszystkie oczekujące operacje układu zostały zakończone

  2. Musisz wywołać go konkretnie w widoku nadrzędnym (np. self.view), A nie w widoku podrzędnym, który ma związane z nim ograniczenia. Spowoduje to zaktualizowanie wszystkich ograniczonych widoków, w tym animację innych widoków, które mogą być ograniczone do widoku, dla którego zmieniono ograniczenie (np. Widok B jest przymocowany do dolnej części widoku A, a właśnie zmieniłeś górne przesunięcie widoku A i chcesz widok B animować go)

Spróbuj tego:

Cel C

- (void)moveBannerOffScreen {
    [self.view layoutIfNeeded];

    [UIView animateWithDuration:5
        animations:^{
            self._addBannerDistanceFromBottomConstraint.constant = -32;
            [self.view layoutIfNeeded]; // Called on parent view
        }];
    bannerIsVisible = FALSE;
}

- (void)moveBannerOnScreen { 
    [self.view layoutIfNeeded];

    [UIView animateWithDuration:5
        animations:^{
            self._addBannerDistanceFromBottomConstraint.constant = 0;
            [self.view layoutIfNeeded]; // Called on parent view
        }];
    bannerIsVisible = TRUE;
}

Szybki 3

UIView.animate(withDuration: 5) {
    self._addBannerDistanceFromBottomConstraint.constant = 0
    self.view.layoutIfNeeded()
}
g3rv4
źródło
199
Wiesz, co ... twoja odpowiedź działa. WWDC działa ... moja wizja zawodzi. Z jakiegoś powodu zajęło mi tydzień uświadomienie sobie, że dzwonię setNeedsLayoutzamiast layoutIfNeeded. Jestem trochę przerażony, ile godzin spędziłem, nie zauważając, że właśnie wpisałem niewłaściwą nazwę metody.
DBD
23
Rozwiązanie działa, ale nie trzeba zmieniać stałej wiązania w bloku animacji. Całkowicie dobrze jest ustawić ograniczenie raz przed rozpoczęciem animacji. Powinieneś edytować swoją odpowiedź.
Ortwin Gentz
74
Na początku nie działało to dla mnie, a potem zdałem sobie sprawę, że musisz wywołać layoutIfNeeded w widoku PARENT, a nie widoku, którego dotyczą ograniczenia.
Oliver Pearmain
20
użycie layoutIfNeeded będzie animować wszystkie odświeżenia widoku podrzędnego, a nie tylko zmianę ograniczenia. jak animować tylko zmianę ograniczenia?
ngb
11
„Apple naprawdę zaleca, aby wywołać go raz przed blokiem animacji, aby upewnić się, że wszystkie oczekujące operacje układu zostały zakończone”, dziękuję, nigdy o tym nie myślałem, ale ma to sens.
Rick van der Linde
109

Doceniam udzieloną odpowiedź, ale myślę, że byłoby miło posunąć się nieco dalej.

Podstawowa animacja bloku z dokumentacji

[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
}];

ale tak naprawdę jest to bardzo uproszczony scenariusz. Co zrobić, jeśli chcę animować ograniczenia widoków cząstkowych za pomocą tej updateConstraintsmetody?

Blok animacji, który wywołuje metodę podrzędności updateConstraints

[self.view layoutIfNeeded];
[self.subView setNeedsUpdateConstraints];
[self.subView updateConstraintsIfNeeded];
[UIView animateWithDuration:1.0f delay:0.0f options:UIViewAnimationOptionLayoutSubviews animations:^{
    [self.view layoutIfNeeded];
} completion:nil];

Metoda updateConstraints jest zastępowana w podklasie UIView i musi wywoływać super na końcu metody.

- (void)updateConstraints
{
    // Update some constraints

    [super updateConstraints];
}

Przewodnik AutoLayout pozostawia wiele do życzenia, ale warto je przeczytać. Sam używam tego jako elementu UISwitchprzełączającego widok podrzędny za pomocą pary UITextFields z prostą i subtelną animacją zwinięcia (o długości 0,2 sekundy). Ograniczenia dla widoku podrzędnego są obsługiwane w metodach updateConstraints podklasy UIView, jak opisano powyżej.

Cameron Lowell Palmer
źródło
Podczas wywoływania wszystkich powyższych metod w self.view(a nie w widoku podrzędnym tego widoku) wywołanie updateConstraintsIfNeedednie jest konieczne (ponieważ setNeedsLayoutrównież wyzwalacze updateConstraintstego widoku). Może to być najbardziej trywialne, ale do tej pory nie było dla mnie;)
anneblue
Trudno jest komentować, nie wiedząc o pełnej grze Autolayout oraz układu sprężyn i rozpórek w konkretnym przypadku. Mówię w całości o czysto automatycznym scenariuszu. Byłbym ciekawy, co dokładnie dzieje się w twojej metodzie layoutSubviews.
Cameron Lowell Palmer
translatesAutoresizingMaskIntoConstraints = NO; Jeśli chcesz mieć wyłącznie autoukład, zdecydowanie powinieneś to wyłączyć.
Cameron Lowell Palmer,
Nie widziałem tego, ale tak naprawdę nie mogę mówić o tym problemie bez jakiegoś kodu. Może mógłbyś naśladować TableView i nakleić go na GitHub?
Cameron Lowell Palmer
Niestety nie używam gitHub :(
anneblue
74

Zasadniczo wystarczy zaktualizować ograniczenia i wywołać layoutIfNeededwewnątrz bloku animacji. Może to być zmiana .constantwłaściwości NSLayoutConstraint, dodanie ograniczeń usuwania (iOS 7) lub zmiana.active właściwości ograniczeń (iOS 8 i 9).

Przykładowy kod:

[UIView animateWithDuration:0.3 animations:^{
    // Move to right
    self.leadingConstraint.active = false;
    self.trailingConstraint.active = true;

    // Move to bottom
    self.topConstraint.active = false;
    self.bottomConstraint.active = true;

    // Make the animation happen
    [self.view setNeedsLayout];
    [self.view layoutIfNeeded];
}];

Przykładowa konfiguracja:

Projekt Xcode, więc przykładowy projekt animacji.

Spór

Istnieją pytania dotyczące tego, czy ograniczenie należy zmienić przed blokiem animacji, czy w środku nim (patrz poprzednie odpowiedzi).

Poniżej znajduje się rozmowa na Twitterze między Martinem Pilkingtonem, który uczy iOS, a Kenem Ferrym, który napisał Auto Layout. Ken wyjaśnia, że ​​chociaż zmienianie stałych poza blokiem animacji może obecnie działać, nie jest to bezpieczne i naprawdę powinny być zmieniane w bloku animacji. https://twitter.com/kongtomorrow/status/440627401018466305

Animacja:

Przykładowy projekt

Oto prosty projekt pokazujący, jak można animować widok. Wykorzystuje Cel C i animuje widok, zmieniając .activewłaściwość kilku ograniczeń. https://github.com/shepting/SampleAutoLayoutAnimation

Steven Hepting
źródło
+1 za pokazanie przykładu przy użyciu nowej aktywnej flagi. Również zmiana ograniczenia poza blokiem animacji zawsze była dla mnie jak hack
Korey Hinton
Podniosłem problem w github, ponieważ to rozwiązanie nie działa w celu przeniesienia widoku z powrotem do miejsca, w którym był. Uważam, że zmiana active nie jest właściwym rozwiązaniem i zamiast tego powinieneś zmienić priorytet. Ustaw górny na 750, a dolny na 250, a następnie w kodzie na przemian między UILayoutPriorityDefaultHigh i UILayoutPriorityDefaultLow.
malhal 11.01.16
Innym powodem aktualizacji wewnątrz bloku jest to, że izoluje on zmiany od kodu wywołującego, które mogą się zdarzyć również w przypadku wywołania layoutIfNeeded. (i dzięki za link na Twitterze)
Chris Conover
1
Świetna odpowiedź. Zwłaszcza, że ​​wspominasz o tym rozmowę Martina i Kena.
Jona,
Jestem zdezorientowany co do sposobu aktualizacji ograniczeń i napisałem to . Czy możesz spojrzeć?
Honey,
36
// Step 1, update your constraint
self.myOutletToConstraint.constant = 50; // New height (for example)

// Step 2, trigger animation
[UIView animateWithDuration:2.0 animations:^{

    // Step 3, call layoutIfNeeded on your animated view's parent
    [self.view layoutIfNeeded];
}];
John Erck
źródło
29

Rozwiązanie Swift 4

UIView.animate

Trzy proste kroki:

  1. Zmień ograniczenia, np .:

    heightAnchor.constant = 50
  2. Poinformuj zawierający, viewże jego układ jest brudny i że autoukład powinien ponownie obliczyć układ:

    self.view.setNeedsLayout()
  3. W bloku animacji powiedz układowi, aby ponownie obliczył układ, co jest równoznaczne z bezpośrednim ustawieniem ramek (w tym przypadku autolayout ustawi klatki):

    UIView.animate(withDuration: 0.5) {
        self.view.layoutIfNeeded()
    }

Kompletny najprostszy przykład:

heightAnchor.constant = 50
self.view.setNeedsLayout()
UIView.animate(withDuration: 0.5) {
    self.view.layoutIfNeeded()
}

Dygresja

Istnieje opcjonalny krok zerowy - przed zmianą ograniczeń możesz zadzwonić, self.view.layoutIfNeeded()aby upewnić się, że punkt początkowy animacji pochodzi ze stanu ze stosowanymi starymi ograniczeniami (w przypadku, gdy wystąpiły inne zmiany ograniczeń, które nie powinny być uwzględnione w animacji ):

otherConstraint.constant = 30
// this will make sure that otherConstraint won't be animated but will take effect immediately
self.view.layoutIfNeeded()

heightAnchor.constant = 50
self.view.setNeedsLayout()
UIView.animate(withDuration: 0.5) {
    self.view.layoutIfNeeded()
}

UIViewPropertyAnimator

Ponieważ w iOS 10 mamy nowy mechanizm animacji - UIViewPropertyAnimatorpowinniśmy wiedzieć, że zasadniczo ten sam mechanizm ma do niego zastosowanie. Kroki są w zasadzie takie same:

heightAnchor.constant = 50
self.view.setNeedsLayout()
let animator = UIViewPropertyAnimator(duration: 0.5, timingParameters: UICubicTimingParameters(animationCurve: .linear))
animator.addAnimations {
    self.view.layoutIfNeeded()
}
animator.startAnimation()

Ponieważ animatorjest to enkapsulacja animacji, możemy zachować odniesienie do niej i wywołać ją później. Ponieważ jednak w bloku animacji po prostu mówimy autoukładowi, aby ponownie obliczył klatki, musimy zmienić ograniczenia przed wywołaniem startAnimation. Dlatego możliwe jest coś takiego:

// prepare the animator first and keep a reference to it
let animator = UIViewPropertyAnimator(duration: 0.5, timingParameters: UICubicTimingParameters(animationCurve: .linear))
animator.addAnimations {
    self.view.layoutIfNeeded()
}

// at some other point in time we change the constraints and call the animator
heightAnchor.constant = 50
self.view.setNeedsLayout()
animator.startAnimation()

Kolejność zmiany wiązań i uruchomienia animatora jest ważna - jeśli po prostu zmienimy więzy i zostawimy naszego animatora na jakiś późniejszy moment, następny cykl przerysowania może wywołać ponowne obliczenie autoukładu, a zmiana nie będzie animowana.

Pamiętaj też, że pojedynczy animator nie nadaje się do ponownego użycia - po uruchomieniu nie można go „ponownie uruchomić”. Sądzę więc, że nie ma naprawdę dobrego powodu, aby trzymać animatora w pobliżu, chyba że użyjemy go do sterowania animacją interaktywną.

Milan Nosáľ
źródło
👍 działa też świetnie w swift 5
spnkr
layoutIfNeeded () jest kluczem
Medhi
15

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.

  1. 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).

  2. 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.

  3. Baw się z wartościami w animacji i rozważ użycie innych wariantów animateWithDuration.

  4. 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. CGRectContainsRectjest jednym przykładem

  5. 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
  6. 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
  7. 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)
  8. 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)
  9. 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.
  10. 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ą.

Tommie C.
źródło
11

Szybkie rozwiązanie:

yourConstraint.constant = 50
UIView.animate(withDuration: 1.0, animations: {
    yourView.layoutIfNeeded
})
Nazariy Vlizlo
źródło
6

Roztwór roboczy 100% Swift 3.1

Przeczytałem wszystkie odpowiedzi i chcę udostępnić kod i hierarchię wierszy, których użyłem we wszystkich moich aplikacjach, aby animować je poprawnie. Niektóre rozwiązania tutaj nie działają, powinieneś je sprawdzić na wolniejszych urządzeniach, np. iPhone 5 w tym momencie.

self.view.layoutIfNeeded() // Force lays of all subviews on root view
UIView.animate(withDuration: 0.5) { [weak self] in // allowing to ARC to deallocate it properly
       self?.tbConstraint.constant = 158 // my constraint constant change
       self?.view.layoutIfNeeded() // Force lays of all subviews on root view again.
}
C0mrade
źródło
4

Próbowałem animować Ograniczenia i nie było łatwo znaleźć dobre wytłumaczenie.

To, co mówią inne odpowiedzi, jest całkowicie prawdziwe: musisz zadzwonić do [self.view layoutIfNeeded];środka animateWithDuration: animations:. Inną ważną kwestią jest jednak posiadanie wskaźników dla każdego, NSLayoutConstraintktóry chcesz animować.

Stworzyłem przykład w GitHub .

Gabriel.Massana
źródło
4

Działające i właśnie przetestowane rozwiązanie dla Swift 3 z Xcode 8.3.3:

self.view.layoutIfNeeded()
self.calendarViewHeight.constant = 56.0

UIView.animate(withDuration: 0.5, delay: 0.0, options: UIViewAnimationOptions.curveEaseIn, animations: {
        self.view.layoutIfNeeded()
    }, completion: nil)

Pamiętaj tylko, że self.calendarViewHeight jest ograniczeniem odnoszącym się do customView (CalendarView). Nazwałem .layoutIfNeeded () na self.view, a NIE na self.calendarView

Mam nadzieję, że to pomoże.

Edoardo Vicoli
źródło
3

Istnieje artykuł na ten temat: http://weblog.invasivecode.com/post/42362079291/auto-layout-and-core-animation-auto-layout-was

W którym napisał tak:

- (void)handleTapFrom:(UIGestureRecognizer *)gesture {
    if (_isVisible) {
        _isVisible = NO;
        self.topConstraint.constant = -44.;    // 1
        [self.navbar setNeedsUpdateConstraints];  // 2
        [UIView animateWithDuration:.3 animations:^{
            [self.navbar layoutIfNeeded]; // 3
        }];
    } else {
        _isVisible = YES;
        self.topConstraint.constant = 0.;
        [self.navbar setNeedsUpdateConstraints];
        [UIView animateWithDuration:.3 animations:^{
            [self.navbar layoutIfNeeded];
        }];
    }
}

Mam nadzieję, że to pomoże.

DD
źródło
1

W kontekście animacji ograniczenia chciałbym wspomnieć o konkretnej sytuacji, w której animowałem ograniczenie natychmiast w powiadomieniu o otwarciu klawiatury.

Ograniczenie definiowało górną przestrzeń od pola tekstowego do góry kontenera. Po otwarciu klawiatury dzielę stałą przez 2.

Nie byłem w stanie uzyskać spójnej animacji płynnego wiązania bezpośrednio w powiadomieniu z klawiatury. Około połowy razy widok po prostu przeskoczyłby na nową pozycję - bez animacji.

Przyszło mi do głowy, że w wyniku otwarcia klawiatury mogą wystąpić dodatkowe układy. Dodanie prostego bloku dispatch_after z opóźnieniem 10 ms sprawiło, że animacja była uruchamiana za każdym razem - bez przeskakiwania.

ivanferdelja
źródło