Właściwa praktyka tworzenia podklas UIView?

158

Pracuję nad niektórymi niestandardowymi kontrolkami wejściowymi opartymi na UIView i próbuję ustalić właściwą praktykę konfigurowania widoku. Podczas pracy z UIViewController, jest to dość proste do używania loadViewi pokrewnych viewWill, viewDidmetod, ale gdy instacji UIView, najbliżsi methosds mam to `awakeFromNib, drawRecti layoutSubviews. (Myślę w kategoriach konfiguracji i wywołań zwrotnych.) W moim przypadku ustawiam ramkę i wewnętrzne widoki w programie layoutSubviews, ale nie widzę niczego na ekranie.

Jaki jest najlepszy sposób, aby upewnić się, że mój widok ma odpowiednią wysokość i szerokość, jaką chcę, aby miał? (Moje pytanie ma zastosowanie niezależnie od tego, czy używam autoukładu, chociaż mogą istnieć dwie odpowiedzi). Jaka jest właściwa „najlepsza praktyka”?

Mosze
źródło

Odpowiedzi:

298

Firma Apple dość jasno zdefiniowała sposób tworzenia podklas UIVieww dokumencie.

Sprawdź poniższą listę, a zwłaszcza spójrz na initWithFrame:i layoutSubviews. Pierwsza z nich jest przeznaczona do ustawienia ramki, UIViewpodczas gdy druga służy do ustawienia ramki i układu jej podglądów.

Pamiętaj również, że initWithFrame:jest ona wywoływana tylko wtedy, gdy tworzysz instancję w sposób UIViewprogramowy. Jeśli ładujesz go z pliku nib (lub storyboardu), initWithCoder:zostanie użyty. A w initWithCoder:ramce nie został jeszcze obliczony, więc nie można zmodyfikować ramkę skonfigurowanej w konstruktorze Interface. Jak zasugerowano w tej odpowiedzi , możesz pomyśleć o dzwonieniu initWithFrame:z initWithCoder:w celu ustawienia ramki.

Na koniec, jeśli ładujesz UIViewze stalówki (lub storyboardu), masz również awakeFromNibmożliwość wykonania niestandardowych inicjalizacji ramki i układu, ponieważ kiedy awakeFromNibjest wywoływany, gwarantuje się, że każdy widok w hierarchii został cofnięty z archiwum i zainicjowany.

Z dokumentu z NSNibAwaking(obecnie zastąpionego przez dokument z awakeFromNib):

Wiadomości do innych obiektów mogą być bezpiecznie wysyłane z poziomu awakeFromNib - do tego czasu mamy pewność, że wszystkie obiekty zostaną przywrócone z archiwum i zainicjowane (choć niekoniecznie przebudzone, oczywiście)

Warto również zauważyć, że w przypadku autoukładu nie należy jawnie ustawiać ramki widoku. Zamiast tego należy określić zestaw wystarczających ograniczeń, aby ramka była automatycznie obliczana przez silnik układu.

Prosto z dokumentacji :

Metody do zastąpienia

Inicjalizacja

  • initWithFrame:Zaleca się wdrożenie tej metody. Oprócz tej metody lub zamiast niej można również zaimplementować niestandardowe metody inicjowania.

  • initWithCoder: Zaimplementuj tę metodę, jeśli ładujesz widok z pliku nib programu Interface Builder, a widok wymaga niestandardowej inicjalizacji.

  • layerClassZaimplementuj tę metodę tylko wtedy, gdy chcesz, aby Twój widok używał innej warstwy animacji podstawowej dla swojego magazynu zapasowego. Na przykład, jeśli używasz OpenGL ES do rysowania, chciałbyś zastąpić tę metodę i zwrócić klasę CAEAGLLayer.

Rysowanie i drukowanie

  • drawRect:Zaimplementuj tę metodę, jeśli Twój widok rysuje zawartość niestandardową. Jeśli widok nie zawiera żadnego niestandardowego rysunku, unikaj zastępowania tej metody.

  • drawRect:forViewPrintFormatter: Zastosuj tę metodę tylko wtedy, gdy chcesz inaczej narysować zawartość widoku podczas drukowania.

Ograniczenia

  • requiresConstraintBasedLayout Zaimplementuj tę metodę klasy, jeśli klasa widoku wymaga ograniczeń do prawidłowego działania.

  • updateConstraints Zaimplementuj tę metodę, jeśli Twój widok wymaga tworzenia niestandardowych ograniczeń między widokami podrzędnymi.

  • alignmentRectForFrame:, frameForAlignmentRect:Zaimplementuj te metody, aby zastąpić sposób dopasowania widoków do innych widoków.

Układ

  • sizeThatFits:Zaimplementuj tę metodę, jeśli chcesz, aby widok miał inny domyślny rozmiar niż normalnie podczas operacji zmiany rozmiaru. Na przykład, możesz użyć tej metody, aby zapobiec zmniejszaniu się widoku do punktu, w którym podwidoki nie mogą być poprawnie wyświetlane.

  • layoutSubviews Zaimplementuj tę metodę, jeśli potrzebujesz dokładniejszej kontroli nad układem widoków podrzędnych, niż zapewniają to ograniczenia lub zachowania autorezyzacji.

  • didAddSubview:, willRemoveSubview:Zaimplementuj te metody w razie potrzeby, aby śledzić dodawanie i usuwanie podglądów podrzędnych.

  • willMoveToSuperview:, didMoveToSuperviewZaimplementuj te metody w razie potrzeby, aby śledzić ruch bieżącego widoku w hierarchii widoków.

  • willMoveToWindow:, didMoveToWindowZaimplementuj te metody w razie potrzeby, aby śledzić ruch widoku do innego okna.

Obsługa zdarzeń:

  • touchesBegan:withEvent:, touchesMoved:withEvent:, touchesEnded:withEvent:, touchesCancelled:withEvent:Wdrożenie tych metod, jeśli chcesz obsługiwać zdarzenia dotykowe bezpośrednio. (W przypadku wprowadzania za pomocą gestów użyj rozpoznawania gestów).

  • gestureRecognizerShouldBegin: Zaimplementuj tę metodę, jeśli Twój widok bezpośrednio obsługuje zdarzenia dotyku i może chcieć uniemożliwić dołączonym modułom rozpoznawania gestów wyzwalanie dodatkowych akcji.

Gabriele Petronella
źródło
a co z ramką - (void) setFrame: (CGRect)?
pfrank
cóż, zdecydowanie możesz to zmienić, ale w jakim celu?
Gabriele Petronella
aby zmienić układ / rysunek w dowolnym momencie, gdy rozmiar klatki lub położenie
ulegną
1
O co chodzi layoutSubviews?
Gabriele Petronella
Ze stackoverflow.com/questions/4000664/… , "problem polega na tym, że podwidoki mogą nie tylko zmieniać swój rozmiar, ale mogą animować tę zmianę. Gdy UIView uruchamia animację, nie wywołuje za każdym razem layoutSubviews." Jednak nie testowałem tego osobiście
pfrank
38

To wciąż pojawia się wysoko w Google. Poniżej znajduje się zaktualizowany przykład dla Swift.

didLoadFunkcja pozwala umieścić swój kod niestandardowy inicjalizacji. Jak wspominali inni, didLoadzostanie wywołany, gdy widok zostanie utworzony programowo za pośrednictwem init(frame:)lub gdy deserializator XIB połączy szablon XIB z widokiem za pomocąinit(coder:)

Poza tym : layoutSubviewsi updateConstraintssą wywoływane wielokrotnie w przypadku większości widoków. Jest to przeznaczone do zaawansowanych układów wieloprzebiegowych i dostosowań w przypadku zmiany granic widoku. Osobiście unikam układów wieloprzebiegowych, jeśli to możliwe, ponieważ spalają cykle procesora i sprawiają, że wszystko przyprawia o ból głowy. Dodatkowo umieściłem kod ograniczenia w samych inicjalizatorach, ponieważ rzadko je unieważniam.

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
  }
}
seo
źródło
Czy możesz wyjaśnić, kiedy / dlaczego należy podzielić kod ograniczenia układu między metodę wywoływaną z procesu inicjalizacji a layoutSubviews i updateConstraints? Wygląda na to, że są to wszystkie trzy możliwe lokalizacje, w których można umieścić kod układu. Skąd więc wiesz, kiedy / co / dlaczego podzielić kod układu między te trzy elementy?
Clay Ellis
3
Nigdy nie używam updateConstraints; updateConstraints może być przyjemne, ponieważ wiesz, że hierarchia widoków została w pełni skonfigurowana w init, więc nie możesz zgłosić wyjątku, dodając ograniczenie między dwoma widokami spoza hierarchii :) layoutSubviews nigdy nie powinien mieć modyfikacji ograniczeń; może łatwo spowodować nieskończoną rekursję, ponieważ layoutSubviews jest wywoływany, jeśli ograniczenia są „unieważniane” podczas przebiegu układu. Ręczna konfiguracja układu (tak jak w przypadku bezpośredniego ustawiania ramek, które rzadko trzeba już robić, chyba że ze względu na wydajność) odbywa się w layoutSubviews. Osobiście umieszczam tworzenie ograniczeń w init
seo
Czy w przypadku kodu renderowania niestandardowego powinniśmy zastąpić drawmetodę?
Petrus Theron
14

W dokumentacji Apple znajduje się przyzwoite podsumowanie , które jest dobrze omówione w bezpłatnym kursie Stanforda dostępnym w iTunes. Przedstawiam moją wersję TL; DR tutaj:

Jeśli twoja klasa składa się głównie z podglądów podrzędnych, właściwym miejscem do ich przydzielania są initmetody. W przypadku widoków istnieją dwie różne initmetody, które można wywołać, w zależności od tego, czy Twój widok jest tworzony z kodu, czy z końcówki / serii ujęć. Co mogę zrobić, to napisać własny setupsposób, a następnie wywołać ją z obu initWithFrame:i initWithCoder:metod.

Jeśli rysujesz niestandardowy, rzeczywiście chcesz nadpisać drawRect:w swoim widoku. Jeśli jednak Twój widok niestandardowy jest głównie kontenerem dla podglądów podrzędnych, prawdopodobnie nie będziesz musiał tego robić.

Zastąp tylko, layoutSubViewsjeśli chcesz zrobić coś takiego, jak dodanie lub usunięcie widoku podrzędnego, w zależności od tego, czy jesteś w orientacji pionowej czy poziomej. W przeciwnym razie powinieneś być w stanie zostawić to w spokoju.

dpassage
źródło
Używam twojej odpowiedzi, aby zmienić ramkę subView widoku (który jest awakeFromNib) w layoutSubViews, zadziałało.
samolot
1

layoutSubviews ma na celu ustawienie ramki na widokach potomnych, a nie na samym widoku.

W przypadku UIView, wyznaczony konstruktor jest zwykle initWithFrame:(CGRect)framei powinieneś ustawić ramkę tam (lub w initWithCoder:), prawdopodobnie ignorując przekazaną wartość ramki. Możesz też podać innego konstruktora i ustawić tam ramkę.

proxi
źródło
czy mógłbyś podać więcej szczegółów? Nie znałem twojego znaczenia. Jak ustawić ramkę widoku podrzędnego widoku? widok jestawakeFromNib
samolot
Przewijając do 2016 roku, prawdopodobnie nie powinieneś w ogóle ustawiać ramek i używać autoukładu (ograniczeń). Jeśli widok pochodzi z XIB (lub scenorysu), widok podrzędny powinien być już skonfigurowany.
proxi