Kapan saya dapat mengaktifkan / menonaktifkan batasan tata letak?

104

Saya telah menyiapkan beberapa rangkaian batasan di IB, dan saya ingin beralih di antara keduanya secara terprogram tergantung pada beberapa status. Ada constraintsAkoleksi outlet yang semuanya ditandai terinstal dari IB, dan constraintsBkoleksi outlet yang semuanya dihapus di IB.

Saya secara terprogram dapat beralih di antara dua set seperti ini:

NSLayoutConstraint.deactivateConstraints(constraintsA)
NSLayoutConstraint.activateConstraints(constraintsB)

Tapi ... Saya tidak tahu kapan harus melakukan itu. Sepertinya saya harus bisa melakukannya sekali viewDidLoad, tapi saya tidak bisa membuatnya bekerja. Saya sudah mencoba menelepon view.updateConstraints()dan view.layoutSubviews()setelah mengatur batasan, tetapi tidak berhasil.

Saya menemukan bahwa jika saya menetapkan batasan dalam viewDidLayoutSubviewssegala hal bekerja seperti yang diharapkan. Saya rasa saya ingin mengetahui dua hal ...

  1. Mengapa saya mendapatkan perilaku ini?
  2. Apakah mungkin untuk mengaktifkan / menonaktifkan batasan dari viewDidLoad?
tybro0103
sumber
2
Apakah yang Anda maksud adalah deactivateConstraints dan activationConstraints bekerja di viewWillLayoutSubviews? Saya mencobanya, dan tidak berhasil di sana atau di viewDidLoad. Ini semacam bekerja di viewDidAppear; tampilan muncul di mana batasan baru harus meletakkannya, tetapi jika saya memutar ke lanskap, tampilan dipindahkan kembali ke posisi yang ditentukan oleh batasan yang ditetapkan dalam IB (dan tetap di sana ketika saya memutar kembali ke potret). Mencatat batasan, menunjukkan batasan yang benar (yang baru diaktifkan). Ini sepertinya bug bagi saya.
rdelmar
1
Ya, mereka valid (mereka bekerja di viewDidAppear), dan tidak perlu memanggil super karena tidak ada implementasi default viewWillLayoutSubviews (saya mencobanya dengan memanggil super, tapi itu tidak ada bedanya).
rdelmar
1
@rdelmar Baru saja mendapat kesempatan untuk menguji lebih lanjut ... Saya dapat memverifikasi bahwa saya benar-benar mendapatkan perilaku yang sama seperti yang Anda jelaskan ... bekerja pada awalnya di viewDidAppear, tetapi kemudian beralih setelah rotasi.
tybro0103
3
Rupanya Anda tidak dapat menandai kendala sebagai tidak dipasang di IB untuk tujuan ini. Menemukan informasi itu di sini: stackoverflow.com/questions/27663249/… dan itu memecahkan masalah bagi saya.
Stefan
1
Saya memiliki kendala saya yang diimplementasikan dengan cara yang sama seperti yang dijelaskan dalam pertanyaan, kecuali saya mengaktifkan / menonaktifkan beberapa di antaranya di viewDidAppear. Ini berhasil, tetapi Anda dapat melihat elemen dengan cepat berubah posisi (masalah kecil tetapi tidak diinginkan). Membuat perubahan pada viewWillAppear atau viewDidLoad tidak berhasil. Tetapi setelah membaca pertanyaan ini, saya mencoba melakukan perubahan itu di viewDidLayoutSubviews. Ini berfungsi dan perubahan posisi tidak lagi terlihat oleh pengguna. (Ini juga bekerja di viewWillLayoutSubviews). Jadi terima kasih atas tip itu!
peacetype

Jawaban:

186

Saya mengaktifkan dan menonaktifkan NSLayoutConstraintsdi viewDidLoad, dan saya tidak memiliki masalah dengan itu. Jadi itu berhasil. Harus ada perbedaan dalam penyiapan antara aplikasi Anda dan aplikasi saya :-)

Saya hanya akan menjelaskan penyiapan saya - mungkin itu bisa memberi Anda petunjuk:

  1. Saya menyiapkan @IBOutletssemua kendala yang perlu saya aktifkan / nonaktifkan.
  2. Di dalam ViewController, saya menyimpan batasan ke dalam properti kelas yang tidak lemah. Alasannya adalah karena saya menemukan bahwa setelah menonaktifkan kendala, saya tidak dapat mengaktifkannya kembali - nol. Jadi, sepertinya akan dihapus saat dinonaktifkan.
  3. Saya tidak menggunakan NSLayoutConstraint.deactivate/activateseperti yang Anda lakukan, saya menggunakan constraint.active = YES/ NOsebagai gantinya.
  4. Setelah mengatur batasan, saya menelepon view.layoutIfNeeded().
Joachim Bøggild
sumber
132
"simpan batasan ke properti kelas yang tidak lemah" Anda menghemat banyak waktu, terima kasih!
OpenUserX03
10
"Saya menyimpan batasan ke dalam properti kelas yang tidak lemah": Ini menyelamatkan saya dari banyak sakit hati. Saya tidak tahu bahwa saya memanggil pemilih pada objek nihil. Terima kasih!!
static0886
4
Penting untuk diperhatikan bahwa batasan "tidak aktif" tidak diabaikan oleh tata letak otomatis, batasan tersebut akan dihapus. Mengaktifkan / menonaktifkan batasan sebenarnya menambahkan dan menghapusnya. Menghabiskan beberapa waktu untuk men-debug tata letak otomatis yang berkonflik setelah saya menambahkan batasan yang telah saya tetapkan sebelumnya dengan .active = falsemengharapkan mereka akan diabaikan sampai saya mengaturnya ke aktif.
lbarbosa
1
simpan batasan ke dalam properti kelas yang tidak lemah, ok ini menghemat banyak waktu, saya mendapatkan beberapa hasil yang beragam tanpa ini. Terima kasih sobat!
MegaManX
3
Dokumen apel mengatakan: Mengaktifkan atau menonaktifkan batasan memanggil addConstraint ( :) dan removeConstraint ( :) pada tampilan yang merupakan leluhur bersama terdekat dari item yang dikelola oleh batasan ini. Gunakan properti ini daripada memanggil addConstraint ( :) atau removeConstraint ( :) secara langsung. Jadi nampaknya ketika batasan dinonaktifkan itu dihapus dan kemudian tidak ada referensi yang kuat untuk batasan yang tersisa, kecuali IBOutlet kuat. Oleh karena itu kendala dihapus. IMHO ini hampir merupakan bug atau setidaknya perilaku yang sangat tidak terduga.
Olle Raab
52

Mungkin Anda bisa memeriksa @properties, ganti weakdenganstrong .

Terkadang karena active = NOdisetel self.yourConstraint = nil, sehingga Anda tidak bisa menggunakan self.yourConstraintlagi.

Leo
sumber
5
Seperti yang dinyatakan dalam Panduan Bahasa Swift , properti secara default kuat sehingga Anda juga dapat menghapus weakdan itu akan melakukannya.
Jonathan Cabrera
30
override func viewDidLayoutSubviews() {
// do it here, after constraints have been materialized
}
Tony Swiftguy
sumber
1
Karena pengontrol tampilan saya adalah pengontrol tampilan anak - melakukannya di "didLayoutSubviews" tampaknya menjadi satu-satunya cara yang berhasil !! FYI.
TalL
Ini adalah satu-satunya jawaban yang valid
Yunus Eren Güzel
@TalL Apakah yang Anda maksud adalah batasan pada pengontrol tampilan anak itu sendiri, atau itu subview?
Stefan
ini adalah yang terbaik
ACAkgul
14

Saya yakin masalah yang Anda alami adalah karena kendala tidak ditambahkan ke pandangan mereka sampai SETELAH viewDidLoad()dipanggil. Anda memiliki sejumlah opsi:

A) Anda dapat menghubungkan batasan tata letak Anda ke IBOutlet dan mengaksesnya dalam kode Anda dengan referensi ini. Karena outlet terhubung sebelum viewDidLoad()dimulai, batasan harus dapat diakses dan Anda dapat terus mengaktifkan dan menonaktifkannya di sana.

B) Jika Anda ingin menggunakan constraints()fungsi UIView untuk mengakses berbagai batasan, Anda harus menunggu untuk viewDidLayoutSubviews()memulai dan melakukannya di sana, karena itu adalah poin pertama setelah membuat pengontrol tampilan dari ujung yang akan memiliki batasan yang terpasang. Jangan lupa menelepon layoutIfNeeded()setelah selesai. Ini memang memiliki kerugian bahwa pass tata letak akan dilakukan dua kali jika ada perubahan yang akan diterapkan dan Anda harus memastikan bahwa tidak ada kemungkinan loop tak terbatas akan dipicu.

Sebuah kata peringatan singkat: batasan yang dinonaktifkan TIDAK dikembalikan oleh constraints() metode! Ini berarti jika Anda LAKUKAN menonaktifkan pembatas dengan maksud untuk mengaktifkannya kembali nanti, Anda perlu menyimpan referensi ke sana.

C) Anda bisa melupakan pendekatan storyboard dan menambahkan batasan Anda secara manual. Karena Anda melakukan ini di, viewDidLoad()saya berasumsi bahwa tujuannya adalah hanya melakukannya sekali seumur hidup objek daripada mengubah tata letak dengan cepat, jadi ini harus menjadi metode yang dapat diterima.

Abu
sumber
10

Anda juga dapat menyesuaikan priorityproperti untuk "mengaktifkan" dan "menonaktifkan" mereka (nilai 750 untuk diaktifkan dan 250 untuk menonaktifkan misalnya). Untuk beberapa alasan, mengubah activeBOOL tidak berpengaruh pada UI saya. Tidak perlu layoutIfNeededdan dapat disetel dan diubah di viewDidLoad atau kapan saja setelah itu.

pengguna2387149
sumber
Saran yang sangat bagus. Mengubah prioritas batasan berfungsi di viewWillTransition(to:, with:)atau viewWillLayoutSubviews()dan Anda dapat menyimpan semua batasan alternatif sebagai "terpasang" di papan cerita. Prioritas batasan tidak boleh berubah dari tidak wajib menjadi wajib, jadi gunakan nilai di bawah ini 1000. Di sisi lain, mengaktifkan (menambahkan) dan menonaktifkan (menghapus) batasan hanya berfungsi viewDidLayoutSubviews()dan membutuhkan strong @IBOutletreferensi ke NSLayoutConstraint-s.
Gary
"Untuk beberapa alasan, mengubah BOOL aktif tidak berpengaruh pada UI saya". Berdasarkan sini . Saya pikir Anda tidak dapat mengubah batasan dengan prioritas 1000 selama runtime. Jika Anda ingin menonaktifkannya, maka Anda harus menetapkan prioritas awal ke 999 atau lebih rendah ....
Honey
Saya tidak setuju dengan pernyataan ini karena dapat menyebabkan masalah debug yang sulit dan tidak menjawab pertanyaan tersebut. Menyetel prioritas ke 250 tidak akan "menonaktifkan" kendala, itu masih akan berpengaruh dan mempengaruhi tata letak. Ini mungkin tampak seperti "menonaktifkan" kendala dalam banyak kasus tetapi jelas tidak di semua kasus. (Secara khusus, bukan kasus yang membuat saya menemukan jawaban untuk pertanyaan ini)
Tumata
Ini dapat menyebabkan error juga seperti "Mengubah prioritas dari wajib ke tidak pada batasan yang diinstal (atau sebaliknya) tidak didukung. Anda melewati prioritas 250 dan prioritas yang ada adalah 1000."
Karthick Ramesh
8

Waktu yang tepat untuk menonaktifkan kendala yang tidak digunakan:

-(void)viewWillLayoutSubviews{
    [super viewWillLayoutSubviews];

    self.myLittleConstraint.active = NO;
}

Perlu diingat bahwa viewWillLayoutSubviewsbisa dipanggil berkali-kali, jadi tidak ada perhitungan berat di sini, oke?

Catatan: jika Anda ingin mengaktifkan kembali beberapa batasan nanti, maka selalu simpan strongreferensi ke sana.

Laszlo
sumber
2
Bagi saya, satu-satunya cara yang dapat diandalkan adalah menyesuaikan batasan viewDidLayoutSubviews(). Menyesuaikan batasan viewWillLayoutSubviews()tidak berfungsi dalam kasus saya.
petrsyn
6

Saat tampilan sedang dibuat, metode siklus hidup berikut dipanggil secara berurutan:

  1. loadView
  2. viewDidLoad
  3. viewWillAppear
  4. viewWillLayoutSubviews
  5. viewDidLayoutSubviews
  6. viewDidAppear

Sekarang untuk pertanyaan Anda.

  1. Mengapa saya mendapatkan perilaku ini?

Jawaban: Karena ketika Anda mencoba untuk mengatur batasan pada tampilan dalam viewDidLoadtampilan tidak memiliki batasan, maka batasan tidak dapat diatur. Hanya setelah viewDidLayoutSubviewsitu batas tampilan diselesaikan.

  1. Apakah mungkin untuk mengaktifkan / menonaktifkan batasan dari viewDidLoad?

Jawaban: Tidak. Alasan dijelaskan di atas.

Sumeet
sumber
Dalam deskripsi siklus hidup viewController, Anda berbicara tentang bagaimana tampilan pertama kali dimuat dan kemudian viewDidLoad dipanggil. Namun Anda juga mengatakan bahwa tampilan tersebut tidak dibuat pada saat viewDidLoad dipanggil, ini jelas merupakan kontradiksi. Selain itu, Anda dapat menguji sendiri dan melihat bahwa tampilan tersebut telah dibuat saat viewDidLoad dipanggil karena Anda dapat menambahkan subview ke tampilan.
ABakerSmith
viewDidLoad seharusnya baik-baik saja karena tampilan dibuat dan dimuat ... Pada kenyataannya di mana Anda mengaktifkan batasan terutama bermuara pada kinerja. Saya menduga masalah aslinya tidak terkait dengan tempat pembatas diaktifkan. stackoverflow.com/questions/19387998/…
Gabe
@ABakerSmith saya telah mengedit jawaban saya agar lebih jelas.
Sumeet
1

Saya telah menemukan selama Anda mengatur batasan per normal dalam menimpa - (void)updateConstraints(objektif c), dengan strongreferensi untuk inisialitas yang digunakan batasan aktif dan tidak aktif. Dan di tempat lain dalam siklus tampilan menonaktifkan dan / atau mengaktifkan apa yang Anda butuhkan, kemudian memanggil layoutIfNeeded, Anda seharusnya tidak memiliki masalah.

Hal utama adalah jangan terus-menerus menggunakan kembali penggantian updateConstraintsdan untuk memisahkan aktivasi batasan, selama Anda memanggilnya updateConstraintsetelah inisialisasi dan tata letak pertama Anda. Tampaknya penting setelah itu di mana dalam siklus tampilan.

elliotrock.dll
sumber