Gdzie powinienem ustawić ograniczenia automatycznego układu podczas programowego tworzenia widoków

79

Widzę różne przykłady, w których ustawiane są ograniczenia. Niektórzy ustawiali je w viewDidLoad/ loadView(po dodaniu widoku podrzędnego). Inni ustawiają je w metodzie updateViewConstraints, która jest wywoływana przez viewDidAppear.

Kiedy próbuję ustawić ograniczenia updateViewContraints, układ może być niespójny, np. Niewielkie opóźnienie przed pojawieniem się widoku. Ponadto, jeśli używam tej metody, czy powinienem najpierw usunąć istniejące ograniczenia, tj. [self.view [removeConstraints:self.view.constraints]?

Heisenberg
źródło
1
Miałem to samo doświadczenie z updateViewConstraints, więc przestałem go używać. Konfiguruję ograniczenia w viewDidLoad lub w metodzie updateConstraints niestandardowego widoku. Miejmy nadzieję, że ktoś udzieli Ci ostatecznej odpowiedzi.
bilobatum
1
updateViewConstraints: Możesz nadpisać tę metodę w podklasie, aby dodać ograniczenia do widoku lub jego widoków podrzędnych. (z dokumentacji Apple )
testowanie

Odpowiedzi:

108

Skonfigurowałem moje ograniczenia w viewDidLoad/ loadView(celuję w iOS> = 6). updateViewConstraintsprzydaje się do zmiany wartości ograniczeń, np. jeśli jakieś ograniczenie jest zależne od orientacji ekranu (wiem, to zła praktyka) to można to zmienić constantw tej metodzie.

Dodawanie ograniczeń w programie viewDidLoadjest pokazane podczas sesji „Wprowadzenie do automatycznego układu dla iOS i OS X” (WWDC 2012), począwszy od 39:22. Myślę, że to jedna z tych rzeczy, o których mówi się na wykładach, ale nie ląduje w dokumentacji.

AKTUALIZACJA: Zauważyłem wzmiankę o ustawianiu ograniczeń w zarządzaniu zasobami w kontrolerach widoku :

Jeśli wolisz programowo tworzyć widoki, zamiast używać scenorysu, możesz to zrobić, zastępując loadView metodę kontrolera widoku . Twoja implementacja tej metody powinna wykonać następujące czynności:

(...)

3. Jeśli korzystasz z automatycznego układu, przypisz wystarczające ograniczenia do każdego z właśnie utworzonych widoków, aby kontrolować pozycję i rozmiar widoków . W przeciwnym razie zaimplementuj metody viewWillLayoutSubviewsi viewDidLayoutSubviews, aby dostosować klatki podglądów podrzędnych w hierarchii widoków. Zobacz „Zmiana rozmiaru widoków kontrolera widoku”.

UPDATE 2 : Podczas WWDC 2015 Jabłko dał nową wyjaśnienie z updateConstraintsi updateViewConstraintsZalecane użycie:

W rzeczywistości wszystko to jest sposobem na to, aby widoki miały szansę na wprowadzenie zmian w ograniczeniach w samą porę do następnego przejścia układu, ale często nie jest to w rzeczywistości potrzebne.

Cała początkowa konfiguracja ograniczeń powinna mieć miejsce w programie Interface Builder.

Lub jeśli naprawdę okaże się, że musisz programowo alokować swoje ograniczenia, miejsce takie jak viewDidLoad jest znacznie lepsze.

Aktualizuj ograniczenia są tak naprawdę tylko do pracy, która musi być okresowo powtarzana.

Poza tym, zmiana ograniczeń po prostu jest prosta, gdy znajdziesz taką potrzebę; mając na uwadze, że jeśli weźmiesz tę logikę z innym kodem, który jest z nią powiązany i przeniesiesz ją do osobnej metody, która zostanie wykonana w późniejszym czasie, kod stanie się znacznie trudniejszy do naśladowania, więc trudniej będzie ci go utrzymać , innym ludziom będzie dużo trudniej zrozumieć.

Kiedy więc będziesz musiał użyć ograniczeń aktualizacji?

Cóż, wszystko sprowadza się do wydajności.

Jeśli okaże się, że sama zmiana ograniczeń jest zbyt wolna, wówczas ograniczenia aktualizacji mogą być pomocne.

Okazuje się, że zmiana ograniczenia wewnątrz ograniczeń aktualizacji jest w rzeczywistości szybsza niż zmiana ograniczenia w innym czasie.

Dzieje się tak dlatego, że silnik jest w stanie traktować wszystkie zmiany ograniczeń, które mają miejsce w tym przebiegu, jako partię.

Arek Holko
źródło
2
+1 za to jako najlepsza odpowiedź. Apple zaleca loadView jako właściwe miejsce do ustawiania początkowych ograniczeń i nie wymaga to dodatkowej flagi BOOL w metodzie updateConstraints (co wydaje się po prostu hackem).
awolf
4
Moim zdaniem widok powinien odpowiadać za ograniczenia, a nie administrator widoku. W wielu przypadkach kontroler widoku nawet nie wie, jakie są wszystkie elementy w widoku (na przykład statyczne etykiety w komórce widoku tabeli).
dasdom
1
@dasdom W jaki sposób widok może kontrolować swoje relacje z innymi widokami? Musisz ustawić ograniczenia, jak @"|-[button1]-[button2]-|"w ViewController, prawda? Czy jest inny sposób?
Joseph,
@ Kasper W większości przypadków mam podklasę UIView, która jest widokiem kontrolera widoku. W tym widoku znajdują się podglądy i ograniczenia dla widoków podrzędnych.
dasdom
3
Dlaczego zmiana ograniczeń w programie jest złą praktyką updateViewConstraints?
testowanie
33

Zalecam utworzenie BOOL i ustawienie ich w -updateConstraintsUIView (lub -updateViewConstraints, dla UIViewController).

-[UIView updateConstraints]: (apple docs)

Widoki niestandardowe, które same konfigurują ograniczenia, powinny to zrobić, zastępując tę ​​metodę.

Oba -updateConstraintsi -updateViewConstraintsmogą być wywoływane wiele razy w trakcie trwania widoku. (Na przykład wywołanie setNeedsUpdateConstraintswidoku spowoduje to, że tak się stanie). W rezultacie musisz upewnić się, że nie tworzysz i nie aktywujesz powielonych ograniczeń - albo używając BOOL, aby wykonać tylko określoną konfigurację ograniczenia, albo upewniając się, że dezaktywować / usunąć istniejące ograniczenia przed utworzeniem i aktywacją nowych.

Na przykład:

  - (void)updateConstraints {  // for view controllers, use -updateViewConstraints

         if (!_hasLoadedConstraints) {
              _hasLoadedConstraints = YES;
             // create your constraints
         }
         [super updateConstraints];
    }

Pozdrawiam @fresidue w komentarzach za wskazanie, że dokumentacja Apple zaleca dzwonienie superjako ostatni krok. Jeśli wywołasz superprzed wprowadzeniem zmian w niektórych ograniczeniach, możesz napotkać wyjątek czasu wykonywania (awarię).

BooRanger
źródło
7
Nie jestem pewien, czy ma to jakiekolwiek znaczenie pragmatycznie, ale w dokumentacji jest napisane „Ważne: wywołaj [super updateConstraints] jako ostatni krok w implementacji”.
fresidue
Zgodnie z dokumentacją, jeśli zmienisz widok w czasie wykonywania i unieważnisz swoje ograniczenia, system natychmiast usunie to ograniczenie i wywoła setNeedsUpdateConstraints. Przed wykonaniem nowego układu system wywołuje updateConstraints, w którym można dostosować unieważniony układ. W związku z tym prawdopodobnie nie ustawiłbym flagi dla tej metody, która mogłaby uniemożliwić systemowi wywołanie jej.
smileBot
1
@cocoanutmobile Jeśli używasz flagi BOOL zgodnie z sugestią w tej odpowiedzi, ma to na celu zapobieganie dodawaniu niektórych ograniczeń więcej niż jeden raz. To bardzo dobra rzecz. Inną alternatywą jest po prostu zapisanie odniesienia do wszelkich utworzonych ograniczeń, a następnie usunięcie (dezaktywacja) wszystkich przed utworzeniem i aktywacją nowych. Takie podejście przyniesie jednak gorszą wydajność, zwłaszcza jeśli stare i nowe ograniczenia są dokładnie takie same.
smileyborg
2
@cocoanutmobile Zauważ też, że flaga BOOL tutaj nie przeszkadza systemowi w wywołaniu -updateConstraintsani niczego - nadal byłby wywoływany, a ty nadal dzwonisz [super updateConstraints].
smileyborg
1
Jak wspomniano w @fresidue, pamiętaj, aby wywołać superna samym końcu implementację -updateConstraintslub -updateViewConstraints. Zobacz ten komentarz, aby uzyskać więcej informacji.
smileyborg
4

Należy to zrobić w ViewDidLoad, zgodnie z filmem WWDC firmy Apple i dokumentacją.

Nie mam pojęcia, dlaczego ludzie polecają updateConstraints. Jeśli zrobisz to w updateConstraints, napotkasz problemy z NSAutoresizingMaskLayoutConstraint z automatyczną zmianą rozmiaru, ponieważ Twoje widoki uwzględniły już automatyczne maski. Aby działało, musisz je usunąć w updateConstraints.

UpdateConstraints powinny służyć właśnie do tego, gdy musisz je „zaktualizować”, wprowadzić zmiany itp. Od początkowej konfiguracji.

BluelineCoding
źródło
1
Czy możesz dodać linki do filmu i dokumentacji, o której się tu odwołujesz.
Shivam Pokhriyal,
2

Zrób to na widoku podwidoków układu

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
}
Zestaw
źródło
1

Mam to rozwiązanie, aby zmienić ograniczenia, zanim ci, którzy są w storyboardzie, zostaną załadowani. To rozwiązanie usuwa wszelkie opóźnienia po załadowaniu widoku.

-(void)updateViewConstraints{

    dispatch_async(dispatch_get_main_queue(), ^{

            //Modify here your Constraint -> Activate the new constraint and deactivate the old one

            self.yourContraintA.active = true;
            self.yourContraintB.active= false;
            //ecc..
           });

    [super updateViewConstraints]; // This must be the last thing that you do here -> if! ->Crash!
}
Davide
źródło
1

Możesz je ustawić w viewWillLayoutSubviews: too:

 override func viewWillLayoutSubviews() {

    if(!wasViewLoaded){
        wasViewLoaded = true

        //update constraint

        //also maybe add a subview            
    }
}
Andre Simon
źródło
0

To zadziałało dla mnie:

Swift 4.2

override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

// Modify your constraints in here

  ...

}

Chociaż szczerze nie jestem pewien, czy warto. Wydaje się, że ładuje się nieco wolniej niż w viewDidLoad (). Chciałem tylko usunąć je z tego ostatniego, ponieważ robi się ogromne.

Michele Dall'Agata
źródło
0

Poniższy przykład to przekazanie dowolnego widoku innej klasie. stworzyć mój widok z storyboardu

Swift 5.0

    override func viewWillAppear(_ animated: Bool) {
        
      super.viewWillAppear(animated) 
        DispatchQueue.main.async {
            self.abcInstance = ABC(frame: self.myView.frame)
          } 
      }

 

Jeśli przegapisz DispatchQueue.main.async, aktualizacja ograniczeń w viewWillAppear zajmie trochę czasu. Utwórz myView w scenorysie i podaj ograniczenia takie same jak szerokość i wysokość ekranu, a następnie spróbuj wydrukować ramkę myView. poda dokładną wartość w DispatchQueue.main.async lub w viewDidAppear, ale nie poda dokładnej wartości w viewWillAppear bez DispatchQueue.main.async.

Shrikant Phadke
źródło