UIStackView „Nie można jednocześnie spełnić ograniczeń” dla „zgniecionych” ukrytych widoków

96

Kiedy moje „wiersze” UIStackView są zgniatane, generują AutoLayoutostrzeżenia. Jednak wyświetlają się dobrze i nic więcej nie jest złego poza tego rodzaju logami:

Nie można jednocześnie spełnić ograniczeń. Prawdopodobnie przynajmniej jedno z ograniczeń na poniższej liście jest tym, którego nie chcesz. Spróbuj tego: (1) spójrz na każde ograniczenie i spróbuj dowiedzieć się, którego się nie spodziewasz; (2) znajdź kod, który dodał niechciane ograniczenie lub ograniczenia i napraw go. (Uwaga: jeśli widzisz NSAutoresizingMaskLayoutConstraints, że nie rozumiesz, zapoznaj się z dokumentacją UIViewnieruchomości translatesAutoresizingMaskIntoConstraints) (

Więc nie jestem jeszcze pewien, jak to naprawić, ale wydaje się, że nic nie psuje, poza tym, że jest denerwujący.

Czy ktoś wie, jak to rozwiązać? Co ciekawe, ograniczenia układu dość często są oznaczane jako „ukrywanie UISV” , co oznacza, że ​​być może powinno zignorować minimalne wysokości dla podglądów podrzędnych lub coś w tym przypadku?

Ben Guild
źródło
1
Wygląda na to, że rozwiązano to w iOS11, nie otrzymując tutaj żadnych ostrzeżeń
trapper

Odpowiedzi:

206

Ten problem występuje, ponieważ ustawiając widok podrzędny z wewnątrz UIStackViewna ukryty, najpierw ograniczy jego wysokość do zera, aby go animować.

Otrzymałem następujący błąd:

2015-10-01 11:45:13.732 <redacted>[64455:6368084] Unable to simultaneously satisfy constraints.
    Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "<NSLayoutConstraint:0x7f7f5be18c80 V:[UISegmentedControl:0x7f7f5bec4180]-(8)-|   (Names: '|':UIView:0x7f7f5be69d30 )>",
    "<NSLayoutConstraint:0x7f7f5be508d0 V:|-(8)-[UISegmentedControl:0x7f7f5bec4180]   (Names: '|':UIView:0x7f7f5be69d30 )>",
    "<NSLayoutConstraint:0x7f7f5bdfbda0 'UISV-hiding' V:[UIView:0x7f7f5be69d30(0)]>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x7f7f5be18c80 V:[UISegmentedControl:0x7f7f5bec4180]-(8)-|   (Names: '|':UIView:0x7f7f5be69d30 )>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.

To, co próbowałem zrobić, to umieścić UIViewwewnątrz mojego, UIStackViewktóre zawierało UISegmentedControlwstawkę o 8 punktów na każdej krawędzi.

Kiedy ustawię go na ukryty, będzie próbował ograniczyć widok kontenera do zerowej wysokości, ale ponieważ mam zestaw ograniczeń od góry do dołu, wystąpił konflikt.

Aby rozwiązać ten problem, zmieniłem priorytet ograniczeń 8pt top and bottom z 1000 na 999, aby w UISV-hidingrazie potrzeby ograniczenie mogło mieć priorytet.

liamnichole
źródło
należy zauważyć, że wydaje się, że jeśli masz tylko ograniczenie wysokości lub szerokości i zmniejszysz ich priorytet, to nie zadziała. Musisz usunąć wysokość / szerokość i dodać górne wiodące końcowe dna, a następnie ustawić ich priorytet na niższy i działa
bolnad
4
Zmiana priorytetów też mi pomogła. Usunięcie również wszelkich nadmiernych (wygaszonych) ograniczeń, które zostały przypadkowo skopiowane z nieużywanych klas rozmiaru. WAŻNA WSKAZÓWKA: aby łatwiej debugować te problemy, ustaw ciąg IDENTYFIKATORA na każdym ograniczeniu. Następnie możesz zobaczyć, które ograniczenie było niegrzeczne w komunikacie debugowania.
Womble
3
W moim przypadku wystarczy obniżyć priorytet wysokości i działa.
pixelfreak
Ta wskazówka dotycząca IDENTYFIKATORA jest świetna! Zawsze zastanawiałem się, jak określić ograniczenia w nazwach komunikatów debugowania, zawsze chciałem dodać coś do widoku, a nie samo ograniczenie. Dzięki @Womble!
Ryan
Dzięki! Priorytet od 1000 do 999 załatwił sprawę.Xcode: Wersja 8.3.3 (8E3004b)
Michael Garito
54

Miałem podobny problem, który nie był łatwy do rozwiązania. W moim przypadku miałem osadzony widok stosu w widoku stosu. Wewnętrzny UIStackView miał dwie etykiety i określony niezerowy odstęp.

Gdy wywołasz addArrangedSubview (), automatycznie utworzy ograniczenia podobne do następujących:

V:|[innerStackView]|              | = outerStackView

  V:|[label1]-(2)-[label2]|       | = innerStackView

Teraz, gdy spróbujesz ukryć innerStackView, zostanie wyświetlone ostrzeżenie o niejednoznacznych ograniczeniach.

Aby zrozumieć, dlaczego, zobaczmy najpierw, dlaczego tak się nie dzieje, gdy innerStackView.spacingjest równe 0. Kiedy dzwonisz innerStackView.hidden = true, @liamnichols miał rację ... w outerStackViewmagiczny sposób przechwytuje to połączenie i tworzy ograniczenie ukrywania0 wysokości UISV z priorytetem 1000 (wymagane). Przypuszczalnie ma to na celu umożliwienie animacji elementów w widoku stosu poza widokiem w przypadku wywołania kodu ukrywającego w UIView.animationWithDuration()bloku. Niestety wydaje się, że nie ma sposobu, aby zapobiec dodaniu tego ograniczenia. Niemniej jednak nie zostanie wyświetlone ostrzeżenie „Nie można jednocześnie spełnić ograniczeń” (USSC), ponieważ zachodzą następujące sytuacje:

  1. Wysokość etykiety1 jest ustawiona na 0
  2. odstęp między dwiema etykietami został już zdefiniowany jako 0
  3. Wysokość etykiety2 jest ustawiona na 0
  4. wysokość innerStackView jest ustawiona na 0

Widać wyraźnie, że te 4 ograniczenia mogą być spełnione. Widok stosu po prostu wygładza wszystko do piksela o wysokości 0.

Teraz wraca do buggy przykład, jeśli mamy ustawić spacingdo 2mamy teraz te ograniczenia:

  1. Wysokość etykiety1 jest ustawiona na 0
  2. odstęp między dwiema etykietami został automatycznie utworzony przez widok stosu jako 2 piksele z priorytetem 1000.
  3. Wysokość etykiety2 jest ustawiona na 0
  4. wysokość innerStackView jest ustawiona na 0

Widok stosu nie może jednocześnie mieć wysokości 0 pikseli i mieć zawartość 2 pikseli. Ograniczenia nie mogą być spełnione.

Uwaga: możesz zobaczyć to zachowanie na prostszym przykładzie. Po prostu dodaj UIView do widoku stosu jako uporządkowany widok podrzędny. Następnie ustaw ograniczenie wysokości dla tego UIView z priorytetem 1000. Teraz spróbuj zadzwonić do hide.

Uwaga: z jakiegoś powodu dzieje się tak tylko wtedy, gdy mój widok stosu był podwidokiem UICollectionViewCell lub UITableViewCell. Jednak nadal można odtworzyć to zachowanie poza komórką, wywołując innerStackView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)następną pętlę uruchamiania po ukryciu widoku stosu wewnętrznego.

Uwaga: Nawet jeśli spróbujesz wykonać kod w UIView.performWithoutAnimations, widok stosu nadal doda ograniczenie wysokości 0, co spowoduje ostrzeżenie USSC.


Istnieją co najmniej 3 rozwiązania tego problemu:

  1. Przed ukryciem dowolnego elementu w widoku stosu sprawdź, czy jest to widok stosu, a jeśli tak, zmień na spacing0. Jest to denerwujące, ponieważ musisz odwrócić ten proces (i zapamiętać oryginalne odstępy) za każdym razem, gdy ponownie wyświetlasz zawartość.
  2. Zamiast ukrywać elementy w widoku stosu, wywołaj removeFromSuperview. Jest to jeszcze bardziej irytujące, ponieważ odwracając proces, musisz pamiętać, gdzie włożyć usunięty element. Możesz zoptymalizować, wywołując tylko removeArrangedSubview, a następnie ukrywając, ale nadal trzeba wykonać wiele czynności księgowych.
  3. Zawijaj zagnieżdżone widoki stosu (które mają wartość spacingróżną od zera ) w UIView. Określ co najmniej jedno ograniczenie jako niewymagany priorytet (999 lub mniej). To najlepsze rozwiązanie, ponieważ nie musisz prowadzić żadnej księgowości. W moim przykładzie utworzyłem górne, wiodące i końcowe ograniczenia na poziomie 1000 między widokiem stosu a widokiem opakowania, a następnie utworzyłem ograniczenie 999 od dołu widoku stosu do widoku opakowania. W ten sposób, gdy zewnętrzny widok stosu tworzy ograniczenie zerowej wysokości, ograniczenie 999 zostaje zerwane i nie widać ostrzeżenia USSC. (Uwaga: jest to podobne do rozwiązania w przypadku ustawienia wartości ContentView.translatesAutoResizingMaskToConstraints podklasy UICollectionViewCell nafalse )

Podsumowując, przyczyny takiego zachowania to:

  1. Apple automatycznie tworzy 1000 ograniczeń priorytetowych podczas dodawania zarządzanych widoków podrzędnych do widoku stosu.
  2. Apple automatycznie tworzy dla Ciebie ograniczenie wysokości 0, gdy ukryjesz widok podrzędny widoku stosu.

Gdyby Apple albo (1) pozwolił ci określić priorytet ograniczeń (zwłaszcza odstępników), albo (2) pozwolił ci zrezygnować z automatycznego ograniczenia ukrywania UISV , problem ten zostałby łatwo rozwiązany.

Rozsądne
źródło
6
Dzięki za super dokładne i pomocne wyjaśnienie. To zdecydowanie wydaje się być błędem po stronie Apple z widokami stosu. Zasadniczo ich funkcja „ukrywania” jest niezgodna z funkcją „odstępów”. Jakieś pomysły rozwiązały ten problem lub dodały pewne funkcje, aby zapobiec hakowaniu z dodatkowymi widokami zawierającymi? (Ponownie, świetne zestawienie potencjalnych rozwiązań i zgadzam się co do elegancji nr 3)
Marchy
1
Czy każde UIStackViewdziecko UIStackViewmusi być owinięte w coś, UIViewczy tylko to, które chcesz ukryć / odkryć?
Adrian
1
Wydaje się, że to naprawdę złe przeoczenie ze strony Apple. Zwłaszcza, że ​​ostrzeżenia i błędy związane z używaniem UIStackViewsą zwykle tajemnicze i trudne do zrozumienia.
bompf
To jest ratunek. Miałem problem polegający na tym, że UIStackViews dodane do UITableViewCell powodowały spamowanie dzienników błędów AutoLayout za każdym razem, gdy komórka była ponownie używana. Osadzenie stackView w UIView z priorytetem ograniczenia dolnej kotwicy ustawionym na niski, rozwiązało problem. Powoduje to, że debuger widoku wyświetla elementy stackView jako mające niejednoznaczne wysokości, ale wyświetla się poprawnie w aplikacji, bez spamu w dzienniku błędów. DZIĘKUJĘ CI.
Womble
6

W większości przypadków ten błąd można rozwiązać, obniżając priorytet ograniczeń w celu wyeliminowania konfliktów.

Luciano Almeida
źródło
Co masz na myśli? Wszystkie ograniczenia są względne w ramach widoków, które są zestawione ...
Ben Guild,
Przepraszam, nie rozumiem dobrze twojego pytania, mój angielski jest taki, ale myślę, że ograniczenia są związane z widokiem, z którym jest powiązany, jeśli widok zostanie ukryty, ograniczenia stają się nieaktywne. Nie jestem pewien, czy to twoje wątpliwości, ale mam nadzieję, że mogę pomóc.
Luciano Almeida,
Wydaje się, że pochodzi z momentu zgniatania lub w trakcie pokazywania / ukrywania. W takim przypadku staje się częściowo widoczny. - Może faktycznie konieczne jest przejście i wyeliminowanie jakichkolwiek minimalnych stałych pionowych, ponieważ można je zgnieść do wysokości 0?
Ben Guild
2

Gdy ustawisz widok jako ukryty, program UIStackviewspróbuje go ożywić. Jeśli chcesz uzyskać ten efekt, musisz ustawić odpowiedni priorytet dla ograniczeń, aby nie kolidowały (jak wielu sugerowało powyżej).

Jeśli jednak nie zależy ci na animacji (być może ukrywasz ją w ViewDidLoad), możesz prostą metodą, removeFromSuperviewktóra będzie miała ten sam efekt, ale bez żadnych problemów z ograniczeniami, ponieważ zostaną one usunięte wraz z widokiem.

Oren
źródło
1

W oparciu o odpowiedź @ Senseful, oto rozszerzenie UIStackView do zawijania widoku stosu w widoku i stosowania ograniczeń, które zaleca:

/// wraps in a `UIView` to prevent autolayout warnings when a stack view with spacing is placed inside another stack view whose height might be zero (usually due to `hidden` being `true`).
/// See http://stackoverflow.com/questions/32428210
func wrapped() -> UIView {
    let wrapper = UIView()
    translatesAutoresizingMaskIntoConstraints = false
    wrapper.addSubview(self)

    for attribute in [NSLayoutAttribute.Top, .Left, .Right, .Bottom] {
        let constraint = NSLayoutConstraint(item: self,
                                            attribute: attribute,
                                            relatedBy: .Equal,
                                            toItem: wrapper,
                                            attribute: attribute,
                                            multiplier: 1,
                                            constant: 0)
        if attribute == .Bottom { constraint.priority = 999 }
        wrapper.addConstraint(constraint)
    }
    return wrapper
}

Zamiast dodawać swój stackView, użyj stackView.wrapped().

Ben Packard
źródło
1

Po pierwsze, jak sugerowali inni, upewnij się, że ograniczenia, które możesz kontrolować, tj. Nie ograniczenia właściwe dla UIStackView, mają ustawiony priorytet 999, aby można je było przesłonić, gdy widok jest ukryty.

Jeśli problem nadal występuje, prawdopodobnie jest on spowodowany odstępami w ukrytych StackViews. Moim rozwiązaniem było dodanie UIView jako odstępnika i ustawienie odstępu UIStackView na zero. Następnie ustaw ograniczenia View.height lub View.width (w zależności od stosu pionowego lub poziomego) na odstępy StackView.

Następnie dostosuj priorytety przytulania treści i odporności na kompresję nowo dodanych widoków. Może być również konieczna zmiana dystrybucji nadrzędnego StackView.

Wszystkie powyższe czynności można wykonać w programie Interface Builder. Być może będziesz musiał programowo ukryć / odkryć niektóre z nowo dodanych widoków, aby uniknąć niepożądanych odstępów.

Peter Coyle
źródło
1

Niedawno zmagałem się z błędami automatycznego układu podczas ukrywania pliku UIStackView. Zamiast zajmować się księgowaniem i pakowaniem stosów UIViews, zdecydowałem się stworzyć ujście dla siebie parentStackViewi dla dzieci, które chcę ukryć / odkryć.

@IBOutlet weak var parentStackView: UIStackView!
@IBOutlet var stackViewNumber1: UIStackView!
@IBOutlet var stackViewNumber2: UIStackView!

Oto jak wygląda mój ParentStack w scenorysie:

wprowadź opis obrazu tutaj

Ma 4 dzieci i każde z nich ma w sobie kilka widoków stosu. Gdy ukryjesz widok stosu, jeśli zawiera on elementy interfejsu użytkownika, które są również widokami stosu, zobaczysz strumień błędów automatycznego układu. Zamiast się ukrywać, zdecydowałem się je usunąć.

W moim przykładzie parentStackViewszawiera tablicę 4 elementów: widok górnego stosu, widok stosu, numer 1, widok stosu numer 2 i przycisk zatrzymania. Ich indeksy w arrangedSubviewsto odpowiednio 0, 1, 2 i 3. Kiedy chcę go ukryć, po prostu usuwam go z parentStackView's arrangedSubviewstablicy. Ponieważ nie jest słaby, pozostaje w pamięci i możesz później po prostu umieścić go z powrotem w wybranym indeksie. Nie uruchamiam go ponownie, więc po prostu wisi, dopóki nie będzie potrzebny, ale nie nadyma pamięci.

Więc w zasadzie możesz ...

1) Przeciągnij IBOutlets dla swojego stosu nadrzędnego i dzieci, które chcesz ukryć / odkryć, do scenorysu.

2) Jeśli chcesz je ukryć, usuń stos, który chcesz ukryć z parentStackView's arrangedSubviewstablicy.

3) Zadzwoń za self.view.layoutIfNeeded()pomocą UIView.animateWithDuration.

Zwróć uwagę, że ostatnie dwa stackViews nie są weak. Musisz je mieć pod ręką, gdy je odkryjesz.

Powiedzmy, że chcę ukryć stackViewNumber2:

parentStackView.removeArrangedSubview(stackViewNumber2)
stackViewNumber2.removeFromSuperview()

Następnie animuj to:

UIView.animate(withDuration: 0.25,
               delay: 0,
               usingSpringWithDamping: 2.0,
               initialSpringVelocity: 10.0,
               options: [.curveEaseOut],
               animations: {
                self.view.layoutIfNeeded()
},
               completion: nil)

Jeśli chcesz „odkryć” stackViewNumber2później, możesz po prostu wstawić go do odpowiedniego parentStackView arrangedSubViewsindeksu i animować aktualizację.

parentStackView.removeArrangedSubview(stackViewNumber1)
stackViewNumber1.removeFromSuperview()
parentStackView.insertArrangedSubview(stackViewNumber2, at: 1)

// Then animate it
UIView.animate(withDuration: 0.25,
               delay: 0,
               usingSpringWithDamping: 2.0,
               initialSpringVelocity: 10.0,
               options: [.curveEaseOut],
               animations: {
                self.view.layoutIfNeeded()
},
               completion: nil)

Okazało się, że jest to o wiele łatwiejsze niż prowadzenie księgowości w zakresie ograniczeń, majstrowanie przy priorytetach itp.

Jeśli masz coś, co chcesz domyślnie ukryć, możesz po prostu ułożyć to w scenorysie, usunąć viewDidLoadi zaktualizować bez użycia animacji view.layoutIfNeeded().

Adrian
źródło
1

Wystąpiły te same błędy z osadzonymi widokami stosu, chociaż wszystko działało dobrze w czasie wykonywania.

Rozwiązałem błędy ograniczenia, ukrywając najpierw wszystkie widoki stosu podrzędnego (ustawienie isHidden = true) przed ukryciem widoku stosu nadrzędnego.

Nie wymagało to całej złożoności usuwania widoków podrzędnych i utrzymywania indeksu, gdy trzeba je ponownie dodać.

Mam nadzieję że to pomoże.

Will Stevens
źródło
1

Senseful dostarczyło doskonałej odpowiedzi na przyczynę powyższego problemu, więc przejdę od razu do rozwiązania.

Wszystko, co musisz zrobić, to ustawić priorytet wszystkich ograniczeń stackView na mniej niż 1000 (999 wykona pracę). Na przykład, jeśli stackView jest ograniczony w lewo, w prawo, na górze i na dole do swojego superview, wówczas wszystkie 4 ograniczenia powinny mieć priorytet niższy niż 1000.

Linh Ta
źródło
0

Możliwe, że utworzyłeś ograniczenie podczas pracy z pewną klasą wielkości (np. WCompact hRegular), a następnie utworzyłeś duplikat po przełączeniu na inną klasę wielkości (np. WAny hAny). sprawdź ograniczenia obiektów UI w różnych klasach wielkości i zobacz, czy nie ma anomalii z ograniczeniami. powinieneś zobaczyć czerwone linie wskazujące na kolidujące wiązania. Nie mogę umieścić zdjęcia, dopóki nie zdobędę 10 punktów reputacji. Przepraszam: /

FM
źródło
OK. ale dostałem ten błąd, gdy miałem przypadek, który opisałem drive.google.com/file/d/0B-mn7bZcNqJMVkt0OXVLVVdnNTA/ ...
FM
Tak, zdecydowanie nie widzę żadnego czerwonego koloru, przełączając klasy rozmiarów w konstruktorze interfejsu. Użyłem tylko rozmiaru „Dowolny”.
Ben Guild
0

Chciałem ukryć całe UIStackView na raz, ale otrzymywałem te same błędy co OP, to naprawiło to dla mnie:

for(UIView *currentView in self.arrangedSubviews){
    for(NSLayoutConstraint *currentConstraint in currentView.constraints){
        [currentConstraint setPriority:999];
    }
}
sp00ky
źródło
To nie zadziałało, ponieważ silnik automatycznego układu narzeka, gdy zmieniasz wymagane ograniczenie ( priority = 1000) na niewymagane ( priority <= 999).
Senseful
0

Miałem rząd przycisków z ograniczeniem wysokości. Dzieje się tak, gdy jeden przycisk jest ukryty. Ustawienie priorytetu ograniczenia wysokości tego przycisku na 999 rozwiązało problem.

Niklas
źródło
-2

Ten błąd nie ma nic wspólnego z UIStackView. Dzieje się tak, gdy masz ograniczenia konfliktu z tymi samymi priorytetami. Na przykład, jeśli masz ograniczenie, które określa, że ​​szerokość widoku wynosi 100, a inne ograniczenie w tym samym czasie stwierdza, że ​​szerokość widoku wynosi 25% jego kontenera. Oczywiście istnieją dwa sprzeczne ograniczenia. Rozwiązaniem jest usunięcie jednego z nich.

William Kinaan
źródło
-3

NOP z [mySubView removeFromSuperview]. Mam nadzieję, że mogłoby to komuś pomóc :)

Grégoire GUYON
źródło
Przepraszam, co to jest NOP?
Pang