Kapan layoutSubviews dipanggil?

264

Saya memiliki tampilan khusus yang tidak menerima layoutSubviewpesan selama animasi.

Saya memiliki pandangan yang memenuhi layar. Ini memiliki subview khusus di bagian bawah layar yang mengubah ukuran dengan benar di Interface Builder jika saya mengubah ketinggian bilah navigasi. layoutSubviewsdipanggil saat tampilan dibuat, tetapi tidak pernah lagi. Subview saya diatur dengan benar. Jika saya menonaktifkan bilah status panggilan masuk, subview itu layoutSubviewstidak dipanggil sama sekali, meskipun tampilan utama memang menganimasikan ukurannya.

Dalam keadaan apa layoutSubviewssebenarnya disebut?

Saya telah autoresizesSubviewsmenetapkan untuk NOuntuk tampilan kustom saya. Dan di Interface Builder saya memiliki struts atas dan bawah dan set panah vertikal.


Bagian lain dari teka-teki adalah bahwa jendela harus dibuat kunci:

[window makeKeyAndVisible];

selain itu subview tidak secara otomatis diubah ukurannya.

Steve Weller
sumber

Jawaban:

492

Saya memiliki pertanyaan serupa, tetapi tidak puas dengan jawabannya (atau apa pun yang dapat saya temukan di internet), jadi saya mencobanya dalam praktik dan inilah yang saya dapatkan:

  • inittidak menyebabkan layoutSubviewsdipanggil (ya)
  • addSubview:menyebabkan layoutSubviewsdipanggil pada tampilan yang ditambahkan, tampilan itu ditambahkan ke (tampilan target), dan semua subview dari target
  • view dengan setFrame cerdas memanggil layoutSubviewstampilan yang memiliki frame hanya jika parameter ukuran frame berbeda
  • menggulir UIScrollView menyebabkan layoutSubviewsdipanggil pada scrollView, dan superview-nya
  • memutar perangkat hanya memanggil layoutSubviewpada tampilan induk (tampilan utama viewControllers yang merespons)
  • Mengubah ukuran tampilan akan memanggil layoutSubviewssuperview-nya

Hasil saya - http://blog.logichigh.com/2011/03/16/when-does-layoutsubviews-get-called/

BadPirate
sumber
1
Jawaban yang bagus Saya selalu bertanya-tanya tentang layoutSubviews. Apakah initWithFrame:sebab layoutSubviewsuntuk dipanggil?
Robert
2
@ Robert - Saya menggunakan initWithFrame ... jadi tidak.
BadPirate
8
@BadPirate: ya . Menurut percobaan saya, jika Anda mengubah ukurannya view1.1panggilan layoutSubviewsdari view1dan kemudian layoutSubviewsdari view1.1. Panggilan ini tidak merambat tanpa batas ke superviews, menyebutnya pada view1.1.1panggilan hanya layoutSubviewspada view1.1dan view1.1.1. Hanya bergerak tanpa mengubah ukurannya tidak memanggil layoutSubviewsmereka.
João Portela
1
viewDidLoad tidak dipanggil di UIView (tetapi UIViewController). Lihat Apakah memuat dipanggil setelah UIView tidak init'd.
BadPirate
2
Berdasarkan percobaan saya, aturan kedua mungkin tidak akurat: ketika saya menambahkan view1.2ke dalam view1, layoutSubviewsdari view1.2dan view1yang dipanggil, tetapi layoutSubviewsdari view1.1tidak disebut. ( view1.1dan view1.2merupakan subview dari view1). Artinya, tidak semua subview dari tampilan target disebut layoutSubviewsmetode .
HongchaoZhang
96

Membangun jawaban sebelumnya oleh @BadPirate, saya bereksperimen sedikit lebih jauh dan muncul dengan beberapa klarifikasi / koreksi. Saya menemukan bahwa layoutSubviews:akan dipanggil pada tampilan jika dan hanya jika:

  • Batas- batasnya sendiri (bukan bingkai) berubah.
  • Batas salah satu subview langsungnya berubah.
  • Subview ditambahkan ke tampilan atau dihapus dari tampilan.

Beberapa detail yang relevan:

  • Batas dianggap berubah hanya jika nilai baru berbeda, termasuk asal yang berbeda . Perhatikan secara spesifik itulah sebabnya mengapa layoutSubviews:dipanggil setiap kali UIScrollView menggulir, saat melakukan pengguliran dengan mengubah asal batasnya.
  • Mengubah bingkai hanya akan mengubah batas jika ukuran telah berubah, karena ini adalah satu-satunya hal yang disebarkan ke properti batas.
  • Perubahan batas tampilan yang belum ada dalam hierarki tampilan akan menghasilkan panggilan layoutSubviews: ketika tampilan akhirnya ditambahkan ke hierarki tampilan .
  • Dan hanya untuk kelengkapan: pemicu ini tidak secara langsung memanggil layoutSubviews, melainkan panggilan setNeedsLayout, yang menetapkan / menaikkan bendera. Setiap iterasi dari run loop, untuk semua tampilan dalam hierarki tampilan , flag ini dicentang. Untuk setiap tampilan tempat bendera ditemukan dinaikkan, layoutSubviews:dipanggil dan bendera diatur ulang. Tampilan lebih tinggi hierarki akan diperiksa / dipanggil terlebih dahulu.
Patrick Pijnappel
sumber
5
tidak dapat mengangkat cukup jawaban ini. itu harus menjadi jawaban teratas. tiga aturan yang diberikan adalah yang Anda butuhkan. Saya belum pernah menemukan perilaku layoutSubview yang tidak dijelaskan dengan baik oleh aturan ini.
Pärserk
1
Saya tidak berpikir layoutSubviews dipanggil ketika batas subview langsung diubah. Saya pikir pemanggilan layoutSubviews hanyalah efek samping dari pengujian Anda. Dalam beberapa kasus, layoutSubviews tidak akan dipanggil ketika subview terikat. Silakan periksa jawaban frogcjn, karena servernya didasarkan pada dokumentasi oleh Apple, bukan hanya eksperimen.
Simon Backx
19

https://developer.apple.com/library/prerelease/tvos/documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/CreatingViews/CreatingViews.html#//apple_ref/doc/uid/TP40009503-CH5-SW1

Perubahan tata letak dapat terjadi setiap kali salah satu peristiwa berikut terjadi dalam tampilan:

Sebuah. Ukuran kotak batas tampilan berubah.
b. Terjadi perubahan orientasi antarmuka, yang biasanya memicu perubahan dalam persegi panjang batas tampilan root.
c. Set sublayers Animasi Inti yang terkait dengan perubahan tampilan dan memerlukan tata letak.
d. Aplikasi Anda memaksa tata letak terjadi dengan memanggil   metode setNeedsLayout atau  layoutIfNeededmetode tampilan.
e. Aplikasi Anda memaksa tata letak dengan memanggil  setNeedsLayout metode objek lapisan yang mendasari tampilan.

kodok
sumber
Juga, yang penting untuk ditunjukkan adalah bahwa tidak satu pun dari peristiwa tersebut dipanggil ketika tampilan belum ditambahkan ke tumpukan tampilan. Anda memasukkan ini dengan kata "bisa", tetapi secara khusus disebut dalam siklus berikutnya yang tersedia dari utas utama aplikasi ketika telah ditandai sebagai membutuhkan tata letak oleh salah satu peristiwa tersebut.
user1122069
13

Beberapa poin dalam jawaban BadPirate hanya sebagian yang benar:

  1. Untuk addSubViewpoin

    addSubview menyebabkan layoutSubviews dipanggil pada tampilan yang ditambahkan, tampilan yang sedang ditambahkan (tampilan target), dan semua subview dari target.

    Itu tergantung pada topeng autoresize tampilan (target view). Jika memiliki autoresize mask ON, layoutSubview akan dipanggil masing-masing addSubview. Jika tidak memiliki mask autoresize maka layoutSubview akan dipanggil hanya ketika ukuran bingkai tampilan (target view) berubah.

    Contoh: jika Anda membuat UIView secara terprogram (secara default tidak memiliki mask autoresize), LayoutSubview akan dipanggil hanya ketika frame UIView berubah tidak pada setiap addSubview.

    Melalui teknik ini kinerja aplikasi juga meningkat.

  2. Untuk titik rotasi perangkat

    Memutar perangkat hanya memanggil layoutSubview pada tampilan induk (tampilan utama viewController yang merespons)

    Ini bisa benar hanya ketika VC Anda berada dalam hierarki VC (root at window.rootViewController ), nah ini kasus yang paling umum. Di iOS 5, jika Anda membuat VC, tetapi tidak ditambahkan ke VC lain, maka VC ini tidak akan mendapat perhatian ketika perangkat diputar. Karenanya pandangannya tidak akan diketahui dengan memanggil layoutSubviews.

Mohit Nigam
sumber
9

Saya melacak solusinya hingga desakan Interface Builder bahwa pegas tidak dapat diubah pada tampilan yang mengaktifkan elemen layar simulasi (bilah status, dll.). Karena pegas tidak aktif untuk tampilan utama, tampilan itu tidak dapat mengubah ukuran dan karenanya digulir ke bawah secara keseluruhan ketika bilah panggilan muncul.

Mematikan fitur yang disimulasikan, kemudian mengubah ukuran tampilan dan mengatur pegas dengan benar menyebabkan animasi terjadi dan metode saya dipanggil.

Masalah tambahan dalam debugging ini adalah bahwa simulator keluar dari aplikasi ketika status panggilan masuk diaktifkan melalui menu. Keluar dari aplikasi = tidak ada debugger.

Steve Weller
sumber
Apakah Anda mengatakan layoutSubviews dipanggil saat tampilan diubah ukurannya? Saya selalu berasumsi itu bukan ...
Andrey Tarantsov
Ini. Ketika sebuah tampilan diubah ukurannya perlu melakukan sesuatu dengan subview nya. Jika Anda tidak memberikan itu kemudian bergerak secara otomatis menggunakan mata air, struts dll
Steve Weller
8

memanggil [self.view setNeedsLayout]; di viewController membuatnya memanggil viewDidLayoutSubviews

bademi
sumber
5

Anda sudah melihat layoutIfNeeded?

Cuplikan dokumentasi di bawah ini. Apakah animasi berfungsi jika Anda memanggil metode ini secara eksplisit selama animasi?

layoutIfNeeded Menjabarkan subview jika diperlukan.

- (void)layoutIfNeeded

Diskusi Gunakan metode ini untuk memaksa tata letak subview sebelum menggambar.

Ketersediaan Tersedia di iPhone OS 2.0 dan yang lebih baru.

Willi Ballenthin
sumber
2

Saat memigrasi aplikasi OpenGL dari SDK 3 ke 4, layoutSubviews tidak dipanggil lagi. Setelah banyak trial and error akhirnya saya membuka MainWindow.xib, memilih objek Window, di inspektur memilih tab Window Attributes (paling kiri) dan centang "Visible at launch". Tampaknya di SDK 3 masih digunakan untuk menyebabkan panggilan layoutSubViews, tetapi tidak di 4.

6 jam frustrasi diakhiri.

Tal Yaniv
sumber
Apakah Anda membuat kunci jendela? Jika tidak, ini dapat menyebabkan semua hal menarik tidak terjadi.
Steve Weller
-2

Kasus yang agak kabur, namun berpotensi penting ketika layoutSubviewstidak pernah dipanggil adalah:

import UIKit

class View: UIView {

    override class var layerClass: AnyClass { return Layer.self }

    class Layer: CALayer {
        override func layoutSublayers() {
            // if we don't call super.layoutSublayers()...
            print(type(of: self), #function)
        }
    }

    override func layoutSubviews() {
        // ... this method never gets called by the OS!
        print(type(of: self), #function)
    }
}

let view = View(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
milos
sumber
1
Mengapa jawaban ini ada di sini?
Dominik Bucher