Co to jest NSLayoutConstraint „UIView-Encapsulated-Layout-Height” i jak powinienem zmusić go do ponownego obliczenia?

262

Mam UITableViewdziałający pod iOS 8 i używam automatycznych wysokości komórek od ograniczeń w serii ujęć.

Jedna z moich komórek zawiera jedną UITextViewi muszę ją skurczyć i rozwinąć na podstawie danych wprowadzonych przez użytkownika - dotknij, aby zmniejszyć / rozwinąć tekst.

Robię to, dodając ograniczenie czasu wykonywania do widoku tekstu i zmieniając stałą ograniczenia w odpowiedzi na zdarzenia użytkownika:

-(void)collapse:(BOOL)collapse; {

    _collapsed = collapse;

    if(collapse)
        [_collapsedtextHeightConstraint setConstant: kCollapsedHeight]; // 70.0
    else
        [_collapsedtextHeightConstraint setConstant: [self idealCellHeightToShowFullText]];

    [self setNeedsUpdateConstraints];

}

Ilekroć to robię, pakuję go w tableViewaktualizacje i dzwonię [tableView setNeedsUpdateConstraints]:

[tableView beginUpdates];

[_briefCell collapse:!_showFullBriefText];

[tableView setNeedsUpdateConstraints];
// I have also tried 
// [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationTop];
// with exactly the same results.

[tableView endUpdates];

Kiedy to robię, moja komórka rozszerza się (i animuje podczas tej operacji), ale pojawia się ostrzeżenie o ograniczeniach:

2014-07-31 13:29:51.792 OneFlatEarth[5505:730175] 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:0x7f94dced2b60 V:[UITextView:0x7f94d9b2b200'Brief text: Lorem Ipsum i...'(388)]>",

    "<NSLayoutConstraint:0x7f94dced2260 V:[UITextView:0x7f94d9b2b200'Brief text: Lorem Ipsum i...']-(15)-|   (Names: '|':UITableViewCellContentView:0x7f94de5773a0 )>",

    "<NSLayoutConstraint:0x7f94dced2350 V:|-(6)-[UITextView:0x7f94d9b2b200'Brief text: Lorem Ipsum i...']   (Names: '|':UITableViewCellContentView:0x7f94de5773a0 )>",

    "<NSLayoutConstraint:0x7f94dced6480 'UIView-Encapsulated-Layout-Height' V:[UITableViewCellContentView:0x7f94de5773a0(91)]>"
 )

Will attempt to recover by breaking constraint 

<NSLayoutConstraint:0x7f94dced2b60 V:[UITextView:0x7f94d9b2b200'Brief text: Lorem Ipsum i...'(388)]>

388 to moja obliczona wysokość, pozostałe ograniczenia UITextViewsą moje od Xcode / IB.

Ostatnia przeszkadza mi - zgaduję, że UIView-Encapsulated-Layout-Heightjest to obliczona wysokość komórki, gdy jest ona renderowana po raz pierwszy - (ustawiłem moją UITextViewwysokość na> = 70,0), jednak nie wydaje się słuszne, aby to pochodne ograniczenie nadpisało zaktualizowano cnstraint użytkownika.

Co gorsza, chociaż kod układu mówi, że próbuje przełamać moje ograniczenie wysokości, nie robi tego - kontynuuje ponowne obliczanie wysokości komórki i wszystko rysuje tak, jak bym chciał.

Czym więc jest NSLayoutConstraint UIView-Encapsulated-Layout-Height(domyślam się, że jest to obliczona wysokość dla automatycznego określania rozmiaru komórki) i jak powinienem zmusić ją do ponownego przeliczenia?

Rog
źródło
3
krzyż wysłany na fora programistów
Rog
3
Rozwiązałem podobny problem w następujący sposób i działa na iOS 7/8. 1) Obniż jeden z priorytetów ograniczeń do 750. Spróbowałbym 1. lub 2. 2) W podklasie komórki w zestawie awakeFromNib self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight;. Myślę, że początkowo ustawienie maski automatycznego zatrzymywania powstrzymuje dodawanie ostatniego ograniczenia. Znalazłem to rozwiązanie tutaj: github.com/wordpress-mobile/WordPress-iOS/commit/…
Jesse
3
@RogerNolan, jakieś wiadomości? Znalazłem ten sam problem podczas gry z automatycznym układem w Konstruktorze interfejsów. Niektóre komórki powodują ten problem, niektóre nie.
orkenstein,
3
Nie wydaje mi się, aby dodanie znaku automatycznej zmiany rozmiaru było dobrym rozwiązaniem.
Rog
1
@RogerNolan czy generujesz tę komórkę w IB przed wykonaniem tych zmian układu? Debugowałem ten sam problem, ale nie dodawałem żadnych dodatkowych ograniczeń. Udało mi się stłumić ostrzeżenie, odbudowując mój widok od zera, a kiedy zmieniłem dwa pliki scenariuszy, jedyną różnicą było to, że wersja z ostrzeżeniami nie zawierała linii <rect key="frame" x="0.0" y="0.0" width="600" height="110"/>w definicji, co prowadzi mnie do przekonania, że ​​to błąd IB . Przynajmniej moja była zresztą.
Ell Neal

Odpowiedzi:

301

Spróbuj obniżyć swój priorytet _collapsedtextHeightConstraintdo 999. W ten sposób UIView-Encapsulated-Layout-Heightograniczenie dostarczone przez system zawsze ma pierwszeństwo.

Opiera się na tym, co wrócisz -tableView:heightForRowAtIndexPath:. Pamiętaj o zwróceniu właściwej wartości i własnego ograniczenia, a wygenerowane powinno być takie samo. Niższy priorytet dla własnego ograniczenia jest potrzebny tylko tymczasowo, aby zapobiec konfliktom podczas animacji zwinięcia / rozwinięcia w locie.

Ortwin Gentz
źródło
74
Osiągnęłoby to dokładnie odwrotność tego, czego chcę. UIView-Encapsulated-Layout-Height jest niepoprawna - należy do poprzedniego układu.
Rog
7
UIView-Encapsulated-Layout-HeightOgraniczeniem jest dodawana przez UITableView Gdy szczyty są ustalone. Wysokość obliczam na podstawie systemLayoutSizeFittingSizecontentView. Tutaj UIView-Encapsulated-Layout-Heightnie ma znaczenia. Następnie tableView ustawia contentSize jawnie na wartość zwracaną przez heightForRowAtIndexPath:. W takim przypadku poprawne jest obniżenie priorytetu naszych ograniczeń niestandardowych, ponieważ ograniczenie tableView musi mieć pierwszeństwo po obliczeniu RowHeights.
Ortwin Gentz
8
@OrtwinGentz: Nadal nie rozumiem, że poprawne jest obniżenie priorytetu naszych ograniczeń niestandardowych, ponieważ ograniczenie tableView musi mieć pierwszeństwo po obliczeniu RowHeights . Chodzi o to, że UIView-Encapsulated-Layout-Heightnie mam racji, jeśli nie obniżę priorytetu ...
testowanie
38
Uważam, że unikanie konfliktu nie rozwiązuje rzeczywistego problemu - chociaż nadal wydaje się to błędem Apple, myślę, że to prawdopodobnie właściwe rozwiązanie. Zwłaszcza w świetle faktu, że Apple ponownie oblicza to ograniczenie i po wydrukowaniu błędu wszystko układa się poprawnie.
Rog
9
-1 dla tej odpowiedzi, ponieważ zakłada, że ​​dodawane ograniczenie jest poprawne. Dodane UIView-Encapsulated-Layout-Widthw moim przypadku jest po prostu błędne, ale wydaje się, że jest ono preferowane w stosunku do moich wyraźnych ograniczeń w czasie wykonywania.
promień
68

Mam podobny scenariusz: widok tabeli z jedną komórką wiersza, w którym znajduje się kilka wierszy obiektów UILabel. Używam iOS 8 i autolayout.

Kiedy się obróciłem, otrzymałem niewłaściwy system obliczoną wysokość wiersza (43,5 to znacznie mniej niż rzeczywista wysokość). To wygląda jak:

"<NSLayoutConstraint:0x7bc2b2c0 'UIView-Encapsulated-Layout-Height' V:[UITableViewCellContentView:0x7bc37f30(43.5)]>"

To nie tylko ostrzeżenie. Układ mojej komórki widoku tabeli jest okropny - cały tekst nakłada się na jedną linię tekstu.

Zaskakuje mnie, że następujący wiersz „naprawia” mój problem magicznie (autoukład nic nie narzeka i otrzymuję to, czego oczekuję na ekranie):

myTableView.estimatedRowHeight = 2.0; // any number but 2.0 is the smallest one that works

z tą linią lub bez:

myTableView.rowHeight = UITableViewAutomaticDimension; // by itself this line only doesn't help fix my specific problem
Złoty kciuk
źródło
8
Wreszcie! To poprawna odpowiedź. W sesji WWDC wspomniano, że jeśli użyjesz automatycznego określania wysokości wiersza, to ustaw szacunkową wysokość lub inne złe rzeczy. (Tak, zdarzyły się naprawdę nieprzyjemne rzeczy Apple)
Abdalrahman Shatou
50
FWIW, dodanie oszacowania nie miało dla mnie żadnej różnicy.
Benjohn
3
Heh Znów wróciłem do tej samej odpowiedzi i zacząłem ją realizować z radością i nadzieją. Po raz kolejny nic dla mnie nie zmieniło :-)
Benjohn
3
Oszacowania zwrócone przez tableView: oszacowanyHeightForRowAtIndexPath: MUSI być co najmniej tak duży jak komórka. W przeciwnym razie obliczona wysokość stołu będzie mniejsza niż wysokość rzeczywista, a stół może się przewinąć w górę (np. Po cofnięciu segue powróci do stołu). UITableViewAutomaticDimension nie powinien być używany.
Matt
1
Zauważ, że im niższa, tym estimatedRowHeightczęściej cellForRowAtIndexPathnazywa się początkowo. wysokość widoku tabeli podzielona przez szacunkowe czasy RowHeight, a dokładnie. Na 12-calowym iPadzie Pro może to być liczba w tysiącach, co utrudni źródło danych i może spowodować znaczne opóźnienie.
Mojo66
32

Byłem w stanie uzyskać ostrzeżenie, aby zniknęło, określając priorytet jednej z wartości w ograniczeniu, które komunikaty ostrzegawcze mówiły, że musiało się złamać (poniżej "Will attempt to recover by breaking constraint"). Wygląda na to, że o ile ustawię priorytet na coś większego niż49 , ostrzeżenie zniknie.

Dla mnie oznaczało to zmianę mojego ograniczenia, ostrzeżenie mówiło, że próbuje się złamać:

@"V:|[contentLabel]-[quoteeLabel]|"

do:

@"V:|-0@500-[contentLabel]-[quoteeLabel]|"

W rzeczywistości mogę dodać priorytet do dowolnego elementu tego ograniczenia i zadziała. Nie ma znaczenia, który z nich. Moje komórki osiągają odpowiednią wysokość, a ostrzeżenie nie jest wyświetlane. Na przykład Roger, spróbuj dodać @500zaraz po 388ograniczeniu wartości wysokości (np 388@500.).

Nie jestem do końca pewien, dlaczego to działa, ale przeprowadziłem trochę dochodzenia. W wyliczeniu NSLayoutPriority wydaje się, że NSLayoutPriorityFittingSizeCompressionpoziom priorytetu to 50. Dokumentacja dla tego poziomu priorytetu mówi:

Kiedy wysyłasz wiadomość fitSize do widoku, obliczany jest najmniejszy rozmiar, który jest wystarczająco duży dla zawartości widoku. Jest to poziom priorytetu, przy którym widok chce być możliwie najmniejszy w tym obliczeniu. Jest dość nisko. Zasadniczo nie jest właściwe ograniczenie tego dokładnie priorytetu. Chcesz być wyższy lub niższy.

Dokumentacja dla wskazanej fittingSizewiadomości brzmi:

Minimalna wielkość widoku, która spełnia ograniczenia, które posiada. (tylko czytać)

AppKit ustawia tę właściwość na najlepszy dostępny rozmiar dla widoku, biorąc pod uwagę wszystkie ograniczenia, jakie posiada i jego widoki podrzędne, i spełniając preferencję, aby widok był jak najmniejszy. Wartości wielkości w tej właściwości nigdy nie są ujemne.

Nie wykopałem poza tym, ale wydaje się mieć sens, że ma to coś wspólnego z tym, gdzie leży problem.

Jeff Bowen
źródło
Thatks Jeff. Nadal wydaje mi się, że to błąd. Apple jednak nie odpowiedział na rdar :-(
Rog
13

99,9% czasu, przy użyciu niestandardowych komórek lub nagłówków, wszystkie konflikty UITableViews występują, gdy tabela ładuje się po raz pierwszy. Po załadowaniu zwykle nie zobaczysz ponownie konfliktu.

Dzieje się tak, ponieważ większość programistów zazwyczaj używa stałej wysokości lub pewnego rodzaju ograniczenia kotwiczenia do układania elementu w komórce / nagłówku. Konflikt występuje, ponieważ przy UITableViewpierwszym ładowaniu / rozkładaniu wysokość jego komórek jest ustawiana na 0. To oczywiście koliduje z własnymi ograniczeniami. Aby rozwiązać ten problem, po prostu ustaw dowolne ograniczenia stałej wysokości na niższy priorytet ( .defaultHigh). Przeczytaj uważnie komunikat konsoli i sprawdź, jakie ograniczenie system układu postanowił złamać. Zwykle należy zmienić priorytet. Możesz zmienić priorytet w ten sposób:

let companyNameTopConstraint = companyNameLabel.topAnchor.constraint(equalTo: companyImageView.bottomAnchor, constant: 15)
    companyNameTopConstraint.priority = .defaultHigh

NSLayoutConstraint.activate([
            companyNameTopConstraint,
           the rest of your constraints here
            ])
JESTEŚ SZALONY
źródło
1
Piękne wyjaśnienie.
Glenn
12

Udało mi się rozwiązać ten problem poprzez usunięcie nieprawdziwy cell.layoutIfNeeded(), że miałem w moim tableView„s cellForRowAtmetody.

Clifton Labrum
źródło
1
Tak, to również rozwiązało problem. Robiłem ograniczenia układu kodu, więc początkowo myślałem, że coś mogę przegapić. Dzięki
John
1
To samo! Dzięki!
Andrey Chernukha
7

Zamiast informować widok tabeli, aby zaktualizował ograniczenia, spróbuj ponownie załadować komórkę:

[tableView beginUpdates];

[_briefCell collapse:!_showFullBriefText];

[tableView reloadRowsAtIndexPaths:@[[tableView indexPathForCell:_briefCell]] withRowAnimation:UITableViewRowAnimationNone];

[tableView endUpdates];

UIView-Encapsulated-Layout-Height jest prawdopodobnie wysokością widoku tabeli obliczoną dla komórki podczas ładowania początkowego, na podstawie ograniczeń komórki w tym czasie.

Austin
źródło
Powinienem był powiedzieć w swoim pytaniu, próbowałem tego i to nie działa. Zgadzam się z twoimi przypuszczeniami na temat UIView-Encapsulated-Layout-Height
Rog
6
Zdobądź nagrodę za przynajmniej przesłanie odpowiedzi. Wydaje się, że SO po prostu pozwoli odparować inaczej.
Rog
6

Inna możliwość:

Jeśli używasz automatycznego układu do obliczania wysokości komórki (wysokość contentView, przez większość czasu jak poniżej), a jeśli masz separator uitableview, musisz dodać wysokość separatora, aby zwrócić wysokość komórki. Gdy uzyskasz prawidłową wysokość, nie pojawi się ostrzeżenie o autoukładzie.

- (CGFloat)calculateHeightForConfiguredSizingCell:(UITableViewCell *)sizingCell {
   [sizingCell setNeedsLayout];
   [sizingCell layoutIfNeeded];
   CGSize size = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
   return size.height; // should + 1 here if my uitableviewseparatorstyle is not none

}
Cullen SUN
źródło
Pomogło mi to w przypadku, gdy otrzymałem niejednoznaczność wysokości układu w dość złożonym układzie komórek tylko w niektórych symulatorach (iPad 6 Plus). Wydaje mi się, że z powodu pewnych wewnętrznych błędów zaokrąglania treść jest nieco ściśnięta, a jeśli ograniczenia nie są przygotowane do ściśnięcia, mam dwuznaczności. Więc zamiast wrócić UITableViewAutomaticDimensionna heightForRowAtIndexPathwrócę [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize] + 0.1
Leo
Mam na myśli oczywiście[sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height + 0.1
Leo
Oszałamiająco; usunięcie separatora sprawiło, że mój stół się zachował, więc dzięki.
royalmurder
5

Jak wspomniał w komentarzu Jesse , działa to dla mnie:

self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight;

Do Twojej wiadomości, ten problem nie występuje w iOS 10.

Phu Nguyen
źródło
2
W Swift 4.2: self.contentView.autoresizingMask = [.fiętkiWysokość]
airowe
4

Wystąpił ten błąd podczas używania UITableViewAutomaticDimension i zmiany ograniczenia wysokości w widoku wewnątrz komórki.

W końcu doszedłem do wniosku, że było to spowodowane tym, że stała wartość ograniczenia nie była zaokrąglana w górę do najbliższej liczby całkowitej.

let neededHeight = width / ratio // This is a CGFloat like 133.2353
constraintPictureHeight.constant = neededHeight // Causes constraint error
constraintPictureHeight.constant = ceil(neededHeight) // All good!
Che
źródło
2
To był komentarz, który przykuł mnie do mojego problemu. Mam komórkę obrazu, która ładuje się dynamicznie (rośnie i kurczy się) oraz widok tabeli z oszacowanymRowHeight = 50 i rowHeight = UITableViewAutomaticDimension. Nadal łamałem ograniczenia, mimo że widok tabeli miał odpowiednią wysokość. Okazało się, że separatory miały wysokość 0,3333 i właśnie to spowodowało złamanie mojego ograniczenia rozmiaru obrazu w komórce, która ma zostać złamana. Po wyłączeniu separatorów wszystko było w porządku. Dzięki Che za danie mi tego, czego mam szukać.
migs647,
1
W takim przypadku stwórz dodatkowe ograniczenie, np. BottomMargin> = view.bottomMargin+1@900. AutoLayout próbuje pomieścić dodatkowy 1 punkt, zmienia rozmiar komórki, myli się z powodu wysokości separatora, próbuje przełamać / rozluźnić niektóre ograniczenia, znajduje jeden @ 900 i odrzuca to. Otrzymasz pożądany układ bez ostrzeżeń.
Anton
ocal mój dzień. w każdym razie kilka godzin
Anton Tropashko
1

Dostosowanie widoku tekstu do jego zawartości i zaktualizowanie stałej ograniczenia wysokości do wynikowej wysokości, naprawiło UIView-Encapsulated-Layout-Heightkonflikt ograniczeń dla mnie, np .:

[self.textView sizeToFit];
self.textViewHeightConstraint.constant = self.textView.frame.size.height;
Kim André Sand
źródło
Gdzie to zrobiłeś? W układzie Wyświetlenia?
stuckj 18.03.16
1

Po spędzeniu kilku godzin na drapaniu się w głowę tym błędem w końcu znalazłem rozwiązanie, które zadziałało dla mnie. moim głównym problemem było to, że zarejestrowałem wiele końcówek dla różnych typów komórek, ale konkretnie jeden typ komórki mógł mieć różne rozmiary (nie wszystkie wystąpienia tej komórki będą tego samego rozmiaru). więc problem pojawił się, gdy widok tabeli próbował usunąć kolejkę z komórki tego typu i okazało się, że ma inną wysokość. Rozwiązałem to przez ustawienie

self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight; self.frame = CGRectMake(0, 0, self.frame.size.width, {correct height});

za każdym razem, gdy komórka ma dane do obliczenia swojego rozmiaru. Myślę, że może być

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

coś jak

cell.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight; cell.frame = CGRectMake(0, 0, self.frame.size.width, {correct height});

Mam nadzieję że to pomoże!

Alcides Eduardo Zelaya
źródło
0

TableView uzyskać wysokość dla komórki w indexPath od delegata. następnie pobierz komórkę z cellForRowAtIndexPath:

top (10@1000)
    cell
bottom (0@1000)

jeśli cell.contentView.height: 0 // <-> (UIView-Encapsulated-Layout-Height: 0 @ 1000) góra (10 @ 1000) jest w konflikcie z (UIView-Encapsulated-Layout-Height: 0 @ 1000),

ponieważ ich priorytety są równe 1000. Potrzebujemy ustawić najwyższy priorytet w ramach UIView-Encapsulated-Layout-Heightpriorytetu.

użytkownik2962814
źródło
0

Otrzymałem taki komunikat:

Nie można jednocześnie spełnić ograniczeń ...
...
...
...
Ograniczenie NSLayout: 0x7fe74bdf7e50 „UIView-Encapsulated-Layout-Height” V: [UITableViewCellContentView: 0x7fe75330c5c0 (21.5)]
...
... Spróbuje
odzyskać ograniczenie zerwania NSLayout Ograniczenie: 0x7fe0f9b200c0 UITableViewCellContentView: 0x7fe0f9b1e090.bottomMargin == UILabel: 0x7fe0f9b1e970.bottom

Używam niestandardowego UITableViewCellz UITableViewAutomaticDimensionwysokością. I zaimplementowałem tę estimatedHeightForRowAtIndex:metodę.

Ograniczenie, które dawało mi problemy, wyglądało mniej więcej tak

[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[title]-|" options:0 metrics:nil views:views];

Zmiana ograniczenia na to rozwiąże problem, ale podobnie jak inna odpowiedź, poczułem, że to nie jest poprawne, ponieważ obniża priorytet ograniczenia, które chcę być wymagane:

[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-6@999-[title]-6@999-|" options:0 metrics:nil views:views];

Zauważyłem jednak, że jeśli po prostu usunę priorytet, to również działa i nie otrzymuję dzienników ograniczeń zerwania:

[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-6-[title]-6-|" options:0 metrics:nil views:views];

To trochę tajemnica, jaka jest różnica między |-6-[title]-6-|i |-[title-|. Ale określenie rozmiaru nie jest dla mnie problemem i pozbywa się dzienników i nie muszę obniżać priorytetu moich wymaganych ograniczeń.

użytkownik1687195
źródło
0

Ustaw to view.translatesAutoresizingMaskIntoConstraints = NO;powinno rozwiązać ten problem.

kathy zhou
źródło
Działa to dla mnie idealnie, nawet jeśli nie było to idealne rozwiązanie.
Enkha,
0

Miałem podobny problem z komórką widoku kolekcji.

Rozwiązałem go, obniżając priorytet ostatecznego ograniczenia, które było połączone z dolną częścią komórki (ostatnia w łańcuchu od góry do dołu widoku - to ostatecznie decyduje o jego wysokości) do 999.

Wysokość celi była prawidłowa, a ostrzeżenia zniknęły.

Bogaty
źródło