Praktik yang tepat untuk subkelas UIView?

158

Saya sedang mengerjakan beberapa kontrol input berbasis UIView khusus, dan saya mencoba memastikan praktik yang tepat untuk mengatur tampilan. Ketika bekerja dengan UIViewController, itu cukup sederhana untuk menggunakan loadViewdan terkait viewWill, viewDidmetode, tetapi ketika subclassing UIView, yang methosds terdekat yang saya miliki adalah `awakeFromNib, drawRect, dan layoutSubviews. (Saya sedang berpikir dalam hal setup dan teardown callbacks.) Dalam kasus saya, saya menyiapkan bingkai dan tampilan internal layoutSubviewssaya, tapi saya tidak melihat apa pun di layar.

Apa cara terbaik untuk memastikan bahwa pandangan saya memiliki tinggi dan lebar yang benar yang saya inginkan? (Pertanyaan saya berlaku terlepas dari apakah saya menggunakan autolayout, meskipun mungkin ada dua jawaban.) Apa "praktik terbaik" yang tepat?

Moshe
sumber

Jawaban:

298

Apple mendefinisikan dengan jelas cara subkelas UIView dalam dokumen.

Lihatlah daftar di bawah ini, terutama lihat initWithFrame:dan layoutSubviews. Yang pertama dimaksudkan untuk mengatur bingkai AndaUIView sedangkan yang kedua dimaksudkan untuk mengatur frame dan tata letak subview-nya.

Ingat juga bahwa initWithFrame:ini dipanggil hanya jika Anda membuat instance UIViewprogram Anda . Jika Anda memuatnya dari file nib (atau storyboard), initWithCoder:akan digunakan. Dan dalam initWithCoder:bingkai belum dihitung, jadi Anda tidak dapat mengubah bingkai yang Anda atur di Interface Builder. Seperti yang disarankan dalam jawaban ini, Anda mungkin berpikir untuk menelepon initWithFrame:dariinitWithCoder: untuk mengatur bingkai.

Akhirnya, jika Anda memuat UIViewdari nib (atau storyboard), Anda juga memiliki awakeFromNibkesempatan untuk melakukan inisialisasi bingkai dan tata letak khusus, sejak saatawakeFromNib disebut dijamin bahwa setiap tampilan dalam hierarki telah diarsipkan dan diinisialisasi.

Dari doc of NSNibAwaking(sekarang digantikan oleh doc of awakeFromNib):

Pesan ke objek lain dapat dikirim dengan aman dari dalam awakeFromNib — saat itu dipastikan bahwa semua objek tidak diarsipkan dan diinisialisasi (walaupun tidak harus dibangunkan, tentu saja)

Perlu juga dicatat bahwa dengan autolayout Anda tidak harus secara eksplisit mengatur bingkai pandangan Anda. Alih-alih, Anda seharusnya menentukan serangkaian kendala yang cukup, sehingga frame secara otomatis dihitung oleh mesin tata letak.

Langsung dari dokumentasi :

Metode untuk Mengesampingkan

Inisialisasi

  • initWithFrame:Anda disarankan untuk menerapkan metode ini. Anda juga dapat menerapkan metode inisialisasi khusus selain, atau bukannya, metode ini.

  • initWithCoder: Terapkan metode ini jika Anda memuat tampilan Anda dari file nib Interface Builder dan tampilan Anda memerlukan inisialisasi khusus.

  • layerClassTerapkan metode ini hanya jika Anda ingin tampilan Anda menggunakan layer Core Animation berbeda untuk backing store-nya. Misalnya, jika Anda menggunakan OpenGL ES untuk melakukan menggambar, Anda ingin mengganti metode ini dan mengembalikan kelas CAEAGLLayer.

Menggambar dan mencetak

  • drawRect:Terapkan metode ini jika tampilan Anda menarik konten khusus. Jika tampilan Anda tidak membuat gambar khusus, hindari mengganti metode ini.

  • drawRect:forViewPrintFormatter: Terapkan metode ini hanya jika Anda ingin menggambar konten tampilan Anda secara berbeda saat mencetak.

Kendala

  • requiresConstraintBasedLayout Terapkan metode kelas ini jika kelas tampilan Anda membutuhkan kendala untuk berfungsi dengan baik.

  • updateConstraints Terapkan metode ini jika tampilan Anda perlu membuat batasan khusus di antara tampilan bawah Anda.

  • alignmentRectForFrame:, frameForAlignmentRect:Terapkan metode ini untuk menimpa bagaimana pandangan Anda selaras dengan tampilan lain.

Tata letak

  • sizeThatFits:Terapkan metode ini jika Anda ingin tampilan Anda memiliki ukuran default yang berbeda dari biasanya selama operasi pengubahan ukuran. Misalnya, Anda mungkin menggunakan metode ini untuk mencegah tampilan Anda menyusut ke titik di mana subview tidak dapat ditampilkan dengan benar.

  • layoutSubviews Terapkan metode ini jika Anda memerlukan kontrol yang lebih tepat atas tata letak subview Anda daripada yang disediakan oleh perilaku kendala atau autoresizing.

  • didAddSubview:, willRemoveSubview:Terapkan metode ini sesuai kebutuhan untuk melacak penambahan dan penghapusan subview.

  • willMoveToSuperview:, didMoveToSuperviewTerapkan metode ini sesuai kebutuhan untuk melacak pergerakan tampilan saat ini dalam hierarki tampilan Anda.

  • willMoveToWindow:, didMoveToWindowTerapkan metode ini sesuai kebutuhan untuk melacak pergerakan tampilan Anda ke jendela yang berbeda.

Penanganan Acara:

  • touchesBegan:withEvent:, touchesMoved:withEvent:, touchesEnded:withEvent:, touchesCancelled:withEvent:Melaksanakan metode ini jika Anda perlu untuk menangani peristiwa sentuhan langsung. (Untuk input berbasis gerakan, gunakan pengenal isyarat.)

  • gestureRecognizerShouldBegin: Terapkan metode ini jika tampilan Anda menangani acara sentuh secara langsung dan mungkin ingin mencegah pengenal gerakan terlampir memicu tindakan tambahan.

Gabriele Petronella
sumber
bagaimana dengan - (void) setFrame: (CGRect) frame?
pfrank
baik Anda pasti bisa menimpanya, tetapi untuk tujuan apa?
Gabriele Petronella
untuk mengubah tata letak / gambar kapan saja ukuran bingkai atau lokasi berubah
pfrank
1
Bagaimana dengan layoutSubviews?
Gabriele Petronella
Dari stackoverflow.com/questions/4000664/… , "masalah dengan hal ini adalah bahwa subview tidak hanya dapat mengubah ukurannya, tetapi mereka juga dapat menganimasikan perubahan ukuran itu. Ketika UIView menjalankan animasi, ia tidak memanggil layoutSubview setiap kali." Belum mengujinya secara pribadi
pfrank
38

Ini masih muncul tinggi di Google. Di bawah ini adalah contoh yang diperbarui untuk swift.

The didLoadFungsi memungkinkan Anda menempatkan semua kode inisialisasi kustom Anda. Seperti yang disebutkan orang lain, didLoadakan dipanggil ketika tampilan dibuat secara programatis melalui init(frame:)atau ketika deserializer XIB menggabungkan templat XIB ke dalam tampilan Anda melaluiinit(coder:)

Selain : layoutSubviewsdan updateConstraintsdipanggil beberapa kali untuk sebagian besar tampilan. Ini dimaksudkan untuk tata letak dan penyetelan multi-pass canggih saat batas tampilan berubah. Secara pribadi, saya menghindari tata letak multi-pass bila mungkin karena mereka membakar siklus CPU dan membuat semuanya sakit kepala. Selain itu, saya menempatkan kode kendala di inisialisasi sendiri karena saya jarang membatalkannya.

import UIKit

class MyView: UIView {
  //-----------------------------------------------------------------------------------------------------
  //Constructors, Initializers, and UIView lifecycle
  //-----------------------------------------------------------------------------------------------------
  override init(frame: CGRect) {
      super.init(frame: frame)
      didLoad()
  }

  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    didLoad()
  }

  convenience init() {
    self.init(frame: CGRectZero)
  }

  func didLoad() {
    //Place your initialization code here

    //I actually create & place constraints in here, instead of in
    //updateConstraints
  }

  override func layoutSubviews() {
     super.layoutSubviews()

     //Custom manually positioning layout goes here (auto-layout pass has already run first pass)
  }

  override func updateConstraints() {
    super.updateConstraints()

    //Disable this if you are adding constraints manually
    //or you're going to have a 'bad time'
    //self.translatesAutoresizingMaskIntoConstraints = false

    //Add custom constraint code here
  }
}
seo
sumber
Bisakah Anda menjelaskan kapan / mengapa Anda akan membagi kode kendala tata letak antara metode yang dipanggil dari proses init Anda dan layoutSubviews dan updateConstraints? Sepertinya mereka adalah tiga kemungkinan lokasi kandidat untuk menempatkan kode tata letak. Jadi, bagaimana Anda tahu kapan / apa / mengapa harus membagi kode tata letak antara ketiganya?
Clay Ellis
3
Saya tidak pernah menggunakan updateConstraints; updateConstraints bisa menyenangkan karena Anda tahu hierarki tampilan Anda diatur sepenuhnya di init, sehingga Anda tidak dapat menaikkan pengecualian dengan menambahkan batasan antara dua tampilan yang tidak ada dalam hierarki :) layoutSubviews tidak boleh memiliki modifikasi kendala; itu dapat dengan mudah menyebabkan rekursi tak terbatas karena layoutSubviews dipanggil jika kendala 'tidak valid' selama pass tata letak. Pengaturan tata letak manual (seperti pada pengaturan frame secara langsung, yang jarang perlu Anda lakukan lagi kecuali karena alasan kinerja) ada di layoutSubviews. Secara pribadi, saya menempatkan batasan pembuatan di init
seo
Untuk kode rendering khusus, haruskah kita mengganti drawmetode?
Petrus Theron
14

Ada ringkasan yang layak dalam dokumentasi Apple , dan ini tercakup dengan baik dalam kursus Stanford gratis yang tersedia di iTunes. Saya menyajikan versi TL; DR saya di sini:

Jika kelas Anda sebagian besar terdiri dari subview, tempat yang tepat untuk mengalokasikannya adalah dalam initmetode. Untuk tampilan, ada dua initmetode berbeda yang bisa dipanggil, tergantung pada apakah tampilan Anda sedang dibuat dari kode atau dari nib / storyboard. Apa yang saya lakukan adalah menulis setupmetode saya sendiri , dan kemudian menyebutnya dari metode initWithFrame:dan initWithCoder:.

Jika Anda melakukan menggambar khusus, Anda memang ingin menimpanya drawRect: pandangan Anda. Namun, jika tampilan khusus Anda sebagian besar merupakan wadah untuk subview, Anda mungkin tidak perlu melakukannya.

Hanya ganti layoutSubViewsjika Anda ingin melakukan sesuatu seperti menambah atau menghapus subview tergantung pada apakah Anda dalam orientasi potret atau lanskap. Kalau tidak, Anda harus bisa membiarkannya sendiri.

dpassage
sumber
Saya menggunakan jawaban Anda untuk mengubah tampilan (yang merupakan bingkai subView awakeFromNib) layoutSubViews, itu berhasil.
Pesawat
1

layoutSubviews dimaksudkan untuk mengatur bingkai pada tampilan anak, bukan pada tampilan itu sendiri.

Sebab UIView, konstruktor yang ditunjuk biasanya initWithFrame:(CGRect)framedan Anda harus mengatur frame di sana (atau di initWithCoder:), mungkin mengabaikan nilai frame yang diteruskan. Anda juga dapat memberikan konstruktor yang berbeda dan mengatur bingkai di sana.

proxi
sumber
Bisakah Anda mengambilnya lebih detail? Saya tidak tahu maksud Anda. Bagaimana cara mengatur bingkai tampilan subView? tampilan adalahawakeFromNib
pesawat
Maju cepat ke 2016, Anda mungkin tidak perlu mengatur frame sama sekali dan menggunakan autolayout (kendala). Jika tampilan berasal dari XIB (atau storyboard), subview harus sudah disiapkan.
proxi