Quando posso attivare / disattivare i vincoli di layout?


104

Ho impostato più set di vincoli in IB e mi piacerebbe passare da uno all'altro a livello di codice a seconda di uno stato. Esiste una constraintsAraccolta outlet, tutte contrassegnate come installate da IB e una constraintsBraccolta outlet, tutte disinstallate in IB.

Posso passare a livello di programmazione tra i due set in questo modo:

NSLayoutConstraint.deactivateConstraints(constraintsA)
NSLayoutConstraint.activateConstraints(constraintsB)

Ma ... non riesco a capire quando farlo. Sembra che dovrei essere in grado di farlo una volta dentro viewDidLoad, ma non riesco a farlo funzionare. Ho provato a chiamare view.updateConstraints()e view.layoutSubviews()dopo aver impostato i vincoli, ma senza alcun risultato.

Ho scoperto che se imposto i vincoli in viewDidLayoutSubviewstutto funziona come previsto. Credo che mi piacerebbe sapere due cose ...

  1. Perché ricevo questo comportamento?
  2. È possibile attivare / disattivare i vincoli da viewDidLoad?

2
Vuoi dire che il disableConstraints e attivareConstraints funzionavano in viewWillLayoutSubviews? L'ho provato e non ha funzionato lì o in viewDidLoad. In un certo senso ha funzionato in viewDidAppear; la vista appariva dove avrebbero dovuto metterla i nuovi vincoli, ma se ruotavo in orizzontale, la vista tornava alla posizione determinata dai vincoli impostati in IB (e rimaneva lì quando ho ruotato di nuovo in verticale). Registrando i vincoli, ha mostrato quelli corretti (quelli appena attivati). Questo mi sembra un bug.
rdelmar

1
Sì, erano validi (hanno funzionato in viewDidAppear) e non è necessario chiamare super perché non esiste un'implementazione predefinita di viewWillLayoutSubviews (l'ho provato comunque chiamando super, ma non ha fatto differenza).
rdelmar

1
@rdelmar Ho appena avuto la possibilità di testare di più ... posso verificare di aver effettivamente ottenuto lo stesso comportamento che hai descritto ... all'inizio funziona in viewDidAppear, ma poi si ripristina alla rotazione.
tybro0103

3
Apparentemente non è possibile contrassegnare i vincoli come non installati in IB per questo scopo. Ho trovato queste informazioni qui: stackoverflow.com/questions/27663249/… e ha risolto il problema per me.
Stefan

1
I miei vincoli sono stati implementati nello stesso modo descritto nella domanda, tranne per aver attivato / disattivato alcuni di essi in viewDidAppear. Ha funzionato, ma potresti vedere gli elementi cambiare rapidamente posizione (un problema minore ma indesiderabile). La modifica in viewWillAppear o viewDidLoad non ha funzionato. Ma dopo aver letto questa domanda, ho provato a fare quella modifica in viewDidLayoutSubviews. Ha funzionato e il cambio di posizione non è più visibile all'utente. (Funzionava anche in viewWillLayoutSubviews). Quindi grazie per quel suggerimento!
peacetype

Risposte:


186

Attivo e disattivo NSLayoutConstraintsin viewDidLoade non ho problemi con esso. Quindi funziona. Ci deve essere una differenza nella configurazione tra la tua app e la mia :-)

Descriverò solo la mia configurazione, forse può darti un vantaggio:

  1. Ho impostato @IBOutletstutti i vincoli che devo attivare / disattivare.
  2. In ViewController, salvo i vincoli in proprietà di classe che non sono deboli. Il motivo è che ho scoperto che dopo aver disattivato un vincolo, non potevo riattivarlo: era nullo. Quindi, sembra essere cancellato quando disattivato.
  3. Non uso NSLayoutConstraint.deactivate/activatecome fai tu, invece uso constraint.active = YES/ NO.
  4. Dopo aver impostato i vincoli, chiamo view.layoutIfNeeded().

132
"salva i vincoli in proprietà di classe che non siano deboli" Mi hai risparmiato un sacco di tempo, grazie!
OpenUserX03

10
"Salvo i vincoli in proprietà di classe che non sono deboli": questo mi ha risparmiato tonnellate di angoscia. Non sapevo di chiamare un selettore su un oggetto nullo. Grazie!!
static0886

4
È importante notare che i vincoli "inattivi" non vengono ignorati dal layout automatico, vengono rimossi. L'attivazione / disattivazione dei vincoli li aggiunge e li rimuove. Ho trascorso un po 'di tempo a eseguire il debug di un layout automatico in conflitto dopo aver aggiunto i vincoli che avevo impostato in precedenza .active = falseaspettandomi che sarebbero stati ignorati fino a quando non li avessi impostati su attivi.
lbarbosa

1
salva i vincoli in proprietà di classe che non sono deboli, ok questo fa risparmiare un sacco di tempo, stavo ottenendo risultati contrastanti senza questo. Grazie uomo!
MegaManX

3
Il documento di Apple dice: L'attivazione o la disattivazione del vincolo chiama addConstraint ( :) e removeConstraint ( :) nella vista che è l'antenato comune più vicino degli elementi gestiti da questo vincolo. Usa questa proprietà invece di chiamare addConstraint ( :) o removeConstraint ( :) direttamente. Quindi sembra che quando un vincolo viene disattivato, viene rimosso e quindi non ci sono riferimenti forti al vincolo rimasto, a meno che IBOutlet non sia forte. Quindi il vincolo viene eliminato. IMHO questo è quasi un bug o almeno un comportamento molto inaspettato.
Olle Raab

52

Forse potresti controllare il tuo @properties, sostituire weakconstrong .

A volte perché active = NOimpostato self.yourConstraint = nil, quindi non puoi usarlo di self.yourConstraintnuovo.


5
Come affermato nella Swift Language Guide , le proprietà sono forti per impostazione predefinita, quindi puoi anche rimuovere weake questo lo farà.
Jonathan Cabrera

30
override func viewDidLayoutSubviews() {
// do it here, after constraints have been materialized
}

1
Perché il mio controller di visualizzazione è un controller di visualizzazione figlio: farlo in "didLayoutSubviews" sembra essere l'unico modo in cui ha funzionato !! FYI.
TalL

Questa è l'unica risposta valida
Yunus Eren Güzel,

@TalL Intendevi vincoli sul controller della vista figlio stesso o sulle sue viste secondarie?
Stefan

questo è il migliore
ACAkgul

14

Credo che il problema che stai riscontrando sia dovuto al fatto che i vincoli non vengono aggiunti alle loro visualizzazioni finché non viewDidLoad()viene chiamato AFTER . Hai una serie di opzioni:

A) È possibile collegare i vincoli di layout a un IBOutlet e accedervi nel codice tramite questi riferimenti. Poiché le prese sono collegate prima dell'inizio viewDidLoad(), i vincoli dovrebbero essere accessibili e puoi continuare ad attivarli e disattivarli lì.

B) Se desideri utilizzare la constraints()funzione di UIView per accedere ai vari vincoli devi aspettare per viewDidLayoutSubviews()dare il via e farlo lì, poiché questo è il primo punto dopo aver creato un view controller da un pennino che avrà dei vincoli installati. Non dimenticare di chiamare layoutIfNeeded()quando hai finito. Questo ha lo svantaggio che il passaggio del layout verrà eseguito due volte se sono presenti modifiche da applicare e devi assicurarti che non vi sia alcuna possibilità che venga attivato un ciclo infinito.

Un breve avvertimento: i vincoli disabilitati NON vengono restituiti dal constraints() metodo! Ciò significa che se si disabilita un vincolo con l'intenzione di riattivarlo in seguito, sarà necessario mantenere un riferimento ad esso.

C) Puoi dimenticare l'approccio dello storyboard e aggiungere i tuoi vincoli manualmente. Dal momento che lo stai facendo in, viewDidLoad()presumo che l'intenzione sia di farlo solo una volta per l'intera durata dell'oggetto piuttosto che cambiare il layout al volo, quindi questo dovrebbe essere un metodo accettabile.


10

È inoltre possibile regolare la priorityproprietà per "abilitarli" e "disabilitarli" (750 valore per abilitare e 250 per disabilitare per esempio). Per qualche motivo la modifica di activeBOOL non ha avuto alcun effetto sulla mia interfaccia utente. Non è necessario layoutIfNeedede può essere impostato e modificato su viewDidLoad o in qualsiasi momento successivo.


Un ottimo suggerimento. La modifica della priorità dei vincoli funziona in viewWillTransition(to:, with:)o viewWillLayoutSubviews()e puoi mantenere tutti i vincoli alternativi come "installati" in uno storyboard. La priorità dei vincoli potrebbe non cambiare da non obbligatorio a obbligatorio, quindi utilizza i valori seguenti 1000. D'altra parte, l'attivazione (aggiunta) e la disattivazione (rimozione) dei vincoli funziona solo in viewDidLayoutSubviews()e richiede di mantenere i strong @IBOutletriferimenti a NSLayoutConstraint-s.
Gary

"Per qualche motivo la modifica del BOOL attivo non ha avuto alcun effetto sulla mia interfaccia utente". Basato su qui . Io penso che non è possibile modificare un vincolo con una priorità 1000 durante il runtime. Se vuoi disattivarlo, dovresti impostare la priorità iniziale su 999 o inferiore ....
Honey

Non sono d'accordo con questa affermazione in quanto può portare a problemi di debug difficili e non risponde alla domanda. L'impostazione della priorità su 250 non "disattiverà" il vincolo, ma avrà comunque effetto e influenzerà il layout. Può sembrare che "disattivi" il vincolo nella maggior parte dei casi, ma sicuramente non in tutti i casi. (in particolare, non è il caso che mi ha portato a trovare risposta a questa domanda)
Tumata

Potrebbe causare arresti anomali come "La modifica di una priorità da obbligatoria a non su un vincolo installato (o viceversa) non è supportata. Hai passato la priorità 250 e la priorità esistente era 1000."
Karthick Ramesh

8

Il momento giusto per disattivare i vincoli inutilizzati:

-(void)viewWillLayoutSubviews{
    [super viewWillLayoutSubviews];

    self.myLittleConstraint.active = NO;
}

Tieni presente che viewWillLayoutSubviewspotrebbe essere chiamato più volte, quindi niente calcoli pesanti qui, ok?

Nota: se si desidera riattivare alcuni vincoli in un secondo momento, memorizzare sempre i strongriferimenti ad essi.


2
Per me l'unico modo affidabile è regolare i vincoli viewDidLayoutSubviews(). La regolazione dei vincoli in viewWillLayoutSubviews()non funziona nel mio caso.
petrsyn

6

Quando viene creata una vista, vengono chiamati in ordine i seguenti metodi del ciclo di vita:

  1. loadview
  2. viewDidLoad
  3. viewWillAppear
  4. viewWillLayoutSubviews
  5. viewDidLayoutSubviews
  6. viewDidAppear

Ora alle tue domande.

  1. Perché ricevo questo comportamento?

Risposta: Perché quando si tenta di impostare i vincoli sulle viste, viewDidLoadla vista non ha i suoi limiti, quindi i vincoli non possono essere impostati. È solo dopo viewDidLayoutSubviewsche i limiti della visualizzazione vengono definiti.

  1. È possibile attivare / disattivare i vincoli da viewDidLoad?

Risposta: No. Motivo spiegato sopra.


Nella tua descrizione del ciclo di vita di viewController hai parlato di come la visualizzazione viene prima caricata e poi viene chiamata viewDidLoad. Eppure hai anche detto che la vista non viene creata nel momento in cui viene chiamato viewDidLoad, questa è chiaramente una contraddizione. Inoltre, puoi testare tu stesso e vedere che la vista è stata creata nel momento in cui viewDidLoad è stato chiamato perché puoi aggiungere sottoview alla vista.
ABakerSmith

viewDidLoad dovrebbe andare bene poiché le viste vengono create e caricate ... In realtà, dove si attivano i vincoli, dipende principalmente dalle prestazioni. Immagino che il problema originale non fosse correlato a dove sono stati attivati ​​i vincoli. stackoverflow.com/questions/19387998/…
Gabe

@ABakerSmith ho modificato la mia risposta per essere più chiara.
Sumeet

1

Ho trovato finché imposti i vincoli per normale nell'override di - (void)updateConstraints(obiettivo c), con un strongriferimento per i vincoli attivi e non attivi di inizializzazione utilizzata. E altrove nel ciclo di visualizzazione disattiva e / o attiva ciò di cui hai bisogno, quindi chiamando layoutIfNeeded, non dovresti avere problemi.

L'importante è non riutilizzare costantemente l'override updateConstraintse separare le attivazioni dei vincoli, purché si chiami updateConstraints dopo la prima inizializzazione e layout. Sembra avere importanza dopo quello in cui si trova nel ciclo di visualizzazione.

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.