Jak mogę zmienić priorytet ograniczeń w czasie wykonywania

86

Mam widok, który ma dynamiczną wysokość i próbuję zmienić ten priorytet wysokości widoku w czasie wykonywania.

Oto moja część kodu;

if (index == 0) {

    surveyViewHeightConstraint.constant = 0;
    surveyViewHeightConstraint.priority = 1000;

} else if (index == 1) {

    surveyViewHeightConstraint.constant = 163;
    surveyViewHeightConstraint.priority = 500;

}

Zmieniam indeks za pomocą przycisku. Kiedy uruchamiam ten kod, pojawia się ten błąd:

*** Assertion failure in -[NSLayoutConstraint setPriority:], /SourceCache/Foundation/Foundation-1141.1/Layout.subproj/NSLayoutConstraint.m:174

Jaki jest mój błąd tutaj?

Le'Kirdok
źródło

Odpowiedzi:

172

Jak podano w opisie NSLayoutConstraintklasy :

Priorytety nie mogą zmieniać się z niewymaganego na wymagany lub z wymaganego na niewymagany. Wyjątek zostanie zgłoszony, jeśli priorytet NSLayoutPriorityRequiredw OS X lub UILayoutPriorityRequirediOS zostanie zmieniony na niższy priorytet lub jeśli niższy priorytet zostanie zmieniony na wymagany priorytet po dodaniu ograniczeń do widoku. Zmiana z jednego opcjonalnego priorytetu na inny opcjonalny priorytet jest dozwolona nawet po zainstalowaniu ograniczenia w widoku.

Użyj priorytetu 999 zamiast 1000. Z technicznego punktu widzenia nie będzie to absolutnie wymagane, ale będzie to wyższy priorytet niż cokolwiek innego.

Cyrille
źródło
4
Dziękuję za miłą odpowiedź, ale jeśli użyję 999 zamiast 1000, nie mogę ukryć mojego widoku, przypisując 0 do ograniczenia wysokości.
Le'Kirdok
To kolejny problem. Możesz mieć inne sprzeczne ograniczenia. Jeśli Twój kod już się nie zawiesza, ale animacja widoku jest nadal nieprawidłowa, oznacz to pytanie jako rozwiązane; następnie otwórz kolejny.
Cyrille
nice one, to nie ma problemu, aby używać różnych wartości (999, 900, 500, itp) :)
user924
7
Poważnie? Czy jest coś, co działa normalnie w programowaniu na iOS? Tak wiele rzeczy trzeba było zrobić ręcznie i / lub trzeba było zaimplementować mnóstwo kodu obejścia powodującego błędy systemowe lub nieobsługiwany. Przepraszamy za nie konstruktywny komentarz.
Luten
6
Wygląda na to, że to ograniczenie zostało usunięte w iOS 13. Próbowałem zmienić ograniczenie z requiredna defaultLowi zadziałało. Ten sam kod, który został użyty do awarii na iOS 12.
Steven Vandeweghe
52

Chcę dodać mały dodatek do odpowiedzi Cyrille'a.

Jeśli tworzysz ograniczenie w kodzie, upewnij się, że ustawiłeś jego priorytet przed uaktywnieniem. Na przykład:

surveyViewHeightConstraint = [NSLayoutConstraint constraintWithItem:self
                                               attribute:NSLayoutAttributeHeight
                                               relatedBy:NSLayoutRelationEqual
                                                  toItem:self.superview
                                               attribute:NSLayoutAttributeHeight
                                              multiplier:1
                                                constant:0];
surveyViewHeightConstraint.active = YES;
surveyViewHeightConstraint.priority = 999;

Spowodowałoby to wyjątek czasu wykonywania.

Mutowanie priorytetu z wymaganego na nie w zainstalowanym ograniczeniu (lub odwrotnie) nie jest obsługiwane.

Prawidłowa kolejność to:

surveyViewHeightConstraint.priority = 999;
surveyViewHeightConstraint.active = YES;

Dla wersji Swift 4+

constraintName.priority = UILayoutPriority(rawValue: 999)
Vipin
źródło
13

Wersja Swift:

myContraint.priority = UILayoutPriority(999.0)
Alex
źródło
8

Sposób, w jaki zawsze sobie z tym radziliśmy, polega na tym, że nie zmieniamy stałej ograniczenia, tylko priorytet. Na przykład w twojej sytuacji obowiązują dwa ograniczenia wysokości.

 heightConstraintOne (who's height is set in the storyboard at 150, and priority set at 750)
 heightConstraintTwo (who's height is set in the storyboard at 0, and priority set at 250)

jeśli chcesz ukryć widok, musisz:

heightConstraintTwo.priority = 999;

podobnie, jeśli chcesz pokazać widok:

heightConstraintTwo.priority = 250;
J Andrew McCormick
źródło
5
Fajnie,
rozumiem
3

Mam nadzieję, że ten scenariusz komuś pomoże: miałem dwa ograniczenia, że ​​były przeciwne do siebie, to znaczy nie mogły być spełnione w tym samym czasie, w kreatorze interfejsów jeden z nich miał priorytet 1000, a drugi mniej niż 1000. kiedy zmieniałem je w kodzie, miałem ten błąd:

Zamykanie aplikacji z powodu nieprzechwyconego wyjątku „NSInternalInconsistencyException”, powód: „Mutowanie priorytetu z wymaganego na nie w zainstalowanym ograniczeniu (lub odwrotnie) nie jest obsługiwane. Przekroczyłeś priorytet 250, a istniejący priorytet to 1000. ”

następnie po prostu zainicjowałem je w funkcji viewDidLoad z wartościami .defaultHigh i .defaultLow i to rozwiązało mój problem.


źródło
1

Według NSLayoutConstraints classśrodka UIKit Module

Jeśli poziom priorytetu ograniczenia jest mniejszy niż UILayoutPriorityRequired, jest opcjonalny. Ograniczenia o wyższym priorytecie są spełnione przed ograniczeniami o niższym priorytecie. Zadowolenie z ograniczeń to nie wszystko albo nic. Jeśli ograniczenie „a == b” jest opcjonalne, oznacza to, że spróbujemy zminimalizować „abs (ab)”. Ta właściwość może być modyfikowana tylko w ramach początkowej konfiguracji lub gdy jest opcjonalna. Po dodaniu ograniczenia do widoku zostanie zgłoszony wyjątek, jeśli priorytet zostanie zmieniony z / na NSLayoutPriorityRequired.

Przykład: - UIButtonograniczenia o różnych priorytetach -

 func setConstraints() {
        buttonMessage.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint(item: buttonMessage, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1.0, constant: -10).isActive = true

        let leading = NSLayoutConstraint(item: buttonMessage, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1.0, constant: 10)

        leading.isActive = true


        let widthConstraint = NSLayoutConstraint(item: buttonMessage, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: 100)

        let heightConstraint = NSLayoutConstraint(item: buttonMessage, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: 50)


        let trailingToSuperView = NSLayoutConstraint(item: buttonMessage, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: 0)

        trailingToSuperView.priority = 999
        trailingToSuperView.isActive = true

        //leading.isActive = false//uncomment & check it will align to right of the View

        buttonMessage.addConstraints([widthConstraint,heightConstraint])

         }  
Jacek
źródło
1
Nie, nigdy nie powinieneś tworzyć ograniczeń w viewDidLayoutSubviews. Tę metodę można wywołać kilka razy, co może spowodować awarię aplikacji, jeśli utworzysz tam zduplikowane ograniczenia.
mph
0

Miałem ten sam problem. Ponieważ niektóre odpowiedzi wymienione powyżej w iOS 13 zmiana priorytetu będą działać dobrze, jednak w iOS 12 doprowadzi to do awarii.

Udało mi się rozwiązać ten problem, tworząc IBOutlet NSLayoutConstraint do tego konkretnego ograniczenia w Storyboard, zachowując jego priorytet na 1000, poniżej znajduje się kod poprawki.

if (Condition) {
    surveyViewHeightConstraint.constant = 0;
    surveyViewHeightConstraint.isActive = false;
} else if (index == 1) {
    surveyViewHeightConstraint.constant = 163;
    surveyViewHeightConstraint.isActive = True;
}

Mam nadzieję że to pomoże!!! Twoje zdrowie

Venkat057
źródło
-3

Teraz możesz, po prostu skorzystaj z połączenia IBOutlet dla swoich ograniczeń, a następnie użyj tego kodu.

self.webviewHeight.priority = UILayoutPriority(rawValue: 1000)
Kishore Kumar
źródło