Come posso animare le modifiche ai vincoli?


959

Sto aggiornando una vecchia app con un AdBannerViewe quando non c'è pubblicità, scorre fuori dallo schermo. Quando c'è un annuncio, questo scorre sullo schermo. Roba di base.

Vecchio stile, ho impostato la cornice in un blocco di animazione. Nuovo stile, ho un IBOutletvincolo al layout automatico che determina la Yposizione, in questo caso è la distanza dalla parte inferiore della superview e modifica la costante:

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

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

E il banner si sposta, esattamente come previsto, ma nessuna animazione.


AGGIORNAMENTO: ho rivisto il WWDC 12 per parlare delle migliori pratiche per la padronanza del layout automatico che copre l'animazione. Descrive come aggiornare i vincoli usando CoreAnimation :

Ho provato con il seguente codice, ma ottengo gli stessi esatti risultati:

- (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;
}

In una nota a margine, ho verificato numerose volte e questo viene eseguito sul thread principale .


11
Non ho mai visto così tanti voti offerti per una domanda e una risposta su un refuso su SO prima
circa

3
Se è presente un refuso nella risposta, è necessario modificare la risposta. Ecco perché sono modificabili.
i_am_jorf,

@jeffamaphone - Sarebbe più utile se facessi notare l'errore di battitura, così sapevo dove fosse l'errore. Puoi modificare tu stesso la risposta e correggere l'errore di battitura salvando tutti gli altri dalla nostra diatriba. L'ho appena modificato per rimuovere la costante dal blocco animazione, se è quello a cui ti riferivi.
DBD

1
Non so quale sia il refuso. Stavo rispondendo ai commenti sopra.
i_am_jorf,

9
Quindi l'errore di battitura è la domanda. Stupidamente stavo scrivendo "setNeedsLayout" invece di "layoutIfNeeded". Viene mostrato chiaramente nella mia domanda quando taglio e incollo il mio codice con l'errore e gli screenshot con il comando corretto. Eppure sembra non accorgersene fino a quando qualcuno non lo indica.
DBD

Risposte:


1697

Due note importanti:

  1. È necessario chiamare layoutIfNeededall'interno del blocco animazione. Apple in realtà consiglia di chiamarlo una volta prima del blocco animazione per assicurarsi che tutte le operazioni di layout in sospeso siano state completate

  2. È necessario chiamarlo specificamente nella vista padre (ad es. self.view), Non nella vista figlio che ha i vincoli ad essa associati. In questo modo si aggiorneranno tutte le viste vincolate, inclusa l'animazione di altre viste che potrebbero essere vincolate alla vista di cui è stato modificato il vincolo (ad esempio, la vista B è collegata alla parte inferiore della vista A e si è appena modificata l'offset superiore della vista A e si desidera la vista B animare con esso)

Prova questo:

Objective-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;
}

Swift 3

UIView.animate(withDuration: 5) {
    self._addBannerDistanceFromBottomConstraint.constant = 0
    self.view.layoutIfNeeded()
}

200
Sai cosa ... la tua risposta funziona. Il WWDC funziona ... la mia visione fallisce. Per qualche motivo mi ci è voluta una settimana per capire che stavo chiamando setNeedsLayoutinvece di layoutIfNeeded. Sono leggermente inorridito da quante ore ho trascorso senza notare che ho appena digitato il nome del metodo sbagliato.
DBD

23
La soluzione funziona ma non è necessario modificare la costante del vincolo all'interno del blocco animazione. Va benissimo impostare il vincolo una volta prima di dare il via all'animazione. Dovresti modificare la tua risposta.
Ortwin Gentz,

74
Inizialmente non ha funzionato per me e poi ho capito che è necessario chiamare layoutIfNeeded nella vista PARENT, non nella vista a cui si applicano i vincoli.
Oliver Pearmain,

20
usando layoutIfNeeded animerà tutti gli aggiornamenti della subview non solo la modifica del vincolo. come animare solo la modifica del vincolo?
ngb,

11
"In realtà Apple consiglia di chiamarlo una volta prima del blocco animazione per assicurarsi che tutte le operazioni di layout in sospeso siano state completate", grazie, non ci ho mai pensato, ma ha senso.
Rick van der Linde,

109

Apprezzo la risposta fornita, ma penso che sarebbe bello prenderla un po 'oltre.

L'animazione del blocco di base dalla documentazione

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

ma in realtà questo è uno scenario molto semplicistico. Cosa succede se desidero animare i vincoli di subview tramite il updateConstraintsmetodo?

Un blocco di animazione che chiama il metodo updateConstraints delle visualizzazioni secondarie

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

Il metodo updateConstraints viene sovrascritto nella sottoclasse UIView e deve chiamare super alla fine del metodo.

- (void)updateConstraints
{
    // Update some constraints

    [super updateConstraints];
}

La Guida al layout automatico lascia molto a desiderare, ma vale la pena leggerla. UISwitchPersonalmente sto usando questo come parte di un che attiva / disattiva una sottoview con una coppia di UITextFields con un'animazione di compressione semplice e sottile (durata 0,2 secondi). I vincoli per la sottoview vengono gestiti nei metodi updateConstraints delle sottoclassi di UIView come descritto sopra.


Quando si chiamano tutti i metodi sopra su self.view(e non su una vista secondaria di quella vista), la chiamata updateConstraintsIfNeedednon è necessaria (perché setNeedsLayoutattiva anche updateConstraintsquella vista). Potrebbe essere banale per la maggior parte, ma non è stato per me fino ad ora;)
anneblue

È difficile commentare senza conoscere la completa interazione di Autolayout e layout di molle e montanti nel tuo caso particolare. Sto parlando interamente di uno scenario di autolayout puro. Sarei curioso di sapere cosa sta succedendo esattamente nel metodo layoutSubviews.
Cameron Lowell Palmer,

translatesAutoresizingMaskIntoConstraints = NO; Se si desidera eseguire il puro autolayout, è necessario disabilitarlo definitivamente.
Cameron Lowell Palmer,

Non l'ho visto, ma non posso davvero parlare del problema senza un po 'di codice. Forse potresti deridere un TableView e incollarlo su GitHub?
Cameron Lowell Palmer,

Spiacente, non uso gitHub :(
anneblue il

74

In genere, è sufficiente aggiornare i vincoli e chiamare layoutIfNeededall'interno del blocco animazione. Ciò può essere la modifica della .constantproprietà di un NSLayoutConstraint, l'aggiunta di rimuovere i vincoli (iOS 7) o la modifica della .activeproprietà dei vincoli (iOS 8 e 9).

Codice di esempio:

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

Esempio di installazione:

Progetto Xcode quindi progetto di animazione di esempio.

Controversia

Vi sono alcune domande sul fatto che il vincolo debba essere modificato prima del blocco animazione o al suo interno (vedere le risposte precedenti).

Quella che segue è una conversazione su Twitter tra Martin Pilkington che insegna iOS e Ken Ferry che ha scritto Auto Layout. Ken spiega che sebbene la modifica delle costanti al di fuori del blocco animazione al momento possa funzionare, non è sicuro e dovrebbero davvero essere modificate all'interno del blocco animazione. https://twitter.com/kongtomorrow/status/440627401018466305

Animazione:

Progetto di esempio

Ecco un semplice progetto che mostra come animare una vista. Utilizza l'Obiettivo C e anima la vista modificando la .activeproprietà di numerosi vincoli. https://github.com/shepting/SampleAutoLayoutAnimation


+1 per mostrare un esempio usando il nuovo flag attivo. Inoltre, cambiare il vincolo al di fuori del blocco dell'animazione mi è sempre sembrato un trucco
Korey Hinton,

Ho sollevato un problema in github perché questa soluzione non funziona per riportare la vista dove era. Credo che cambiare attivo non sia la soluzione corretta e dovresti invece cambiare la priorità. Impostare la parte superiore su 750 e la parte inferiore su 250, quindi nel codice si alternano tra UILayoutPriorityDefaultHigh e UILayoutPriorityDefaultLow.
Malhal,

Un altro motivo per l'aggiornamento all'interno del blocco è che isola le modifiche dal codice chiamante che possono capitare anche di chiamare layoutIfNeeded. (e grazie per il link Twitter)
Chris Conover,

1
Bella risposta. Soprattutto perché menzioni la conversazione di Martin e Ken su questo.
Jona,

Sono confuso sul modo di aggiornare i vincoli e ho scritto questo . Potete per favore dare un'occhiata?
Miele

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

29

Soluzione Swift 4

UIView.animate

Tre semplici passaggi:

  1. Modifica i vincoli, ad esempio:

    heightAnchor.constant = 50
  2. Indica al contenitore viewche il suo layout è sporco e che il layout automatico dovrebbe ricalcolare il layout:

    self.view.setNeedsLayout()
  3. Nel blocco animazione dire al layout di ricalcolare il layout, che equivale a impostare direttamente i frame (in questo caso il layout automatico imposterà i frame):

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

Completa esempio più semplice:

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

Nota a margine

C'è un passaggio facoltativo 0: prima di modificare i vincoli che potresti voler chiamare self.view.layoutIfNeeded()per assicurarti che il punto di partenza per l'animazione sia dallo stato con vecchi vincoli applicati (nel caso in cui vi fossero altri cambiamenti di vincoli che non dovrebbero essere inclusi nell'animazione ):

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

Dato che con iOS 10 abbiamo ottenuto un nuovo meccanismo di animazione UIViewPropertyAnimator, dovremmo sapere che sostanzialmente lo stesso meccanismo si applica ad esso. I passaggi sono sostanzialmente gli stessi:

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

Poiché animatorè un incapsulamento dell'animazione, possiamo tenerne riferimento e chiamarlo in seguito. Tuttavia, poiché nel blocco di animazione diciamo solo al completamento automatico di ricalcolare i frame, dobbiamo cambiare i vincoli prima di chiamare startAnimation. Pertanto è possibile qualcosa del genere:

// 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()

L'ordine di modifica dei vincoli e l'avvio di un animatore è importante: se cambiamo i vincoli e lasciamo il nostro animatore per qualche punto successivo, il prossimo ciclo di ridisegno può invocare il ricalcolo del trasferimento automatico e la modifica non verrà animata.

Inoltre, ricorda che un singolo animatore non è riutilizzabile: una volta eseguito, non è possibile "eseguirlo nuovamente". Quindi immagino che non ci sia davvero una buona ragione per tenere l'animatore in giro, a meno che non lo utilizziamo per controllare un'animazione interattiva.


Funziona alla grande anche con
Swift

layoutIfNeeded () è la chiave
Medhi

15

Storyboard, codice, suggerimenti e alcuni Gotcha

Le altre risposte vanno bene, ma questa mette in evidenza alcuni aspetti piuttosto importanti dei vincoli di animazione usando un esempio recente. Ho attraversato molte varianti prima di realizzare quanto segue:

Rendere i vincoli che si desidera targetizzare nelle variabili di classe per contenere un riferimento forte. In Swift ho usato variabili pigre:

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!
}()

Dopo un po 'di sperimentazione ho notato che uno DEVE ottenere il vincolo dalla vista SOPRA (alias la supervista) le due viste in cui è definito il vincolo. Nell'esempio seguente (sia MNGStarRating che UIWebView sono i due tipi di elementi tra cui sto creando un vincolo e sono sottoview in self.view).

Concatenamento del filtro

Approfitto del metodo di filtro di Swift per separare il vincolo desiderato che fungerà da punto di flesso. Uno potrebbe anche diventare molto più complicato ma il filtro fa un buon lavoro qui.

Vincoli animati usando Swift

Nota Bene: questo esempio è la soluzione storyboard / codice e presuppone che uno abbia creato vincoli predefiniti nello storyboard. È quindi possibile animare le modifiche utilizzando il codice.

Supponendo che tu crei una proprietà da filtrare con criteri precisi e arrivi a un punto di flesso specifico per l'animazione (ovviamente puoi anche filtrare un array e passare in rassegna se hai bisogno di più vincoli):

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!
}()

....

Qualche tempo dopo...

@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

        }
    }
}

Le molte svolte sbagliate

Queste note sono davvero una serie di suggerimenti che ho scritto per me stesso. Ho fatto tutto ciò che non era personale e doloroso. Spero che questa guida possa risparmiare gli altri.

  1. Fai attenzione a zPosition. A volte quando apparentemente non succede nulla, dovresti nascondere alcune delle altre viste o utilizzare il debugger delle viste per individuare la vista animata. Ho anche trovato casi in cui un attributo di runtime definito dall'utente è stato perso nell'xml di uno storyboard e ha portato alla copertura della vista animata (durante il lavoro).

  2. Prenditi sempre un minuto per leggere la documentazione (vecchia e nuova), la Guida rapida e le intestazioni. Apple continua a apportare molte modifiche per gestire meglio i vincoli di AutoLayout (vedi visualizzazioni di stack). O almeno il ricettario AutoLayout . Tieni presente che a volte le soluzioni migliori si trovano nella documentazione / nei video precedenti.

  3. Gioca con i valori nell'animazione e considera l'utilizzo di altre varianti di animateWithDuration.

  4. Non codificare valori di layout specifici come criteri per determinare le modifiche ad altre costanti, utilizzare invece valori che consentono di determinare la posizione della vista. CGRectContainsRectè un esempio

  5. Se necessario, non esitare a utilizzare i margini del layout associati a una vista che partecipa alla definizione del vincolo let viewMargins = self.webview.layoutMarginsGuide: è ad esempio
  6. Non fare lavori che non devi fare, tutte le viste con vincoli sullo storyboard hanno vincoli collegati alla proprietà self.viewName.constraints
  7. Cambia le tue priorità per eventuali vincoli a meno di 1000. Ho impostato il mio su 250 (basso) o 750 (alto) sullo storyboard; (se provi a cambiare una priorità di 1000 in qualcosa nel codice, l'app andrà in crash perché è necessario 1000)
  8. Considera di non provare immediatamente a usare activConstraints e disableateConstraints (hanno il loro posto ma quando stai imparando o se stai usando uno storyboard usando questi probabilmente significa che stai facendo troppo ~ hanno un posto anche se come visto di seguito)
  9. Considera di non utilizzare addConstraints / removeConstraints a meno che tu non stia davvero aggiungendo un nuovo vincolo nel codice. Ho scoperto che la maggior parte delle volte layout le viste nello storyboard con i vincoli desiderati (posizionando la vista fuori dallo schermo), quindi nel codice, animo i vincoli precedentemente creati nello storyboard per spostare la vista.
  10. Ho trascorso molto tempo sprecato a costruire vincoli con la nuova classe e sottoclassi NSAnchorLayout. Funzionano bene, ma mi ci è voluto un po 'per capire che tutti i vincoli di cui avevo bisogno esistevano già nello storyboard. Se si creano vincoli nel codice, sicuramente utilizzare questo metodo per aggregare i vincoli:

Esempio rapido di soluzioni da EVITARE quando si utilizzano gli storyboard

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
    }()

Se dimentichi uno di questi suggerimenti o quelli più semplici come dove aggiungere il layout Se necessario, molto probabilmente non accadrà nulla: nel qual caso potresti avere una soluzione a metà cottura come questa:

NB - Prenditi un momento per leggere la sezione AutoLayout in basso e la guida originale. C'è un modo per usare queste tecniche per integrare i tuoi animatori dinamici.

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 dalla Guida di AutoLayout (nota che il secondo frammento è per l'utilizzo di OS X). A proposito, questo non è più nella guida attuale per quanto posso vedere. Le tecniche preferite continuano ad evolversi.

Animazione delle modifiche apportate dal layout automatico

Se è necessario il controllo completo sull'animazione delle modifiche apportate dal layout automatico, è necessario apportare modifiche ai vincoli a livello di codice. Il concetto di base è lo stesso per iOS e OS X, ma ci sono alcune differenze minori.

In un'app iOS, il tuo codice sarebbe simile al seguente:

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

In OS X, utilizzare il codice seguente quando si utilizzano animazioni supportate da layer:

[containterView layoutSubtreeIfNeeded];
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
     [context setAllowsImplicitAnimation: YES];
     // Make all constraint changes here
     [containerView layoutSubtreeIfNeeded];
}];

Quando non si utilizzano animazioni supportate da livelli, è necessario animare la costante utilizzando l'animatore del vincolo:

[[constraint animator] setConstant:42];

Per coloro che imparano meglio visivamente dai un'occhiata a questo primo video di Apple .

Fai molta attenzione

Spesso nella documentazione ci sono piccole note o pezzi di codice che portano a idee più grandi. Ad esempio, collegare vincoli di layout automatico agli animatori dinamici è una grande idea.

Buona fortuna e che la Forza sia con te.


12

Soluzione rapida:

yourConstraint.constant = 50
UIView.animate(withDuration: 1.0, animations: {
    yourView.layoutIfNeeded
})

7

Soluzione di lavoro rapida al 100% 3.1

ho letto tutte le risposte e voglio condividere il codice e la gerarchia delle linee che ho usato in tutte le mie applicazioni per animarle correttamente, alcune soluzioni qui non funzionano, in questo momento dovresti controllarle su dispositivi più lenti, ad esempio iPhone 5.

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

4

Stavo cercando di animare i vincoli e non è stato davvero facile trovare una buona spiegazione.

Ciò che altre risposte stanno dicendo è totalmente vero: devi chiamare [self.view layoutIfNeeded];dentro animateWithDuration: animations:. Tuttavia, l'altro punto importante è avere puntatori per ogniNSLayoutConstraint cosa che si desidera animare.

Ho creato un esempio in GitHub .


4

Soluzione funzionante e appena testata per Swift 3 con 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)

Basta tenere presente che self.calendarViewHeight è un vincolo riferito a un customView (CalendarView). Ho chiamato .layoutIfNeeded () su self.view e NON su self.calendarView

Spero che questo aiuto.


3

C'è un articolo che parla di questo: http://weblog.invasivecode.com/post/42362079291/auto-layout-and-core-animation-auto-layout-was

In cui, ha codificato in questo modo:

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

Spero che sia d'aiuto.


1

Nel contesto dell'animazione del vincolo, vorrei menzionare una situazione specifica in cui ho animato immediatamente un vincolo all'interno di una notifica aperta da tastiera.

Il vincolo ha definito uno spazio superiore da un campo di testo all'inizio del contenitore. All'apertura della tastiera, divido semplicemente la costante per 2.

Non sono stato in grado di ottenere un'animazione uniforme e vincolata direttamente all'interno della notifica da tastiera. Circa la metà delle volte la vista passerebbe semplicemente alla sua nuova posizione, senza animare.

Mi è venuto in mente che potrebbero esserci dei layout aggiuntivi in ​​seguito all'apertura della tastiera. L'aggiunta di un semplice blocco dispatch_after con un ritardo di 10ms ha fatto correre l'animazione ogni volta, senza saltare.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.