Come posso ottenere la larghezza e l'altezza correnti di una vista quando si utilizzano i vincoli di layout automatico?


108

Non sto parlando della proprietà frame, perché da quella puoi ottenere solo la dimensione della vista in xib. Sto parlando di quando la vista viene ridimensionata a causa dei suoi vincoli (magari dopo una rotazione, o in risposta a un evento). C'è un modo per ottenere la larghezza e l'altezza correnti?

Ho provato a iterare attraverso i suoi vincoli alla ricerca di vincoli di larghezza e altezza, ma non è molto chiaro e fallisce quando ci sono vincoli intrinseci (dal momento che non riesco a distinguere tra i due). Inoltre, funziona solo se hanno effettivamente vincoli di larghezza e altezza, cosa che non fanno se si basano su altri vincoli per il ridimensionamento.

Perché è così difficile per me. ARG!


Avrei pensato che i limiti della vista in un determinato momento mantenessero la larghezza e l'altezza correnti. Questo è ciò che viene regolato durante il processo di layout.
Phillip Mills

Hmm non ho mai pensato di controllare i limiti. Lo farò adesso.
yuf

Risposte:


246

La risposta è [view layoutIfNeeded].

Ecco perché:

Puoi comunque ottenere la larghezza e l'altezza correnti della vista ispezionando view.bounds.size.widthe view.bounds.size.height(o il fotogramma, che è equivalente a meno che tu non stia giocando con il view.transform).

Se quello che vuoi è la larghezza e l'altezza implicite dai tuoi vincoli esistenti, la risposta è di non ispezionare i vincoli manualmente, poiché ciò richiederebbe di reimplementare l'intera logica di risoluzione dei vincoli del sistema di layout automatico per interpretarli vincoli. Invece, quello che dovresti fare è semplicemente chiedere al layout automatico di aggiornare quel layout , in modo che risolva i vincoli e aggiorni il valore di view.bounds con la soluzione corretta, quindi ispezioni view.bounds.

Come chiedi al layout automatico di aggiornare il layout? Chiama [view setNeedsLayout]se desideri che il layout automatico aggiorni il layout alla svolta successiva del ciclo di esecuzione.

Tuttavia, se si desidera che aggiorni immediatamente il layout, in modo da poter accedere immediatamente al nuovo valore dei limiti in un secondo momento all'interno della funzione corrente o in un altro punto prima della svolta del ciclo di esecuzione, è necessario chiamare [view setNeedsLayout]e [view layoutIfNeeded].

Hai posto una seconda domanda: "come posso modificare un vincolo di altezza / larghezza se non ho un riferimento diretto ad esso?".

Se si crea il vincolo in IB, la soluzione migliore è creare un IBOutlet nel controller della vista o nella vista in modo da avere un riferimento diretto ad esso. Se hai creato il vincolo nel codice, dovresti mantenere un riferimento in una proprietà debole interna nel momento in cui lo hai creato. Se qualcun altro ha creato il vincolo, è necessario trovarlo esaminando la proprietà view.constraints sulla vista e possibilmente l'intera gerarchia della vista e implementando la logica che trova NSLayoutConstraint cruciale. Questo è probabilmente il modo sbagliato di procedere, poiché richiede anche di determinare quale particolare vincolo ha determinato la dimensione dei limiti, quando non è garantita una risposta semplice a quella domanda. Il valore dei limiti finali potrebbe essere la soluzione a un sistema molto complicato di vincoli multipli,


16
Una risposta eccellente e anche ben spiegata. Mi ha salvato dopo un'ora o due in cui mi sono strappato i capelli.
Ben Kreeger

2
layoutIfNeededè fantastico e renderà immediatamente disponibile la cornice. Tuttavia, forzerà anche il rendering dei vincoli su tutte le viste nei sottoalberi della vista su cui è chiamato. Se si aggiungono visualizzazioni a livello di codice, ad esempio, e si richiamano layoutIfNeededtutte nella routine ricorsiva, è possibile che il rendering della gerarchia delle visualizzazioni venga eseguito molto lentamente. (L'ho imparato a mie spese) Come menzionato nell'eccellente risposta, 'setNeedsLayout` è più efficiente e renderà il frame disponibile al passaggio di layout successivo, cosa che idealmente avviene in circa 1/60 di secondo.
shmim

1
So che è vecchio, ma dove lo chiami questo metodo? In initWithCoder?
MayNotBe

4
Perché forzare il layout solo per ottenere i limiti? Questo sembra inefficiente e con un'interfaccia complessa potrebbe essere costoso. Accedi invece agli ultimi limiti da viewDidLayoutSubviews o anche viewWillLayoutSubviews e lascia che cocoa gestisca i propri tempi di layout. Impostare una proprietà se è necessario accedere al valore o al flag per evitare più chiamate se questo è un problema.
smileBot

1
Sì, è necessario forzare il layout solo se è necessario accedere al valore calcolato dal layout automatico prima della svolta del ciclo di esecuzione, ad esempio entro l'ambito della chiamata di funzione corrente.
Algal

8

Ho avuto un problema simile in cui avevo bisogno di aggiungere un bordo superiore e inferiore a un UITableViewche si ridimensiona in base alla configurazione dei suoi vincoli in UIStoryboard. Sono stato in grado di accedere ai vincoli aggiornati con - (void)viewDidLayoutSubviews. Ciò è utile in modo da non dover creare una sottoclasse di una vista e sovrascrivere il suo metodo di layout.

/*** SET TOP AND BOTTOM BORDERS ON TABLE VIEW ***/
- (void)addBorders
{
    CALayer *topBorder           = [CALayer layer];
    topBorder.frame              = CGRectMake(0.0f, self.tableView.frame.origin.y, 320.0f, 0.5f);
    topBorder.backgroundColor    = [UIColor redColor].CGColor;

    CALayer *bottomBorder        = [CALayer layer];
    bottomBorder.frame           = CGRectMake(0.0f, (self.tableView.frame.origin.y + self.tableView.frame.size.height), 320.0f, 0.5f);
    bottomBorder.backgroundColor = [UIColor redColor].CGColor;

    [self.view.layer addSublayer:topBorder];
    [self.view.layer addSublayer:bottomBorder];
}

/*** GET AUTORESIZED FRAME DIMENSIONS ***/
- (void)viewDidLayoutSubviews{
    [self addBorders];
}

Senza chiamare il metodo dal viewDidLayoutSubviewmetodo, solo il bordo superiore viene disegnato correttamente, poiché il bordo inferiore è da qualche parte fuori dallo schermo.


3
viewDidLayoutSubviews viene chiamato un paio di volte, stai creando tonnellate di livelli ... Devi aggiungere alcuni controlli di esistenza prima di aggiungere un altro livello.
Kalzem

Commenta con qualche anno di ritardo ... dovresti anche chiamare questo metodo sulla superclasse quando sovrascrivi prima di ogni altra cosa:[super viewDidLayoutSubviews];
Alejandro Iván

5

Per coloro che potrebbero ancora affrontare tali problemi, specialmente con TableviewCell.

Basta sovrascrivere il metodo:

-(void)layoutSubviews
{
//your code here like drawing a shadow
}

In caso di UITableViewCell o UICollectionViewCell creare una sottoclasse della cella e sovrascrivere lo stesso metodo:

-(void)layoutSubviews
{
//your code here like drawing a shadow
}

questo è l'unico modo in cui ho potuto ottenere la dimensione finale reale della mia vista
Benoit Jadinon

Questo è stato l'unico modo in cui sono stato in grado di ottenere la dimensione finale per una qualsiasi delle mie visualizzazioni limitate perché prima stavo cercando di ottenerle su awakeFromNib che non ha dato alle visualizzazioni secondarie il tempo di ridimensionarsi effettivamente per prime. Grazie!
Azin Mehrnoosh,

3

Usa -(void)viewWillAppear:(BOOL)animatede chiama [self.view layoutIfNeeded];- Funziona, ho provato.

perché se lo usi -(void)viewDidLayoutSubviewsfunzionerà sicuramente ma questo metodo viene chiamato ogni volta che la tua interfaccia utente richiede aggiornamenti / modifiche. Che diventerà difficile da gestire. Il tasto funzione è che utilizzi una variabile bool per evitare tale ciclo di chiamate. uso migliore viewWillAppear. Ricorda viewWillAppearverrà chiamato anche se la vista viene caricata di nuovo (senza riallocare).


2

Il telaio è ancora valido. Alla fine, la vista usa la sua proprietà frame per disporsi. Calcola quel frame in base a tutti i vincoli. I vincoli vengono utilizzati solo per il layout iniziale (e ogni volta che layoutSubviews viene chiamato su una vista come dopo una rotazione). Dopodiché, le informazioni sulla posizione si trovano nella proprietà frame. O stai vedendo diversamente?


1
Sto vedendo diversamente. I valori del frame non cambiano, anche dopo aver modificato direttamente i vincoli di larghezza / altezza (modificando le loro costanti. Ho un IBOutlet per loro in
xib

Il mio problema è unico perché cambio il vincolo, quindi devo usare il frame nella stessa funzione. La chiamata a setNeedsLayout non lo aggiorna immediatamente. Immagino di dover prima attendere il layout, quindi utilizzare la cornice. La mia seconda domanda è: come posso modificare un vincolo di altezza / larghezza se non ho un riferimento diretto ad esso?
yuf

1
Dovresti quindi dispatch_async e ti permetterà di ritardare il tuo codice fino al frame successivo. Per quanto riguarda la seconda parte ... non so ... dovresti iterare tutti i vincoli e cercare quale è applicabile ... IBOutlet sono molto più facili.
borrrden

Vero per IBOutlet, ma voglio scrivere una funzione in una categoria per un UIView con cui non avrò IB con cui lavorare.
yuf
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.