Apa NSLayoutConstraint "UIView-Encapsulated-Layout-Height" dan bagaimana saya harus memaksanya untuk menghitung ulang secara bersih?


262

Saya memiliki UITableViewmenjalankan di bawah iOS 8 dan saya menggunakan ketinggian sel otomatis dari kendala di storyboard.

Salah satu sel saya berisi satu UITextViewdan saya membutuhkannya untuk berkontraksi dan berkembang berdasarkan input pengguna - ketuk untuk mengecilkan / perluas teks.

Saya melakukan ini dengan menambahkan kendala runtime ke tampilan teks dan mengubah konstanta pada kendala sebagai respons terhadap peristiwa pengguna:

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

    _collapsed = collapse;

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

    [self setNeedsUpdateConstraints];

}

Setiap kali saya melakukan ini, saya membungkusnya dalam tableViewpembaruan dan menelepon [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];

Ketika saya melakukan ini, sel saya berkembang (dan menjiwai saat melakukannya) tapi saya mendapat peringatan kendala:

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 adalah tinggi saya yang dihitung, kendala lain pada UITextViewadalah milik saya dari Xcode / IB.

Yang terakhir mengganggu saya - saya menduga itu UIView-Encapsulated-Layout-Heightadalah tinggi sel yang dihitung saat pertama kali diberikan - (saya menetapkan UITextViewtinggi badan saya menjadi> = 70,0) namun sepertinya tidak benar bahwa batasan turunan ini kemudian mengesampingkan suatu pembaruan cnstraint pengguna.

Lebih buruk lagi, meskipun kode tata letak mengatakan itu mencoba untuk memecahkan batasan ketinggian saya, itu tidak - ia melanjutkan untuk menghitung ulang tinggi sel dan semuanya menarik seperti yang saya inginkan.

Jadi, apa itu NSLayoutConstraint UIView-Encapsulated-Layout-Height(saya menduga itu adalah tinggi yang dihitung untuk ukuran sel otomatis) dan bagaimana saya harus memaksanya untuk menghitung ulang secara bersih?


3
cross diposting ke forum Apple dev: devforums.apple.com/thread/238803
Rog

3
Saya menyelesaikan masalah serupa dengan cara berikut dan berfungsi di iOS 7/8. 1) Turunkan salah satu prioritas kendala ke 750. Saya akan mencoba 1 atau 2 2) Dalam subkelas sel di set awakeFromNib self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight;. Saya pikir bahwa pengaturan awal topeng autoresize menghentikan kendala terakhir yang ditambahkan. Saya menemukan solusi ini di sini: github.com/wordpress-mobile/WordPress-iOS/commit/…
Jesse

3
@RogerNolan, ada berita? Saya menemukan masalah yang sama saat bermain dengan Auto-layout di Interface Builder. Beberapa sel menyebabkan masalah ini, beberapa tidak.
orkenstein

3
Saya tidak berpikir bahwa menambahkan tanda autoresizing adalah solusi yang baik.
Rog

1
@RogerNolan apakah Anda membuat sel ini di IB sebelum melakukan perubahan tata letak ini? Saya telah men-debug masalah yang sama tetapi saya tidak menambahkan kendala tambahan. Saya berhasil menekan peringatan dengan membangun kembali pandangan saya dari awal, dan ketika saya membedakan 2 file storyboard, satu-satunya perbedaan adalah bahwa versi dengan peringatan tersebut tidak sesuai <rect key="frame" x="0.0" y="0.0" width="600" height="110"/>dengan definisi, membuat saya percaya bahwa ini adalah bug IB . Setidaknya milikku toh.
Ell Neal

Jawaban:


301

Cobalah untuk menurunkan prioritas Anda _collapsedtextHeightConstraintke 999. Dengan cara itu UIView-Encapsulated-Layout-Heightkendala yang disediakan sistem selalu diutamakan.

Ini didasarkan pada apa yang Anda kembali -tableView:heightForRowAtIndexPath:. Pastikan untuk mengembalikan nilai yang tepat dan kendala Anda sendiri dan yang dihasilkan harus sama. Prioritas yang lebih rendah untuk kendala Anda sendiri hanya diperlukan sementara untuk mencegah konflik saat animasi runtuh / luas sedang terbang.


74
Itu akan mencapai kebalikan dari apa yang saya inginkan. The UIView-Encapsulated-Layout-Height salah - itu milik tata letak sebelumnya.
Rog

7
The UIView-Encapsulated-Layout-Heightkendala ditambahkan oleh UITableView setelah ketinggian ditentukan. Saya menghitung ketinggian berdasarkan pada systemLayoutSizeFittingSizecontentView. Di sini, UIView-Encapsulated-Layout-Heighttidak masalah. Kemudian, tableView menetapkan contentSize secara eksplisit ke nilai yang dikembalikan oleh heightForRowAtIndexPath:. Dalam hal ini benar untuk menurunkan prioritas kendala khusus kami karena kendala tableView harus diutamakan setelah rowHeights dihitung.
Ortwin Gentz

8
@OrtwinGentz: Masih tidak mendapatkan poin itu benar untuk menurunkan prioritas kendala kustom kami karena kendala tableView harus diutamakan setelah rowHeights dihitung . Masalahnya adalah itu UIView-Encapsulated-Layout-Heightsalah jika saya tidak menurunkan prioritas ...
menguji

38
Saya berpendapat bahwa menghindari konflik tidak memperbaiki masalah yang sebenarnya - meskipun karena ini masih terasa seperti bug Apple, saya pikir itu mungkin hal yang benar untuk dilakukan. Terutama mengingat fakta bahwa Apple menghitung ulang batasan ini dan semuanya tidak tertata dengan benar setelah kesalahan dicetak.
Rog

9
-1 untuk jawaban ini karena menganggap kendala yang ditambahkan sudah benar. Menambahkan UIView-Encapsulated-Layout-Widthdalam kasus saya hanya salah, tetapi tampaknya lebih disukai daripada kendala eksplisit saya saat runtime.
ray

68

Saya memiliki skenario yang sama: tampilan tabel dengan satu sel baris, di mana ada beberapa baris objek UILabel. Saya menggunakan iOS 8 dan autolayout.

Ketika saya diputar saya mendapat sistem salah menghitung tinggi baris (43,5 jauh lebih sedikit dari tinggi sebenarnya). Sepertinya:

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

Itu bukan hanya peringatan. Tata letak sel tampilan tabel saya mengerikan - semua teks tumpang tindih pada satu baris teks.

Itu mengejutkan saya bahwa baris berikut "memperbaiki" masalah saya secara ajaib (autolayout tidak mengeluh apa pun dan saya mendapatkan apa yang saya harapkan di layar):

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

dengan atau tanpa baris ini:

myTableView.rowHeight = UITableViewAutomaticDimension; // by itself this line only doesn't help fix my specific problem

8
Akhirnya! Ini jawaban yang benar. Dalam sesi WWDC, disebutkan, jika Anda akan menggunakan ukuran tinggi baris otomatis, maka Anda harus menetapkan estimasiRowHeight atau hal-hal buruk terjadi. (Ya, Apple benar-benar hal-hal buruk terjadi)
Abdalrahman Shatou

50
FWIW, menambahkan perkiraan tidak membuat perbedaan sama sekali bagi saya.
Benjohn

3
Heh. Saya kembali pada jawaban yang sama ini lagi, dan menerapkannya dengan sukacita dan harapan. Sekali lagi, tidak ada bedanya bagi saya :-)
Benjohn

3
Taksiran dikembalikan oleh tableView: diperkirakanHeightForRowAtIndexPath: HARUS paling tidak sebesar sel. Jika tidak, tinggi tabel yang dihitung akan lebih kecil dari tinggi sebenarnya, dan tabel tersebut dapat menggulir ke atas (misalnya setelah melepas segue, kembali ke tabel). UITableViewAutomaticDimension tidak boleh digunakan.
Matt

1
Perhatikan bahwa semakin rendah estimatedRowHeightsemakin sering cellForRowAtIndexPathakan dipanggil pada awalnya. tinggi tampilan tabel dibagi dengan taksiran kaliRowHeight, tepatnya. Pada iPad Pro 12 inci, ini berpotensi menjadi angka dalam ribuan dan akan memalu sumber data dan dapat menyebabkan penundaan yang signifikan.
Mojo66

32

Saya bisa mendapatkan peringatan untuk pergi dengan menetapkan prioritas pada salah satu nilai dalam kendala yang menurut pesan peringatan harus dipatahkan (di bawah "Will attempt to recover by breaking constraint"). Tampaknya selama saya menetapkan prioritas untuk sesuatu yang lebih besar dari itu 49, peringatan itu hilang.

Bagi saya ini berarti mengubah batasan saya, peringatan mengatakan itu berusaha untuk mematahkan:

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

untuk:

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

Bahkan, saya dapat menambahkan prioritas ke salah satu elemen dari kendala itu dan itu akan berhasil. Tampaknya tidak masalah yang mana. Sel saya berakhir dengan ketinggian yang tepat dan peringatan tidak ditampilkan. Roger, misalnya, coba tambahkan @500tepat setelah 388batasan nilai tinggi (mis 388@500.).

Saya tidak sepenuhnya yakin mengapa ini berhasil, tetapi saya telah melakukan sedikit penyelidikan. Di enum NSLayoutPriority , tampaknya NSLayoutPriorityFittingSizeCompressiontingkat prioritasnya adalah 50. Dokumentasi untuk tingkat prioritas mengatakan:

Saat Anda mengirim pesan fittingSize ke tampilan, ukuran terkecil yang cukup besar untuk konten tampilan dihitung. Ini adalah tingkat prioritas yang ingin dilihat sekecil mungkin dalam perhitungan itu. Cukup rendah. Pada umumnya tidak tepat untuk membuat batasan pada prioritas ini. Anda ingin menjadi lebih tinggi atau lebih rendah.

Itu dokumentasi untuk direferensikan fittingSizepesan berbunyi:

Ukuran minimum tampilan yang memenuhi batasan yang dimilikinya. (hanya baca)

AppKit menetapkan properti ini ke ukuran terbaik yang tersedia untuk tampilan, mengingat semua kendala yang dipegangnya dan subview-nya dan memuaskan preferensi untuk membuat tampilan sekecil mungkin. Nilai ukuran di properti ini tidak pernah negatif.

Saya belum menggali lebih dari itu tetapi tampaknya masuk akal bahwa ini ada hubungannya dengan di mana masalahnya.


Itu Jeff. Masih terasa seperti bug bagi saya. Apple belum merespons rdar :-(
Rog

13

99,9% dari waktu, saat menggunakan sel atau header khusus, semua konflik UITableViewsterjadi ketika tabel memuat pertama kali. Setelah dimuat Anda biasanya tidak akan melihat konflik lagi.

Ini terjadi karena sebagian besar pengembang biasanya menggunakan ketinggian tetap atau batasan jangkar dari beberapa jenis untuk tata letak elemen di sel / header. Konflik terjadi karena ketika UITableViewbeban pertama / diletakkan, ia mengatur ketinggian selnya menjadi 0. Ini jelas bertentangan dengan kendala Anda sendiri. Untuk mengatasi ini, cukup setel batasan ketinggian tetap apa pun ke prioritas yang lebih rendah ( .defaultHigh). Baca dengan hati-hati pesan konsol dan lihat kendala apa yang diputus sistem tata letak. Biasanya ini yang perlu diubah prioritasnya. Anda dapat mengubah prioritas seperti ini:

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

NSLayoutConstraint.activate([
            companyNameTopConstraint,
           the rest of your constraints here
            ])

1
Penjelasan yang indah.
Glenn

12

Saya bisa mengatasi kesalahan ini dengan menghapus palsu cell.layoutIfNeeded()bahwa aku telah di saya tableView's cellForRowAtmetode.


1
Ya, ini juga memecahkan masalah bagi saya. Saya melakukan kendala tata letak kode jadi saya awalnya berpikir bahwa saya mungkin kehilangan sesuatu. Terima kasih
John

1
Hal yang sama! Terima kasih!
Andrey Chernukha

7

Alih-alih menginformasikan tampilan tabel untuk memperbarui batasannya, coba muat ulang sel:

[tableView beginUpdates];

[_briefCell collapse:!_showFullBriefText];

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

[tableView endUpdates];

UIView-Encapsulated-Layout-Height mungkin tinggi tampilan tabel dihitung untuk sel selama beban awal, berdasarkan kendala sel pada waktu itu.


Seharusnya saya menyatakan dalam pertanyaan saya, saya sudah mencoba ini dan itu tidak berhasil. Saya setuju pada tebakan Anda tentang UIView-Encapsulated-Layout-Height
Rog

6
Miliki karunia itu setidaknya untuk mengirimkan jawaban. Sepertinya SO hanya akan membiarkannya menguap sebaliknya.
Rog

6

Kemungkinan lain:

Jika Anda menggunakan tata letak otomatis untuk menghitung tinggi sel (tinggi contentView, sebagian besar waktu seperti di bawah ini), dan jika Anda memiliki pemisah uitableview, Anda perlu menambahkan tinggi pemisah, untuk mengembalikan tinggi sel. Setelah Anda mendapatkan ketinggian yang benar, Anda tidak akan memiliki peringatan pembayaran otomatis itu.

- (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

}

Itu memang membantu saya dalam kasus di mana saya mendapat ambiguitas ketinggian tata letak dalam tata letak sel yang cukup kompleks hanya di beberapa simulator (iPad 6 Plus). Tampak bagi saya bahwa karena beberapa kesalahan pembulatan internal kontennya agak terjepit dan jika kendala tidak siap untuk diperas daripada saya mendapat ambiguitas. Jadi, bukannya kembali UITableViewAutomaticDimensiondi heightForRowAtIndexPathsaya kembali [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize] + 0.1
Leo

Maksud saya tentu saja[sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height + 0.1
Leo

Secara mengejutkan; menghapus pemisah adalah apa yang membuat meja saya berfungsi, jadi terima kasih.
royalmurder

5

Seperti yang disebutkan oleh Jesse dalam komentar pertanyaan, ini bekerja untuk saya:

self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight;

FYI, masalah ini tidak terjadi di iOS 10.


2
Dalam Swift 4.2: self.contentView.autoresizingMask = [.flexibleHeight]
airowe

4

Saya mengalami kesalahan ini saat menggunakan UITableViewAutomaticDimension dan mengubah batasan ketinggian pada tampilan di dalam sel.

Saya akhirnya menemukan bahwa itu karena nilai konstan kendala tidak dibulatkan ke bilangan bulat terdekat.

let neededHeight = width / ratio // This is a CGFloat like 133.2353
constraintPictureHeight.constant = neededHeight // Causes constraint error
constraintPictureHeight.constant = ceil(neededHeight) // All good!

2
Ini adalah komentar yang memberi saya petunjuk tentang masalah saya. Saya memiliki sel gambar yang memuat secara dinamis (tumbuh dan menyusut) dan tampilan tabel dengan taksiranRowHeight = 50 dan rowHeight = UITableViewAutomaticDimension. Saya masih melanggar kendala meskipun tampilan tabel adalah ketinggian yang benar. Ternyata pemisah tingginya 0,3333 dan itulah yang menendang kendala ukuran gambar saya di sel yang akan rusak. Setelah mematikan pemisah semua baik-baik saja. Terima kasih Che karena telah memberiku apa yang harus dicari.
migs647

1
Dalam hal ini buat batasan tambahan, misalnya, bottomMargin> = view.bottomMargin+1@900. AutoLayout mencoba mengakomodasi 1 poin tambahan, mengubah ukuran sel, menjadi bingung karena ketinggian pemisah, mencoba memecahkan / mengendurkan beberapa kendala, menemukan satu @ 900, dan membuangnya. Anda mendapatkan tata letak yang Anda inginkan tanpa peringatan.
Anton

selamatkan hari saya. toh beberapa jam
Anton Tropashko

1

Mengubah ukuran tampilan teks agar sesuai dengan isinya, dan memperbarui batasan ketinggian konstan ke ketinggian yang dihasilkan, memperbaiki UIView-Encapsulated-Layout-Heightkonflik kendala bagi saya, misalnya:

[self.textView sizeToFit];
self.textViewHeightConstraint.constant = self.textView.frame.size.height;

Di mana Anda melakukan ini? Di layoutSubviews?
stuckj

1

Setelah menghabiskan beberapa jam menggaruk-garuk kepala saya dengan bug ini, saya akhirnya menemukan solusi yang bekerja untuk saya. masalah utama saya adalah bahwa saya memiliki beberapa nib yang terdaftar untuk jenis sel yang berbeda tetapi satu jenis sel secara khusus diizinkan untuk memiliki ukuran yang berbeda (tidak semua contoh sel itu akan memiliki ukuran yang sama). jadi masalah muncul ketika tampilan tabel mencoba untuk mengeluarkan sel dari jenis itu dan kebetulan memiliki ketinggian yang berbeda. Saya menyelesaikannya dengan mengatur

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

setiap kali sel memiliki data untuk menghitung ukurannya. Saya pikir itu bisa masuk

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

sesuatu seperti

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

Semoga ini membantu!


0

TableView mendapatkan tinggi untuk sel di indexPath dari delegasi. lalu dapatkan sel dari cellForRowAtIndexPath:

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

jika cell.contentView.height: 0 // <-> (UIView-Encapsulated-Layout-Height: 0 @ 1000) top (10 @ 1000) bentrok dengan (UIView-Encapsulated-Layout-Height: 0 @ 1000),

karena mereka prioritas sama dengan 1000. Kita perlu menetapkan prioritas utama di bawah UIView-Encapsulated-Layout-Heightprioritas.


0

Saya mendapat pesan seperti ini:

Tidak dapat secara bersamaan memenuhi kendala ...
...
...
...
NSLayoutConstraint: 0x7fe74bdf7e50 'UIView-Encapsulated-Layout-Height' V: [UITableViewCellContentView: 0x7fe75330c5c0 (21.5)]
...
...
Akan berupaya memulihkan dengan melanggar batasan NSLayoutConstraint: 0x7fe0f9b200c0 UITableViewCellContentView: 0x7fe0f9b1e090.bottomMargin == UILabel: 0x7fe0f9b1e970.bottom

Saya menggunakan custom UITableViewCelldengan UITableViewAutomaticDimensionuntuk ketinggian. Dan saya juga menerapkan estimatedHeightForRowAtIndex:metode ini.

Kendala yang memberi saya masalah tampak seperti ini

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

Mengubah batasan untuk ini akan memperbaiki masalah, tetapi seperti jawaban lain saya merasa bahwa ini tidak benar, karena menurunkan prioritas kendala yang saya inginkan:

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

Namun, yang saya perhatikan adalah bahwa jika saya benar-benar hanya menghapus prioritas, ini juga berfungsi dan saya tidak mendapatkan log kendala pemecah:

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

Ini adalah sedikit misteri tentang apa perbedaan antara |-6-[title]-6-|dan |-[title-|. Tetapi menentukan ukuran bukan masalah bagi saya dan menghilangkan log, dan saya tidak perlu menurunkan prioritas kendala yang diperlukan.


0

Setel ini view.translatesAutoresizingMaskIntoConstraints = NO;harus menyelesaikan masalah ini.


Ini bekerja dengan sempurna bagi saya, bahkan jika itu bukan solusi yang sempurna.
Enkha

0

Saya punya masalah serupa dengan sel tampilan koleksi.

Saya menyelesaikannya dengan menurunkan prioritas batasan akhir yang dikaitkan dengan bagian bawah sel (yang terakhir dalam rantai dari atas ke bawah tampilan - inilah yang akhirnya menentukan tingginya) menjadi 999.

Ketinggian sel itu benar, dan peringatan itu hilang.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.