Bagaimana saya bisa mendapatkan lebar dan tinggi tampilan saat ini saat menggunakan batasan tata letak otomatis?

108

Saya tidak berbicara tentang properti bingkai, karena dari itu Anda hanya bisa mendapatkan ukuran tampilan di xib. Saya berbicara tentang kapan tampilan diubah ukurannya karena batasannya (mungkin setelah rotasi, atau sebagai respons terhadap suatu peristiwa). Apakah ada cara untuk mengetahui lebar dan tinggi saat ini?

Saya mencoba iterasi melalui batasannya mencari batasan lebar dan tinggi, tetapi itu tidak terlalu bersih dan gagal ketika ada batasan intrinsik (karena saya tidak dapat membedakan keduanya). Selain itu, ini hanya berfungsi jika mereka benar-benar memiliki batasan lebar dan tinggi, yang tidak akan berfungsi jika mereka bergantung pada batasan lain untuk mengubah ukurannya.

Mengapa ini sangat sulit bagi saya. ARG!

yuf
sumber
Saya akan mengira bahwa batas tampilan pada waktu tertentu mempertahankan lebar dan tinggi saat ini. Itulah yang akan disesuaikan selama proses tata letak.
Phillip Mills
Hmm saya tidak pernah berpikir untuk memeriksa batas. Saya akan melakukannya sekarang.
yuf

Jawaban:

246

Jawabannya adalah [view layoutIfNeeded].

Inilah alasannya:

Anda masih mendapatkan lebar dan tinggi tampilan saat ini dengan memeriksa view.bounds.size.widthdan view.bounds.size.height(atau bingkai, yang setara kecuali Anda bermain-main dengan view.transform).

Jika yang Anda inginkan adalah lebar dan tinggi yang disiratkan oleh batasan yang ada, jawabannya bukanlah memeriksa batasan secara manual, karena itu akan mengharuskan Anda untuk menerapkan ulang seluruh logika penyelesaian batasan dari sistem tata letak otomatis untuk menafsirkannya. kendala. Alih-alih, yang harus Anda lakukan hanyalah meminta tata letak otomatis untuk memperbarui tata letak itu , sehingga ini memecahkan batasan dan memperbarui nilai view.bounds dengan solusi yang benar, lalu Anda memeriksa view.bounds.

Bagaimana Anda meminta tata letak otomatis untuk memperbarui tata letak? Panggil [view setNeedsLayout]jika Anda ingin tata letak otomatis memperbarui tata letak pada putaran berikutnya dari putaran proses.

Namun, jika Anda ingin segera memperbarui tata letak, sehingga Anda dapat segera mengakses nilai batas baru nanti dalam fungsi Anda saat ini, atau di titik lain sebelum pergantian putaran proses, Anda perlu memanggil [view setNeedsLayout]dan [view layoutIfNeeded].

Anda mengajukan pertanyaan kedua: "bagaimana saya bisa mengubah batasan tinggi / lebar jika saya tidak memiliki referensi ke sana secara langsung?".

Jika Anda membuat batasan di IB, solusi terbaik adalah membuat IBOutlet di pengontrol tampilan atau tampilan Anda sehingga Anda memiliki referensi langsung ke sana. Jika Anda membuat batasan dalam kode, maka Anda harus menyimpan referensi di properti internal weak pada saat Anda membuatnya. Jika orang lain yang membuat batasan, Anda perlu menemukannya dengan memeriksa properti memeriksa view.constraints pada tampilan, dan mungkin seluruh hierarki tampilan, serta menerapkan logika yang menemukan NSLayoutConstraint yang penting. Ini mungkin cara yang salah, karena ini juga secara efektif mengharuskan Anda untuk menentukan kendala tertentu yang menentukan ukuran batas, ketika tidak ada jaminan untuk menjadi jawaban sederhana untuk pertanyaan itu. Nilai batas akhir bisa menjadi solusi untuk sistem yang sangat rumit dengan banyak kendala,

alga
sumber
16
Sebuah sangat baik jawaban dan baik menjelaskan, juga. Menyelamatkan saya setelah satu atau dua jam menarik rambut saya.
Ben Kreeger
2
layoutIfNeededbagus, dan akan segera membuat bingkai tersedia. Namun, ini juga akan memaksa rendering batasan pada semua tampilan di sub-hierarki tampilan tempat ia dipanggil. Jika Anda menambahkan tampilan secara terprogram, misalnya, dan memanggil layoutIfNeededsemuanya dalam rutinitas rekursif, Anda mungkin mendapati bahwa hierarki tampilan Anda dirender sangat lambat. (Saya mempelajarinya dengan cara yang sulit) Seperti yang disebutkan dalam jawaban yang sangat baik, 'setNeedsLayout` lebih efisien dan akan membuat bingkai tersedia pada tata letak berikutnya, yang idealnya terjadi dalam waktu sekitar 1/60 detik.
shmim
1
Saya tahu ini sudah tua, tetapi di mana Anda menyebut metode ini? Masuk initWithCoder?
MayNotBe
4
Mengapa memaksa tata letak hanya untuk mendapatkan batasan? Ini tampaknya tidak efisien dan dengan antarmuka yang rumit ini berpotensi menjadi mahal. Alih-alih, akses batas terbaru dari viewDidLayoutSubviews atau bahkan viewWillLayoutSubviews dan biarkan cocoa menangani pengaturan waktu tata letaknya sendiri. Setel properti jika Anda perlu mengakses nilai atau tanda untuk menghindari beberapa panggilan jika itu menjadi masalah.
smileBot
1
Ya, Anda hanya perlu memaksakan tata letak jika memerlukan akses ke nilai yang dihitung tata letak otomatis sebelum pergantian loop proses - misalnya, dalam cakupan panggilan fungsi saat ini.
alga
8

Saya memiliki masalah serupa di mana saya perlu menambahkan batas atas dan bawah ke UITableViewyang mengubah ukuran berdasarkan pengaturan batasannya di UIStoryboard. Saya dapat mengakses batasan yang diperbarui dengan - (void)viewDidLayoutSubviews. Ini berguna agar Anda tidak perlu membuat subkelas tampilan dan mengganti metode tata letaknya.

/*** SET TOP AND BOTTOM BORDERS ON TABLE VIEW ***/
- (void)addBorders
{
    CALayer *topBorder           = [CALayer layer];
    topBorder.frame              = CGRectMake(0.0f, self.tableView.frame.origin.y, 320.0f, 0.5f);
    topBorder.backgroundColor    = [UIColor redColor].CGColor;

    CALayer *bottomBorder        = [CALayer layer];
    bottomBorder.frame           = CGRectMake(0.0f, (self.tableView.frame.origin.y + self.tableView.frame.size.height), 320.0f, 0.5f);
    bottomBorder.backgroundColor = [UIColor redColor].CGColor;

    [self.view.layer addSublayer:topBorder];
    [self.view.layer addSublayer:bottomBorder];
}

/*** GET AUTORESIZED FRAME DIMENSIONS ***/
- (void)viewDidLayoutSubviews{
    [self addBorders];
}

Tanpa memanggil metode dari viewDidLayoutSubviewmetode tersebut, hanya batas atas yang digambar dengan benar, karena batas bawah berada di luar layar.

Brandon Read
sumber
3
viewDidLayoutSubviews dipanggil beberapa kali, Anda membuat banyak lapisan ... Anda perlu menambahkan beberapa pemeriksaan keberadaan sebelum menambahkan lapisan lain .
Kalzem
Berkomentar terlambat beberapa tahun ... Anda juga harus memanggil metode ini pada superclass saat menimpa sebelum hal lain:[super viewDidLayoutSubviews];
Alejandro Iván
5

Bagi mereka yang mungkin masih menghadapi masalah seperti itu, terutama dengan TableviewCell.

Ganti saja metode ini:

-(void)layoutSubviews
{
//your code here like drawing a shadow
}

Jika UITableViewCell atau UICollectionViewCell buat subkelas sel dan ganti metode yang sama:

-(void)layoutSubviews
{
//your code here like drawing a shadow
}
Mayank Pahuja
sumber
ini adalah satu-satunya cara saya bisa mendapatkan ukuran akhir sebenarnya dari pandangan saya
Benoit Jadinon
Ini adalah satu-satunya cara saya bisa mendapatkan ukuran akhir untuk setiap tampilan terbatas saya karena sebelum saya mencoba untuk membuatnya di awakeFromNib yang tidak memberikan waktu subview untuk benar-benar mengubah ukuran terlebih dahulu. Terima kasih!
Azin Mehrnoosh
3

Gunakan -(void)viewWillAppear:(BOOL)animateddan panggil [self.view layoutIfNeeded];- Berhasil saya telah mencoba.

karena jika Anda menggunakannya -(void)viewDidLayoutSubviewspasti akan berhasil tetapi metode ini dipanggil setiap kali UI Anda menuntut pembaruan / perubahan. Yang akan sulit dikelola. Tombol lunak adalah Anda menggunakan variabel bool untuk menghindari loop panggilan seperti itu. penggunaan yang lebih baik viewWillAppear. Ingat viewWillAppearjuga akan dipanggil jika view dimuat kembali (tanpa realokasi).


sumber
2

Bingkai masih berlaku. Pada akhirnya, tampilan menggunakan properti bingkai untuk menata dirinya sendiri. Ini menghitung bingkai itu berdasarkan semua kendala. Batasan hanya digunakan untuk tata letak awal (dan kapan saja layoutSubviews dipanggil pada tampilan seperti setelah rotasi). Setelah itu, info posisi ada di properti frame. Atau apakah Anda melihat sebaliknya?

borrrden.dll
sumber
1
Saya melihat sebaliknya. Nilai bingkai tidak berubah, bahkan setelah mengubah batasan lebar / tinggi secara langsung (dengan mengubah konstanta mereka. Saya memiliki IBOutlet untuk mereka di xib)
yuf
Masalah saya unik karena saya mengubah kendala, kemudian perlu menggunakan bingkai dalam fungsi yang sama. Memanggil setNeedsLayout tidak segera memperbaruinya. Saya kira saya harus menunggu tata letak dulu baru menggunakan bingkai. Pertanyaan kedua saya adalah, bagaimana saya bisa mengubah batasan tinggi / lebar jika saya tidak memiliki referensi ke sana secara langsung?
yuf
1
Anda harus dispatch_async kemudian, dan ini akan memungkinkan Anda untuk menunda kode Anda hingga frame berikutnya. Adapun bagian kedua ... Saya tidak tahu ... Anda harus mengulangi semua kendala dan mencari mana yang berlaku ... IBOutlets jauh lebih mudah.
borrrden
Benar untuk IBOutlets, tetapi saya ingin menulis fungsi dalam kategori untuk UIView yang tidak memiliki IB untuk saya kerjakan.
yuf