Pratica corretta per la sottoclasse di UIView?


158

Sto lavorando ad alcuni controlli di input personalizzati basati su UIView e sto provando ad accertare la pratica corretta per impostare la vista. Quando si lavora con un UIViewController, è abbastanza semplice da usare le loadViewe relativi viewWill, viewDidi metodi, ma quando sottoclasse un UIView, i methosds più vicini che ho sono `awakeFromNib, drawRecte layoutSubviews. (Sto pensando in termini di installazione e richiamate.) Nel mio caso, sto impostando il mio frame e le viste interne layoutSubviews, ma non vedo nulla sullo schermo.

Qual è il modo migliore per garantire che la mia vista abbia l'altezza e la larghezza corrette che desidero che abbia? (La mia domanda si applica indipendentemente dal fatto che io stia utilizzando il layout automatico, anche se potrebbero esserci due risposte.) Qual è la "best practice" corretta?

Risposte:


298

Apple ha definito abbastanza chiaramente come sottoclassare UIViewnel documento.

Dai un'occhiata all'elenco seguente, in particolare dai un'occhiata a initWithFrame:e layoutSubviews. Il primo ha lo scopo di impostare il frame del tuo UIViewmentre il secondo ha lo scopo di impostare il frame e il layout delle sue sottoview.

Ricorda inoltre che initWithFrame:viene chiamato solo se stai creando un'istanza a livello di UIViewprogrammazione. Se lo stai caricando da un file pennino (o uno storyboard), initWithCoder:verrà utilizzato. E nel initWithCoder:frame non è stato ancora calcolato, quindi non è possibile modificare il frame impostato in Interface Builder. Come suggerito in questa risposta si potrebbe pensare di chiamare initWithFrame:da initWithCoder:al fine di impostare il telaio.

Infine, se carichi il tuo UIViewda un pennino (o uno storyboard), hai anche l' awakeFromNibopportunità di eseguire inizializzazioni personalizzate di frame e layout, poiché quando awakeFromNibviene chiamato è garantito che ogni vista nella gerarchia sia stata archiviata e inizializzata.

Dal documento di NSNibAwaking(ora sostituito dal documento di awakeFromNib):

I messaggi ad altri oggetti possono essere inviati in modo sicuro dall'interno di awakeFromNib — a quel punto è garantito che tutti gli oggetti sono non archiviati e inizializzati (anche se non necessariamente risvegliati, ovviamente)

Vale anche la pena notare che con il layout automatico non è necessario impostare esplicitamente il frame della vista. Dovresti invece specificare una serie di vincoli sufficienti, in modo che il frame venga automaticamente calcolato dal motore di layout.

Direttamente dalla documentazione :

Metodi per eseguire l'override

Inizializzazione

  • initWithFrame:Si consiglia di implementare questo metodo. È inoltre possibile implementare metodi di inizializzazione personalizzati in aggiunta o al posto di questo metodo.

  • initWithCoder: Implementare questo metodo se si carica la vista da un file pennino di Interface Builder e la vista richiede un'inizializzazione personalizzata.

  • layerClassImplementare questo metodo solo se si desidera che la vista utilizzi un livello Animazione core diverso per il relativo archivio di supporto. Ad esempio, se si utilizza OpenGL ES per eseguire il disegno, si desidera sovrascrivere questo metodo e restituire la classe CAEAGLLayer.

Disegno e stampa

  • drawRect:Implementa questo metodo se la tua vista disegna contenuti personalizzati. Se la vista non esegue alcun disegno personalizzato, evitare di ignorare questo metodo.

  • drawRect:forViewPrintFormatter: Implementare questo metodo solo se si desidera disegnare il contenuto della vista in modo diverso durante la stampa.

vincoli

  • requiresConstraintBasedLayout Implementare questo metodo di classe se la classe di visualizzazione richiede che i vincoli funzionino correttamente.

  • updateConstraints Implementare questo metodo se la vista deve creare vincoli personalizzati tra le visualizzazioni secondarie.

  • alignmentRectForFrame:, frameForAlignmentRect:Implementa questi metodi per sostituire il modo in cui le tue viste sono allineate ad altre viste.

disposizione

  • sizeThatFits:Implementare questo metodo se si desidera che la vista abbia dimensioni predefinite diverse rispetto a quelle normali durante le operazioni di ridimensionamento. Ad esempio, è possibile utilizzare questo metodo per evitare che la vista si riduca al punto in cui le visualizzazioni secondarie non possono essere visualizzate correttamente.

  • layoutSubviews Implementa questo metodo se hai bisogno di un controllo più preciso sul layout delle tue visualizzazioni secondarie rispetto a quanto previsto dal comportamento di vincolo o ridimensionamento automatico.

  • didAddSubview:, willRemoveSubview:Implementare questi metodi in base alle necessità per tenere traccia delle aggiunte e delle rimozioni delle visualizzazioni secondarie.

  • willMoveToSuperview:, didMoveToSuperviewImplementare questi metodi in base alle necessità per tenere traccia del movimento della vista corrente nella gerarchia della vista.

  • willMoveToWindow:, didMoveToWindowImplementare questi metodi in base alle necessità per tenere traccia dello spostamento della vista in un'altra finestra.

Gestione degli eventi:

  • touchesBegan:withEvent:, touchesMoved:withEvent:, touchesEnded:withEvent:, touchesCancelled:withEvent:Implementare questi metodi, se avete bisogno di gestire direttamente gli eventi di tocco. (Per input basati su gesti, utilizzare i riconoscitori di gesti.)

  • gestureRecognizerShouldBegin: Implementare questo metodo se la vista gestisce direttamente gli eventi di tocco e potrebbe voler impedire che i riconoscimenti di gesti collegati attivino azioni aggiuntive.


che dire - (vuoto) setFrame: (CGRect) frame?
pag

bene, puoi sicuramente annullarlo, ma per quale scopo?
Gabriele Petronella,

per cambiare layout / disegno ogni volta che cambiano le dimensioni della cornice o la posizione
pag

1
Che dire layoutSubviews?
Gabriele Petronella,

Da stackoverflow.com/questions/4000664/… , "il problema è che le visualizzazioni secondarie non solo possono cambiare la loro dimensione, ma possono anche animare la modifica della dimensione. Quando UIView esegue l'animazione, non chiama layoutSubviews ogni volta." Non hanno provato personalmente però
PFrank

38

Questo è ancora alto in Google. Di seguito è riportato un esempio aggiornato di swift.

La didLoadfunzione consente di inserire tutto il codice di inizializzazione personalizzato. Come altri hanno già detto, didLoadverrà chiamato quando una vista viene creata programmaticamente tramite init(frame:)o quando il deserializzatore XIB unisce un modello XIB alla vista tramiteinit(coder:)

A parte : layoutSubviewse updateConstraintssono chiamati più volte per la maggior parte delle visualizzazioni. Questo è inteso per layout e regolazioni multi-pass avanzati quando i limiti di una vista cambiano. Personalmente, evito layout multi-pass quando possibile perché bruciano i cicli della CPU e rendono tutto un mal di testa. Inoltre, inserisco il codice di vincolo negli stessi inizializzatori poiché li invalido raramente.

import UIKit

class MyView: UIView {
  //-----------------------------------------------------------------------------------------------------
  //Constructors, Initializers, and UIView lifecycle
  //-----------------------------------------------------------------------------------------------------
  override init(frame: CGRect) {
      super.init(frame: frame)
      didLoad()
  }

  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    didLoad()
  }

  convenience init() {
    self.init(frame: CGRectZero)
  }

  func didLoad() {
    //Place your initialization code here

    //I actually create & place constraints in here, instead of in
    //updateConstraints
  }

  override func layoutSubviews() {
     super.layoutSubviews()

     //Custom manually positioning layout goes here (auto-layout pass has already run first pass)
  }

  override func updateConstraints() {
    super.updateConstraints()

    //Disable this if you are adding constraints manually
    //or you're going to have a 'bad time'
    //self.translatesAutoresizingMaskIntoConstraints = false

    //Add custom constraint code here
  }
}

Puoi spiegare quando / perché dividere il codice di vincolo del layout tra un metodo chiamato dal processo init e layoutSubviews e updateConstraints? Sembra che siano tutte e tre le possibili posizioni candidate per posizionare il codice layout. Quindi, come fai a sapere quando / cosa / perché dividere il codice di layout tra i tre?
Clay Ellis,

3
Non uso mai updateConstraints; updateConstraints può essere utile perché sai che la tua gerarchia di viste è stata impostata completamente in init, quindi non puoi sollevare un'eccezione aggiungendo un vincolo tra due viste non nella gerarchia :) layoutSubviews non dovrebbe mai avere modifiche ai vincoli; può facilmente causare una ricorsione infinita poiché layoutSubviews viene chiamato se i vincoli vengono "invalidati" durante il passaggio del layout. L'impostazione manuale del layout (come nell'impostazione diretta dei frame, che raramente è necessario eseguire più a meno che per motivi di prestazioni) vada in layoutSubviews. Personalmente, metto la creazione di vincoli in init
seo

Per il codice di rendering personalizzato, dovremmo sostituire il drawmetodo?
Petrus Theron,

14

C'è un sommario decente nella documentazione di Apple , e questo è ben coperto nel corso gratuito di Stanford disponibile su iTunes. Vi presento la mia versione TL; DR qui:

Se la tua classe è composta principalmente da sottoview, il metodo giusto per assegnarle è nei initmetodi. Per le viste, esistono due diversi initmetodi che possono essere chiamati, a seconda che la vista venga istanziata dal codice o da un pennino / storyboard. Quello che faccio è scrivere il mio setupmetodo, e quindi chiamare da entrambi i initWithFrame:e initWithCoder:metodi.

Se stai eseguendo un disegno personalizzato, vuoi davvero scavalcare la drawRect:tua vista. Se la visualizzazione personalizzata è principalmente un contenitore per le visualizzazioni secondarie, probabilmente non sarà necessario farlo.

layoutSubViewsSostituisci solo se vuoi fare qualcosa come aggiungere o rimuovere una sottoview a seconda che tu sia orientato in verticale o orizzontale. Altrimenti, dovresti essere in grado di lasciarlo da solo.


Uso la tua risposta per cambiare il frame della subView di view (che è awakeFromNib) layoutSubViews, ha funzionato.
aereo il

1

layoutSubviews ha lo scopo di impostare la cornice sulle viste figlio, non sulla vista stessa.

Per UIView, il costruttore designato è in genere initWithFrame:(CGRect)framee dovresti impostare il frame lì (o dentro initWithCoder:), possibilmente ignorando il valore del frame passato. Puoi anche fornire un costruttore diverso e impostare qui il frame.


potresti prenderlo più in dettaglio? Non conoscevo la tua media. Come impostare il frame di una subView di una vista? il punto di vista èawakeFromNib
aereo il

Avanzando rapidamente al 2016, probabilmente non dovresti impostare alcun frame e utilizzare il layout automatico (vincoli). Se la vista proviene da XIB (o storyboard), la vista secondaria dovrebbe già essere configurata.
Proxi,
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.