UIStackView "Tidak dapat memenuhi batasan secara bersamaan" pada tampilan tersembunyi yang "terhimpun"

97

Ketika "baris" UIStackView saya tertekan, mereka mengeluarkan AutoLayoutperingatan. Namun, mereka ditampilkan dengan baik dan tidak ada yang salah selain jenis logging ini:

Tidak dapat memenuhi batasan secara bersamaan. Mungkin setidaknya salah satu kendala dalam daftar berikut adalah yang tidak Anda inginkan. Coba ini: (1) lihat setiap kendala dan coba cari tahu yang tidak Anda harapkan; (2) temukan kode yang menambahkan batasan atau batasan yang tidak diinginkan dan perbaiki. (Catatan: Jika Anda melihat NSAutoresizingMaskLayoutConstraintsbahwa Anda tidak mengerti, lihat dokumentasi untuk UIViewproperti translatesAutoresizingMaskIntoConstraints) (

Jadi, saya belum yakin bagaimana cara memperbaikinya, tetapi sepertinya tidak merusak apa pun selain hanya mengganggu.

Apakah ada yang tahu bagaimana mengatasinya? Menariknya, batasan tata letak sering diberi tag dengan 'UISV-hiding' , yang menunjukkan bahwa mungkin harus mengabaikan ketinggian minimum untuk subview atau sesuatu dalam contoh ini?

Ben Guild
sumber
1
Ini tampaknya diperbaiki di iOS11, tidak mendapatkan peringatan apa pun di sini
trapper

Jawaban:

206

Anda mendapatkan masalah ini karena saat menyetel subview dari dalam UIStackViewmenjadi tersembunyi, pertama-tama akan membatasi tingginya ke nol untuk menghidupkannya.

Saya mendapatkan kesalahan berikut:

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.

Apa yang saya coba lakukan, adalah menempatkan UIViewdalam saya UIStackViewyang berisi UISegmentedControlinset oleh 8pts di setiap sisi.

Ketika saya menyetelnya ke tersembunyi, itu akan mencoba untuk membatasi tampilan kontainer ke ketinggian nol tetapi karena saya memiliki sekumpulan batasan dari atas ke bawah, ada konflik.

Untuk mengatasi masalah ini, saya mengubah prioritas 8pt atas dan batasan bawah dari 1000 menjadi 999 sehingga UISV-hidingbatasan tersebut dapat diprioritaskan jika diperlukan.

liamnichols
sumber
Perlu dicatat bahwa jika Anda hanya memiliki batasan tinggi atau lebar pada ini dan mengurangi prioritasnya, itu tidak akan berfungsi. Anda perlu menghapus tinggi / lebar dan menambahkan trailing bottom di bagian atas, kemudian mengatur prioritasnya sebagai lebih rendah dan berhasil
bolnad
4
Mengubah prioritas juga berhasil bagi saya. Selain itu, menghapus batasan berlebih (redup) yang secara tidak sengaja disalin dari Kelas Ukuran yang tidak digunakan. TIPS PENTING: untuk lebih mudah men-debug masalah ini, setel string IDENTIFER pada setiap Kendala. Kemudian Anda dapat melihat Constraint mana yang nakal dalam pesan debug.
Womble
3
Dalam kasus saya, saya hanya perlu menurunkan prioritas pada ketinggian dan berhasil.
pixelfreak
Tip IDENTIFIER itu bagus! Saya selalu bertanya-tanya bagaimana memberi batasan pada nama pesan debug, saya selalu mencari untuk menambahkan sesuatu ke tampilan daripada batasan itu sendiri. Terima kasih @Womble!
Ryan
Terima kasih! Prioritas dari 1000 hingga 999 berhasil. Xcode: Versi 8.3.3 (8E3004b)
Michael Garito
54

Saya mengalami masalah serupa yang tidak mudah diselesaikan. Dalam kasus saya, saya memiliki tampilan tumpukan yang disematkan dalam tampilan tumpukan. UIStackView internal memiliki dua label dan spasi bukan nol yang ditentukan.

Saat Anda memanggil addArrangedSubview (), secara otomatis akan membuat batasan yang mirip dengan berikut ini:

V:|[innerStackView]|              | = outerStackView

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

Sekarang saat Anda mencoba menyembunyikan innerStackView, Anda mendapatkan peringatan batasan yang ambigu.

Untuk memahami mengapa, pertama-tama mari kita lihat mengapa ini tidak terjadi bila innerStackView.spacingsama dengan 0. Saat Anda memanggil innerStackView.hidden = true, @liamnichols benar ... outerStackViewkehendak secara ajaib akan mencegat panggilan ini, dan membuat batasan penyembunyian UISV0 ketinggian dengan prioritas 1000 (diperlukan). Agaknya ini untuk memungkinkan elemen dalam tampilan tumpukan dianimasikan keluar dari tampilan jika kode persembunyian Anda dipanggil di dalam blok. Sayangnya, tampaknya tidak ada cara untuk mencegah penambahan batasan ini. Namun demikian, Anda tidak akan mendapatkan peringatan "Unable to concored satisfaints" (USSC), karena hal berikut terjadi:UIView.animationWithDuration()

  1. tinggi label1 disetel ke 0
  2. jarak antara dua label sudah ditentukan sebagai 0
  3. tinggi label2 disetel ke 0
  4. tinggi innerStackView disetel ke 0

Jelas terlihat bahwa 4 kendala tersebut dapat dipenuhi. Tampilan tumpukan hanya memoles semuanya menjadi piksel 0-tinggi.

Sekarang kembali ke contoh buggy, jika kita menyetel spacingke 2, kita sekarang memiliki batasan ini:

  1. tinggi label1 disetel ke 0
  2. jarak antara dua label secara otomatis dibuat oleh tampilan tumpukan sebagai 2 piksel tinggi pada prioritas 1000.
  3. tinggi label2 disetel ke 0
  4. tinggi innerStackView disetel ke 0

Tampilan tumpukan tidak boleh setinggi 0 piksel dan isinya setinggi 2 piksel. Kendala tidak bisa dipenuhi.

Catatan: Anda dapat melihat perilaku ini dengan contoh yang lebih sederhana. Cukup tambahkan UIView ke tampilan tumpukan sebagai subview yang diatur. Kemudian setel batasan ketinggian pada UIView dengan prioritas 1000. Sekarang coba panggil hide tentang itu.

Catatan: Untuk alasan apa pun, ini hanya terjadi jika tampilan tumpukan saya adalah subview dari UICollectionViewCell atau UITableViewCell. Namun, Anda masih bisa mereproduksi perilaku ini di luar sel dengan memanggil innerStackView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)run loop berikutnya setelah menyembunyikan tampilan tumpukan dalam.

Catatan: Meskipun Anda mencoba menjalankan kode di UIView.performWithoutAnimations, tampilan tumpukan akan tetap menambahkan batasan ketinggian 0 yang akan menyebabkan peringatan USSC.


Setidaknya ada 3 solusi untuk masalah ini:

  1. Sebelum menyembunyikan elemen apa pun dalam tampilan tumpukan, periksa apakah itu tampilan tumpukan, dan jika demikian, ubah spacingke 0. Ini mengganggu karena Anda perlu membalikkan proses (dan mengingat spasi asli) setiap kali Anda menampilkan konten lagi.
  2. Alih-alih menyembunyikan elemen dalam tampilan tumpukan, panggil removeFromSuperview. Ini bahkan lebih menjengkelkan karena ketika Anda membalik proses, Anda perlu mengingat di mana harus memasukkan item yang dihapus. Anda dapat mengoptimalkan hanya dengan memanggil removeArrangedSubview lalu bersembunyi, tetapi ada banyak pembukuan yang masih perlu dilakukan.
  3. Gabungkan tampilan tumpukan bersarang (yang memiliki nilai bukan nol spacing) dalam UIView. Tentukan setidaknya satu batasan sebagai prioritas yang tidak diperlukan (999 atau lebih rendah). Ini adalah solusi terbaik karena Anda tidak perlu melakukan pembukuan apa pun. Dalam contoh saya, saya membuat batasan teratas, memimpin, dan tertinggal pada 1000 antara tampilan tumpukan dan tampilan pembungkus, lalu membuat batasan 999 dari bagian bawah tampilan tumpukan ke tampilan pembungkus. Dengan cara ini, saat tampilan tumpukan luar membuat batasan ketinggian nol, batasan 999 rusak dan Anda tidak melihat peringatan USSC. (Catatan: Ini mirip dengan solusi untuk Haruskah contentView.translatesAutoResizingMaskToConstraints subkelas UICollectionViewCell disetel kefalse )

Singkatnya, alasan Anda mendapatkan perilaku ini adalah:

  1. Apple secara otomatis membuat 1000 batasan prioritas untuk Anda saat Anda menambahkan subview terkelola ke tampilan tumpukan.
  2. Apple secara otomatis membuat batasan ketinggian 0 untuk Anda saat Anda menyembunyikan subview dari tampilan tumpukan.

Seandainya Apple (1) mengizinkan Anda menentukan prioritas batasan (terutama spacer), atau (2) mengizinkan Anda memilih keluar dari batasan penyembunyian UISV otomatis , masalah ini akan mudah diselesaikan.

Senseful
sumber
6
Terima kasih atas penjelasan yang sangat menyeluruh dan bermanfaat. Namun ini pasti tampak seperti bug di sisi Apple dengan tampilan tumpukan. Pada dasarnya fitur "persembunyian" mereka tidak kompatibel dengan fitur "spasi" mereka. Adakah gagasan tentang mereka telah menyelesaikan ini sejak itu, atau telah menambahkan beberapa fitur untuk mencegah peretasan dengan pandangan berisi tambahan? (Sekali lagi, perincian besar dari solusi potensial dan sepakati keanggunan # 3)
Marchy
1
Apakah setiap UIStackViewanak dari suatu UIStackViewkebutuhan untuk dibungkus UIViewatau hanya yang ingin Anda sembunyikan / sembunyikan?
Adrian
1
Ini terasa seperti pengawasan yang sangat buruk di pihak Apple. Terutama karena peringatan dan kesalahan seputar penggunaan UIStackViewcenderung samar dan sulit dipahami.
bompf
Ini adalah penyelamat. Saya mengalami masalah di mana UIStackViews yang ditambahkan ke UITableViewCell menyebabkan spamming log kesalahan AutoLayout, setiap kali sel digunakan kembali. Menyematkan stackView dalam UIView dengan prioritas batasan jangkar bawahnya disetel ke rendah, memecahkan masalah. Hal ini menyebabkan debugger tampilan menampilkan elemen stackView sebagai memiliki ketinggian yang ambigu, namun ditampilkan dengan benar di aplikasi, tanpa spam log kesalahan. TERIMA KASIH.
Womble
6

Sering kali, kesalahan ini dapat diatasi dengan menurunkan prioritas batasan untuk menghilangkan konflik.

Luciano Almeida
sumber
Apa maksudmu? Batasan semuanya relatif dalam tampilan yang ditumpuk ....
Ben Guild
Maaf, saya tidak mengerti pertanyaan Anda, bahasa Inggris saya begitu, tapi yang saya pikirkan adalah bahwa kendala relatif terhadap tampilan penyihir yang terkait, jika tampilan menjadi tersembunyi, kendala menjadi tidak aktif. Saya tidak yakin apakah itu keraguan Anda, tetapi saya harap saya dapat membantu.
Luciano Almeida
Sepertinya itu berasal dari saat barang-barang itu tertekan atau dalam proses ditampilkan / disembunyikan. Dalam kasus ini, itu menjadi terlihat sebagian. - Mungkin perlu untuk benar-benar melalui dan menghilangkan konstanta vertikal minimum karena dapat dipadatkan hingga ketinggian 0?
Ben Guild
2

Saat Anda menyetel tampilan menjadi tersembunyi, UIStackviewkehendak mencoba untuk membuatnya menjauh. Jika Anda menginginkan efek itu, Anda harus menetapkan prioritas yang tepat untuk batasan sehingga tidak bertentangan (seperti yang disarankan banyak orang di atas).

Namun jika Anda tidak peduli dengan animasinya (mungkin Anda menyembunyikannya di ViewDidLoad), maka Anda bisa sederhana removeFromSuperviewyang akan memiliki efek yang sama tetapi tanpa masalah dengan batasan karena itu akan dihapus bersama dengan tampilan.

Oren
sumber
1

Berdasarkan jawaban @ Senseful, berikut adalah ekstensi UIStackView untuk menggabungkan tampilan tumpukan dalam tampilan dan menerapkan batasan yang dia rekomendasikan:

/// 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
}

Alih-alih menambahkan Anda stackView, gunakanstackView.wrapped() .

Ben Packard
sumber
1

Pertama, seperti yang disarankan orang lain, pastikan batasan yang dapat Anda kontrol, yaitu bukan batasan yang melekat pada UIStackView yang disetel ke prioritas 999 sehingga dapat diganti saat tampilan disembunyikan.

Jika Anda masih mengalami masalah, kemungkinan masalahnya karena jarak di StackView yang tersembunyi. Solusi saya adalah menambahkan UIView sebagai spacer dan mengatur jarak UIStackView ke nol. Kemudian setel batasan View.height atau View.width (bergantung pada tumpukan vertikal atau horizontal) ke jarak StackView.

Kemudian sesuaikan prioritas memeluk konten dan ketahanan kompresi konten dari tampilan yang baru Anda tambahkan. Anda mungkin harus mengubah distribusi StackView induk juga.

Semua hal di atas bisa dilakukan di Interface Builder. Anda mungkin juga harus menyembunyikan / menampilkan beberapa tampilan yang baru ditambahkan secara terprogram sehingga Anda tidak memiliki spasi yang tidak diinginkan.

Peter Coyle
sumber
1

Saya baru-baru ini bergumul dengan kesalahan tata letak otomatis saat menyembunyikan file UIStackView. Daripada melakukan banyak pembukuan dan membungkus tumpukan UIViews, saya memilih untuk membuat outlet untuk saya parentStackViewdan outlet untuk anak-anak yang ingin saya sembunyikan / perlihatkan.

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

Di storyboard, inilah tampilan parentStack saya:

masukkan deskripsi gambar di sini

Ini memiliki 4 anak dan masing-masing anak memiliki banyak tampilan tumpukan di dalamnya. Saat Anda menyembunyikan tampilan tumpukan, jika ada elemen UI yang juga merupakan tampilan tumpukan, Anda akan melihat aliran kesalahan tata letak otomatis. Daripada menyembunyikan, saya memilih untuk menghapusnya.

Dalam contoh saya, parentStackViewsberisi array dari 4 elemen: Top Stack View, StackViewNumber1, Stack View Number 2, dan Stop Button. Indeksnya arrangedSubviewsmasing-masing adalah 0, 1, 2, dan 3. Ketika saya ingin menyembunyikannya, saya cukup menghapusnya dari parentStackView's arrangedSubviewsarray. Karena tidak lemah, itu tetap ada di memori dan Anda bisa meletakkannya kembali ke indeks yang Anda inginkan nanti. Saya tidak menginisialisasi ulang, jadi itu hanya hang sampai dibutuhkan, tapi tidak mengasapi memori.

Jadi pada dasarnya, Anda dapat ...

1) Seret IBOutlets untuk tumpukan orang tua Anda dan anak-anak yang ingin Anda sembunyikan / tampilkan ke storyboard.

2) Saat Anda ingin menyembunyikannya, hapus tumpukan yang ingin Anda sembunyikan dari parentStackView's arrangedSubviewsarray.

3) Telepon self.view.layoutIfNeeded()dengan UIView.animateWithDuration.

Perhatikan bahwa dua stackView terakhir tidak weak. Anda harus menyimpannya saat Anda memperlihatkannya.

Katakanlah saya ingin menyembunyikan stackViewNumber2:

parentStackView.removeArrangedSubview(stackViewNumber2)
stackViewNumber2.removeFromSuperview()

Kemudian animasikan:

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

Jika Anda ingin "memperlihatkan" stackViewNumber2nanti, Anda dapat memasukkannya ke dalam parentStackView arrangedSubViewsindeks yang diinginkan dan menganimasikan pembaruan.

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)

Saya menemukan itu jauh lebih mudah daripada melakukan pembukuan pada kendala, mengutak-atik prioritas, dll.

Jika Anda memiliki sesuatu yang ingin disembunyikan secara default, Anda bisa meletakkannya di storyboard dan menghapusnya viewDidLoaddan memperbarui tanpa menggunakan animasi view.layoutIfNeeded().

Adrian
sumber
1

Saya mengalami kesalahan yang sama dengan Stack Views yang disematkan, meskipun semuanya bekerja dengan baik saat runtime.

Saya memecahkan kesalahan batasan dengan menyembunyikan semua tampilan sub-tumpukan terlebih dahulu (pengaturan isHidden = true) sebelum menyembunyikan tampilan tumpukan induk.

Melakukan hal ini tidak memiliki semua kerumitan untuk menghapus tampilan yang tersusun, mempertahankan indeks saat perlu menambahkannya kembali.

Semoga ini membantu.

Will Stevens
sumber
1

Senseful telah memberikan jawaban yang sangat baik untuk akar masalah di atas jadi saya akan langsung menuju solusinya.

Yang perlu Anda lakukan adalah menyetel semua prioritas batasan stackView lebih rendah dari 1000 (999 akan melakukan pekerjaan itu). Misalnya jika stackView dibatasi kiri, kanan, atas, dan bawah ke superview-nya, maka keempat batasan harus memiliki prioritas lebih rendah dari 1000.

Linh Ta
sumber
0

Anda mungkin telah membuat batasan saat bekerja dengan kelas ukuran tertentu (contoh: wCompact hRegular) dan kemudian Anda membuat duplikat saat Anda beralih ke kelas ukuran lain (misal: wAny hAny). periksa batasan objek UI dalam kelas ukuran yang berbeda dan lihat apakah ada anomali dengan batasan tersebut. Anda akan melihat garis merah yang menunjukkan kendala bertabrakan. Saya tidak bisa memasang gambar sampai saya mendapatkan 10 poin reputasi. Maaf: /

FM
sumber
Oh oke. tetapi saya mendapat kesalahan itu ketika saya memiliki kasus yang saya jelaskan drive.google.com/file/d/0B-mn7bZcNqJMVkt0OXVLVVdnNTA/…
FM
Ya, saya pasti tidak melihat warna merah sama sekali dengan mengubah kelas ukuran di pembuat antarmuka. Saya hanya menggunakan ukuran "Apa saja".
Ben Guild
0

Saya ingin menyembunyikan seluruh UIStackView sekaligus tetapi saya mendapatkan kesalahan yang sama dengan OP, ini memperbaikinya untuk saya:

for(UIView *currentView in self.arrangedSubviews){
    for(NSLayoutConstraint *currentConstraint in currentView.constraints){
        [currentConstraint setPriority:999];
    }
}
sp00ky
sumber
Ini tidak berhasil untuk saya karena mesin tata letak otomatis mengeluh saat Anda mengubah batasan yang diperlukan ( priority = 1000) menjadi tidak diperlukan ( priority <= 999).
Penuh perasaan
0

Saya memiliki deretan tombol dengan batasan ketinggian. Ini terjadi jika satu tombol disembunyikan. Menyetel prioritas batasan ketinggian tombol ke 999 telah menyelesaikan masalah.

Niklas
sumber
-2

Kesalahan ini tidak ada hubungannya dengan UIStackView. Itu terjadi ketika Anda memiliki konflik yang dibatasi dengan prioritas yang sama. Misalnya, jika Anda memiliki batasan yang menyatakan bahwa lebar tampilan Anda adalah 100, dan Anda memiliki batasan lain pada saat yang sama yang menyatakan bahwa lebar tampilan adalah 25% dari penampungnya. Jelas ada dua kendala yang saling bertentangan. Solusinya adalah dengan menghapusnya.

William Kinaan
sumber
-3

NOP dengan [mySubView removeFromSuperview]. Saya harap ini bisa membantu seseorang :)

Grégoire GUYON
sumber
Maaf, apa NOP?
Pang