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.
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).
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.
Gioca con i valori nell'animazione e considera l'utilizzo di altre varianti di animateWithDuration.
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
- 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
- Non fare lavori che non devi fare, tutte le viste con vincoli sullo storyboard hanno vincoli collegati alla proprietà self.viewName.constraints
- 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)
- 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)
- 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.
- 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.