Quando viene chiamato layoutSubviews?


264

Ho una vista personalizzata che non riceve layoutSubviewmessaggi durante l'animazione.

Ho una vista che riempie lo schermo. Ha una sottoview personalizzata nella parte inferiore dello schermo che si ridimensiona correttamente in Interface Builder se cambio l'altezza della barra di navigazione. layoutSubviewsviene chiamato quando viene creata la vista, ma mai più. Le mie osservazioni secondarie sono strutturate correttamente. Se disattivo la barra di stato della chiamata, la vista secondaria layoutSubviewsnon viene affatto richiamata, anche se la vista principale anima il suo ridimensionamento.

In quali circostanze viene layoutSubviewseffettivamente chiamato?

Ho autoresizesSubviewsimpostato su NOper la mia visualizzazione personalizzata. E in Interface Builder ho i montanti superiore e inferiore e la freccia verticale impostata.


Un'altra parte del puzzle è che la finestra deve essere resa chiave:

[window makeKeyAndVisible];

altrimenti le visualizzazioni secondarie non vengono ridimensionate automaticamente.

Risposte:


492

Avevo una domanda simile, ma non ero soddisfatto della risposta (o di quella che riuscivo a trovare in rete), quindi l'ho provata in pratica ed ecco cosa ho ottenuto:

  • initnon fa layoutSubviewsessere chiamato (duh)
  • addSubview:provoca la layoutSubviewschiamata sulla vista aggiunta, la vista a cui viene aggiunta (vista destinazione) e tutte le viste secondarie della destinazione
  • view setFrame chiama in modo intelligente layoutSubviewsla vista con il frame impostato solo se il parametro size del frame è diverso
  • lo scorrimento di un UIScrollView provoca la layoutSubviewschiamata su scrollView e la sua superview
  • la rotazione di un dispositivo richiama solo layoutSubviewla vista padre (la vista principale Vista principale dei controller)
  • Il ridimensionamento di una vista richiamerà la layoutSubviewssua superview

I miei risultati - http://blog.logichigh.com/2011/03/16/when-does-layoutsubviews-get-called/


1
Bella risposta. Mi sono sempre chiesto layoutSubviews. Ha initWithFrame:ragione layoutSubviewsdi essere chiamato?
Robert,

2
@Robert - Stavo usando initWithFrame ... quindi no.
BadPirate,

8
@BadPirate: . Secondo i miei esperimenti, se si ridimensiona view1.1chiama layoutSubviewsdi view1e poi layoutSubviewsdi view1.1. Questa chiamata non si propaga indefinitamente alle superview, chiamandola view1.1.1solo layoutSubviewssu chiamate view1.1e view1.1.1. Il solo spostamento senza modificarne le dimensioni non richiede layoutSubviewsnessuno di essi.
João Portela,

1
viewDidLoad non viene chiamato su UIView (ma UIViewController). Visualizza Did load viene chiamato dopo che UIView è stato avviato.
BadPirate,

2
Sulla base della mia esperienza, la seconda regola potrebbe non essere esatta: quando aggiungo view1.2in view1, layoutSubviewsdi view1.2e view1sono chiamati, ma layoutSubviewsdi view1.1non viene chiamato. ( view1.1e view1.2sono subview di view1). Cioè, non tutte le sottoview della vista target sono chiamate layoutSubviewsmetodo .
HongchaoZhang,

96

Sulla base della precedente risposta di @BadPirate, ho sperimentato un po 'di più e ho trovato alcuni chiarimenti / correzioni. Ho scoperto che layoutSubviews:verrà chiamato in una vista se e solo se:

  • I suoi limiti (non frame) sono cambiati.
  • I limiti di una delle sue sottoview dirette sono cambiati.
  • Una vista secondaria viene aggiunta alla vista o rimossa dalla vista.

Alcuni dettagli rilevanti:

  • I limiti vengono considerati modificati solo se il nuovo valore è diverso, inclusa un'origine diversa . Nota in particolare che è per questo che layoutSubviews:viene chiamato ogni volta che un UIScrollView scorre, poiché esegue lo scorrimento modificando l'origine dei suoi limiti.
  • La modifica della cornice cambierà i limiti solo se la dimensione è cambiata, poiché questa è l'unica cosa propagata alla proprietà dei limiti.
  • Una modifica dei limiti di una vista che non è ancora in una gerarchia di viste comporterà una chiamata a layoutSubviews: quando la vista verrà infine aggiunta a una gerarchia di viste .
  • E solo per completezza: questi trigger non chiamano direttamente layoutSubviews, ma piuttosto call setNeedsLayout, che imposta / alza un flag. Ad ogni iterazione del ciclo di esecuzione, per tutte le viste nella gerarchia delle viste , questo flag viene controllato. Per ogni vista in cui viene trovato il flag sollevato, layoutSubviews:viene chiamato su di esso e il flag viene ripristinato. Le viste più in alto nella gerarchia verranno controllate / chiamate per prime.

5
non posso votare abbastanza questa risposta. dovrebbe essere la risposta migliore. le tre regole fornite sono tutto ciò di cui hai bisogno. non ho mai riscontrato alcun layout di presentazione che queste regole non descrivono perfettamente.
Pärserk,

1
Non credo che layoutSubviews venga chiamato quando i limiti di una subview diretta vengono modificati. Penso che la chiamata di layoutSubviews sia solo un effetto collaterale dei tuoi test. In alcuni casi layoutSubviews non verrà chiamato quando cambiano i limiti di una subview. Ti preghiamo di controllare la risposta di frogcjn, perché la sua risposta si basa sulla documentazione di Apple invece che su semplici esperimenti.
Simon Backx,

19

https://developer.apple.com/library/prerelease/tvos/documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/CreatingViews/CreatingViews.html#//apple_ref/doc/uid/TP40009503-CH5-SW1

Le modifiche al layout possono verificarsi ogni volta che si verifica uno dei seguenti eventi in una vista:

un. La dimensione del rettangolo dei limiti di una vista cambia.
b. Si verifica una modifica dell'orientamento dell'interfaccia, che di solito provoca una modifica nel rettangolo dei limiti della vista radice.
c. L'insieme dei sublayer di Animazione principale associati al livello della vista cambia e richiede il layout.
d. L'applicazione impone il layout chiamando il   metodo setNeedsLayout o  layoutIfNeededdi una vista.
e. L'applicazione impone il layout chiamando il  setNeedsLayout metodo dell'oggetto layer sottostante della vista.


Inoltre, è importante sottolineare che nessuno di quegli eventi viene chiamato quando la vista non è stata ancora aggiunta allo stack di viste. Lo si include con la parola "can", ma in particolare viene chiamato nel successivo ciclo disponibile del thread principale dell'applicazione quando è stato contrassegnato come layout da uno di quegli eventi.
user1122069

13

Alcuni dei punti nella risposta di BadPirate sono solo parzialmente veri:

  1. Per il addSubViewpunto

    addSubview fa sì che layoutSubviews venga chiamato sulla vista aggiunta, la vista viene aggiunta a (vista target) e tutte le sottoview del target.

    Dipende dalla maschera di ridimensionamento automatico della vista (vista target). Se ha la maschera di ridimensionamento automatico attivata, layoutSubview verrà chiamato su ciascuno addSubview. Se non ha una maschera di ridimensionamento automatico, layoutSubview verrà chiamato solo quando cambiano le dimensioni del frame della vista (vista target).

    Esempio: se hai creato UIView a livello di codice (non ha una maschera di ridimensionamento automatico per impostazione predefinita), LayoutSubview verrà chiamato solo quando il frame UIView non cambia su tutti addSubview.

    È attraverso questa tecnica che aumentano anche le prestazioni dell'applicazione.

  2. Per il punto di rotazione del dispositivo

    La rotazione di un dispositivo chiama layoutSubview solo nella vista padre (la vista principale Vista principale del controller)

    Questo può essere vero solo quando il tuo VC è nella gerarchia VC (root at window.rootViewController), bene questo è il caso più comune. In iOS 5, se crei un VC, ma non viene aggiunto a nessun altro VC, questo VC non verrà notato quando il dispositivo ruota. Pertanto la sua vista non verrebbe notata chiamando layoutSubviews.


9

Ho rintracciato la soluzione in base all'insistenza di Interface Builder che le molle non possono essere modificate in una vista in cui gli elementi dello schermo simulato sono attivati ​​(barra di stato, ecc.). Poiché le molle erano disattivate per la vista principale, quella vista non poteva cambiare dimensione e quindi veniva fatta scorrere verso il basso nella sua interezza quando appariva la barra di chiamata.

La disattivazione delle funzioni simulate, il ridimensionamento della vista e l'impostazione corretta delle molle hanno causato l'animazione e il mio metodo è stato chiamato.

Un ulteriore problema nel debug è che il simulatore esce dall'app quando lo stato della chiamata viene attivato tramite il menu. Esci dall'app = nessun debugger.


Stai dicendo che layoutSubviews viene chiamato quando la vista viene ridimensionata? Ho sempre pensato che non fosse ...
Andrey Tarantsov,

È. Quando una vista viene ridimensionata, deve fare qualcosa con le sue viste secondarie. Se non lo fornisci, li sposta automaticamente utilizzando molle, puntoni, ecc.
Steve Weller,

8

chiamando [self.view setNeedsLayout]; in viewController lo fa chiamare viewDidLayoutSubviews


5

hai visto layoutIfNeeded?

Lo snippet di documentazione è di seguito. L'animazione funziona se si chiama questo metodo esplicitamente durante l'animazione?

layoutIfNeeded Distribuisce le visualizzazioni secondarie, se necessario.

- (void)layoutIfNeeded

Discussione Utilizzare questo metodo per forzare il layout delle viste secondarie prima del disegno.

Disponibilità Disponibile in iPhone OS 2.0 e versioni successive.


2

Durante la migrazione di un'app OpenGL da SDK 3 a 4, layoutSubviews non veniva più chiamato. Dopo molte prove ed errori ho finalmente aperto MainWindow.xib, selezionato l'oggetto Window, nella finestra di ispezione ho scelto la scheda Attributi della finestra (all'estrema sinistra) e ho controllato "Visibile all'avvio". Sembra che in SDK 3 sia ancora usato per causare una chiamata layoutSubViews, ma non in 4.

Finite 6 ore di frustrazione.


Hai creato la chiave della finestra? In caso contrario, ciò può causare il verificarsi di qualsiasi cosa interessante.
Steve Weller,

-2

Un caso piuttosto oscuro, ma potenzialmente importante quando layoutSubviewsnon viene mai chiamato è:

import UIKit

class View: UIView {

    override class var layerClass: AnyClass { return Layer.self }

    class Layer: CALayer {
        override func layoutSublayers() {
            // if we don't call super.layoutSublayers()...
            print(type(of: self), #function)
        }
    }

    override func layoutSubviews() {
        // ... this method never gets called by the OS!
        print(type(of: self), #function)
    }
}

let view = View(frame: CGRect(x: 0, y: 0, width: 100, height: 100))

1
Perché questa risposta è qui?
Dominik Bucher,
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.