TL; DR: Tidak suka membaca? Langsung langsung ke proyek sampel di GitHub:
Deskripsi Konseptual
2 langkah pertama di bawah ini berlaku untuk apa pun versi iOS yang Anda kembangkan.
1. Mengatur & Menambahkan Kendala
Dalam Anda UITableViewCell
subclass, menambahkan kendala sehingga subviews dari sel memiliki tepi mereka disematkan ke tepi sel contentView (yang paling penting ke atas dan tepi bawah). CATATAN: jangan sematkan subview ke sel itu sendiri; hanya ke sel contentView
! Biarkan ukuran konten intrinsik dari subview ini mendorong ketinggian tampilan konten sel tampilan tabel dengan memastikan hambatan kompresi konten dan kendala memeluk konten dalam dimensi vertikal untuk setiap subview tidak ditimpa oleh kendala prioritas lebih tinggi yang telah Anda tambahkan. ( Hah? Klik di sini. )
Ingat, idenya adalah untuk membuat subview sel terhubung secara vertikal ke tampilan konten sel sehingga mereka dapat "menekan" dan membuat tampilan konten diperluas agar sesuai dengan mereka. Menggunakan contoh sel dengan beberapa subview, berikut ini adalah ilustrasi visual tentang apa yang beberapa (tidak semua!) Dari kendala Anda perlu terlihat seperti:
Anda dapat membayangkan bahwa karena lebih banyak teks ditambahkan ke label tubuh multi-garis pada contoh sel di atas, maka teks tersebut akan perlu tumbuh secara vertikal agar sesuai dengan teks, yang secara efektif akan memaksa sel untuk tumbuh tinggi. (Tentu saja, Anda perlu memperbaiki kendala agar ini berfungsi dengan benar!)
Memperbaiki kendala dengan tepat merupakan bagian tersulit dan paling penting untuk mendapatkan ketinggian sel dinamis yang bekerja dengan Tata Letak Otomatis. Jika Anda membuat kesalahan di sini, itu bisa mencegah semuanya bekerja - jadi luangkan waktu Anda! Saya sarankan mengatur batasan Anda dalam kode karena Anda tahu persis kendala mana yang ditambahkan di mana, dan itu jauh lebih mudah untuk debug ketika ada masalah. Menambahkan kendala dalam kode bisa semudah dan secara signifikan lebih kuat daripada Interface Builder menggunakan tata letak jangkar, atau salah satu API open source fantastis yang tersedia di GitHub.
- Jika Anda menambahkan batasan dalam kode, Anda harus melakukannya sekali dari dalam
updateConstraints
metode subkelas UITableViewCell Anda. Catatan yang updateConstraints
dapat dipanggil lebih dari sekali, jadi untuk menghindari menambahkan kendala yang sama lebih dari sekali, pastikan untuk memasukkan kode penambahan kendala Anda dalam updateConstraints
cek untuk properti boolean seperti didSetupConstraints
(yang Anda setel ke YA setelah Anda menjalankan kendala Anda -menambahkan kode sekali). Di sisi lain, jika Anda memiliki kode yang memperbarui kendala yang ada (seperti menyesuaikan constant
properti pada beberapa kendala), letakkan ini di updateConstraints
luar pemeriksaan didSetupConstraints
agar dapat dijalankan setiap kali metode dipanggil.
2. Tentukan Unik Table View Cell Reuse Identifiers
Untuk setiap rangkaian kendala unik dalam sel, gunakan pengidentifikasi penggunaan kembali sel yang unik. Dengan kata lain, jika sel Anda memiliki lebih dari satu tata letak yang unik, masing-masing tata letak yang unik harus menerima pengenal penggunaan kembali sendiri. (Petunjuk bagus bahwa Anda perlu menggunakan pengenal penggunaan kembali yang baru adalah ketika varian sel Anda memiliki jumlah subview yang berbeda, atau subview tersebut diatur dengan cara yang berbeda.)
Misalnya, jika Anda menampilkan pesan email di setiap sel, Anda mungkin memiliki 4 tata letak yang unik: pesan hanya dengan subjek, pesan dengan subjek dan badan, pesan dengan subjek dan lampiran foto, dan pesan dengan subjek, tubuh, dan lampiran foto. Setiap tata letak memiliki batasan yang sangat berbeda yang diperlukan untuk mencapainya, jadi setelah sel diinisialisasi dan kendala ditambahkan untuk salah satu dari jenis sel ini, sel harus mendapatkan pengidentifikasi penggunaan ulang yang unik khusus untuk jenis sel itu. Ini berarti ketika Anda membuat sel untuk digunakan kembali, batasannya telah ditambahkan dan siap digunakan untuk jenis sel itu.
Perhatikan bahwa karena perbedaan dalam ukuran konten intrinsik, sel dengan batasan (tipe) yang sama mungkin masih memiliki ketinggian yang berbeda-beda! Jangan bingung tata letak yang berbeda secara fundamental (kendala yang berbeda) dengan bingkai tampilan terhitung yang berbeda (diselesaikan dari kendala yang sama) karena ukuran konten yang berbeda.
- Jangan menambahkan sel dengan set kendala yang sangat berbeda ke kumpulan reuse yang sama (yaitu menggunakan pengidentifikasi reuse yang sama) dan kemudian mencoba untuk menghapus kendala lama dan mengatur batasan baru dari awal setelah setiap dequeue. Mesin Tata Letak Otomatis internal tidak dirancang untuk menangani perubahan skala besar pada kendala, dan Anda akan melihat masalah kinerja yang sangat besar.
Untuk iOS 8 - Sel Self-Sizing
3. Aktifkan Estimasi Tinggi Baris
Untuk mengaktifkan sel tampilan tabel ukuran sendiri, Anda harus mengatur properti rowHeight tampilan tabel ke UITableViewAutomaticDimension. Anda juga harus menetapkan nilai ke properti taksiranRowHeight. Segera setelah kedua properti ini diatur, sistem menggunakan Tata Letak Otomatis untuk menghitung ketinggian aktual baris
Apple: Bekerja dengan Sel Tampilan Tabel Self-Sizing
Dengan iOS 8, Apple telah menginternalisasi banyak pekerjaan yang sebelumnya harus diimplementasikan oleh Anda sebelum iOS 8. Untuk memungkinkan mekanisme sel sizing bekerja, Anda harus terlebih dahulu mengatur rowHeight
properti pada tampilan tabel ke konstanta UITableViewAutomaticDimension
. Kemudian, Anda hanya perlu mengaktifkan estimasi tinggi baris dengan menyetel estimatedRowHeight
properti tampilan tabel ke nilai bukan nol, misalnya:
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 44.0; // set to whatever your "average" cell height is
Apa yang dilakukan adalah menyediakan tampilan tabel dengan estimasi sementara / placeholder untuk ketinggian baris sel yang belum tampil di layar. Kemudian, ketika sel-sel ini akan bergulir di layar, tinggi baris sebenarnya akan dihitung. Untuk menentukan ketinggian aktual untuk setiap baris, tampilan tabel secara otomatis menanyakan setiap sel berapa tinggi yang contentView
dibutuhkan berdasarkan pada lebar tetap dari tampilan konten (yang didasarkan pada lebar tampilan tabel, dikurangi hal-hal tambahan seperti indeks bagian) atau tampilan aksesori) dan batasan tata letak otomatis yang telah Anda tambahkan ke tampilan dan subview konten sel. Setelah tinggi sel aktual ini telah ditentukan, tinggi estimasi lama untuk baris diperbarui dengan tinggi aktual baru (dan setiap penyesuaian pada konten tampilan tabelSize / contentOffset dibuat sesuai kebutuhan untuk Anda).
Secara umum, perkiraan yang Anda berikan tidak harus sangat akurat - itu hanya digunakan untuk mengukur dengan benar indikator gulir dalam tampilan tabel, dan tampilan tabel melakukan pekerjaan yang baik untuk menyesuaikan indikator gulir untuk perkiraan yang salah saat Anda gulirkan sel di layar. Anda harus mengatur estimatedRowHeight
properti pada tampilan tabel (dalam viewDidLoad
atau serupa) dengan nilai konstan yaitu tinggi baris "rata-rata". Hanya jika ketinggian baris Anda memiliki variabilitas ekstrem (mis. Berbeda berdasarkan urutan besarnya) dan Anda melihat indikator gulir "melompat" ketika Anda menggulir, Anda harus bersusah payah menerapkan tableView:estimatedHeightForRowAtIndexPath:
untuk melakukan perhitungan minimal yang diperlukan untuk mengembalikan perkiraan yang lebih akurat untuk setiap baris.
Untuk dukungan iOS 7 (menerapkan ukuran sel otomatis sendiri)
3. Lakukan Layout Pass & Dapatkan Tinggi Sel
Pertama, instantiate instance layar tampilan sel tampilan tabel, satu instance untuk setiap pengidentifikasi ulang , yang digunakan secara ketat untuk perhitungan ketinggian. (Offscreen artinya referensi sel disimpan dalam properti / ivar pada pengontrol tampilan dan tidak pernah kembali dari tableView:cellForRowAtIndexPath:
tampilan tabel untuk benar-benar membuat layar). Selanjutnya, sel harus dikonfigurasi dengan konten yang tepat (misalnya teks, gambar, dll) bahwa itu akan berlaku jika itu akan ditampilkan dalam tampilan tabel.
Kemudian, paksa sel untuk segera menata subview, dan kemudian gunakan systemLayoutSizeFittingSize:
metode pada UITableViewCell
's contentView
untuk mengetahui berapa tinggi sel yang diperlukan. Gunakan UILayoutFittingCompressedSize
untuk mendapatkan ukuran terkecil yang diperlukan agar sesuai dengan semua isi sel. Ketinggian kemudian dapat dikembalikan dari tableView:heightForRowAtIndexPath:
metode delegasi.
4. Gunakan Estimated Row Heights
Jika tampilan tabel Anda memiliki lebih dari beberapa lusin baris di dalamnya, Anda akan menemukan bahwa melakukan penyelesaian kendala Tata Letak Otomatis dapat dengan cepat memotong utas saat pertama memuat tampilan tabel, seperti tableView:heightForRowAtIndexPath:
yang dipanggil pada setiap baris pada beban pertama ( untuk menghitung ukuran indikator gulir).
Pada iOS 7, Anda dapat (dan benar-benar harus) menggunakan estimatedRowHeight
properti pada tampilan tabel. Apa yang dilakukan adalah menyediakan tampilan tabel dengan estimasi sementara / placeholder untuk ketinggian baris sel yang belum tampil di layar. Kemudian, ketika sel-sel ini akan bergulir di layar, tinggi baris aktual akan dihitung (dengan menelepon tableView:heightForRowAtIndexPath:
), dan estimasi tinggi diperbarui dengan yang sebenarnya.
Secara umum, perkiraan yang Anda berikan tidak harus sangat akurat - itu hanya digunakan untuk mengukur dengan benar indikator gulir dalam tampilan tabel, dan tampilan tabel melakukan pekerjaan yang baik untuk menyesuaikan indikator gulir untuk perkiraan yang salah saat Anda gulirkan sel di layar. Anda harus mengatur estimatedRowHeight
properti pada tampilan tabel (dalam viewDidLoad
atau serupa) dengan nilai konstan yaitu tinggi baris "rata-rata". Hanya jika ketinggian baris Anda memiliki variabilitas ekstrem (mis. Berbeda berdasarkan urutan besarnya) dan Anda melihat indikator gulir "melompat" ketika Anda menggulir, Anda harus bersusah payah menerapkan tableView:estimatedHeightForRowAtIndexPath:
untuk melakukan perhitungan minimal yang diperlukan untuk mengembalikan perkiraan yang lebih akurat untuk setiap baris.
5. (Jika Diperlukan) Tambahkan Caching Ketinggian Baris
Jika Anda telah melakukan semua hal di atas dan masih menemukan bahwa kinerja sangat lambat saat melakukan penyelesaian kendala tableView:heightForRowAtIndexPath:
, sayangnya Anda harus menerapkan beberapa caching untuk ketinggian sel. (Ini adalah pendekatan yang disarankan oleh para insinyur Apple.) Gagasan umum adalah membiarkan mesin Autolayout memecahkan kendala pertama kali, lalu menyimpan cache yang dihitung tinggi untuk sel itu dan menggunakan nilai cache untuk semua permintaan di masa depan untuk tinggi sel itu. Triknya tentu saja adalah memastikan Anda menghapus ketinggian cache untuk sebuah sel ketika terjadi sesuatu yang dapat menyebabkan tinggi sel berubah - terutama, ini akan terjadi ketika konten sel itu berubah atau ketika peristiwa penting lainnya terjadi (seperti pengguna menyesuaikan panel geser ukuran teks Tipe Dinamis).
Kode Sampel Generik iOS 7 (dengan banyak komentar menarik)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Determine which reuse identifier should be used for the cell at this
// index path, depending on the particular layout required (you may have
// just one, or may have many).
NSString *reuseIdentifier = ...;
// Dequeue a cell for the reuse identifier.
// Note that this method will init and return a new cell if there isn't
// one available in the reuse pool, so either way after this line of
// code you will have a cell with the correct constraints ready to go.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
// Configure the cell with content for the given indexPath, for example:
// cell.textLabel.text = someTextForThisCell;
// ...
// Make sure the constraints have been set up for this cell, since it
// may have just been created from scratch. Use the following lines,
// assuming you are setting up constraints from within the cell's
// updateConstraints method:
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
// If you are using multi-line UILabels, don't forget that the
// preferredMaxLayoutWidth needs to be set correctly. Do it at this
// point if you are NOT doing it within the UITableViewCell subclass
// -[layoutSubviews] method. For example:
// cell.multiLineLabel.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds);
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Determine which reuse identifier should be used for the cell at this
// index path.
NSString *reuseIdentifier = ...;
// Use a dictionary of offscreen cells to get a cell for the reuse
// identifier, creating a cell and storing it in the dictionary if one
// hasn't already been added for the reuse identifier. WARNING: Don't
// call the table view's dequeueReusableCellWithIdentifier: method here
// because this will result in a memory leak as the cell is created but
// never returned from the tableView:cellForRowAtIndexPath: method!
UITableViewCell *cell = [self.offscreenCells objectForKey:reuseIdentifier];
if (!cell) {
cell = [[YourTableViewCellClass alloc] init];
[self.offscreenCells setObject:cell forKey:reuseIdentifier];
}
// Configure the cell with content for the given indexPath, for example:
// cell.textLabel.text = someTextForThisCell;
// ...
// Make sure the constraints have been set up for this cell, since it
// may have just been created from scratch. Use the following lines,
// assuming you are setting up constraints from within the cell's
// updateConstraints method:
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
// Set the width of the cell to match the width of the table view. This
// is important so that we'll get the correct cell height for different
// table view widths if the cell's height depends on its width (due to
// multi-line UILabels word wrapping, etc). We don't need to do this
// above in -[tableView:cellForRowAtIndexPath] because it happens
// automatically when the cell is used in the table view. Also note,
// the final width of the cell may not be the width of the table view in
// some cases, for example when a section index is displayed along
// the right side of the table view. You must account for the reduced
// cell width.
cell.bounds = CGRectMake(0.0, 0.0, CGRectGetWidth(tableView.bounds), CGRectGetHeight(cell.bounds));
// Do the layout pass on the cell, which will calculate the frames for
// all the views based on the constraints. (Note that you must set the
// preferredMaxLayoutWidth on multiline UILabels inside the
// -[layoutSubviews] method of the UITableViewCell subclass, or do it
// manually at this point before the below 2 lines!)
[cell setNeedsLayout];
[cell layoutIfNeeded];
// Get the actual height required for the cell's contentView
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
// Add an extra point to the height to account for the cell separator,
// which is added between the bottom of the cell's contentView and the
// bottom of the table view cell.
height += 1.0;
return height;
}
// NOTE: Set the table view's estimatedRowHeight property instead of
// implementing the below method, UNLESS you have extreme variability in
// your row heights and you notice the scroll indicator "jumping"
// as you scroll.
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Do the minimal calculations required to be able to return an
// estimated row height that's within an order of magnitude of the
// actual height. For example:
if ([self isTallCellAtIndexPath:indexPath]) {
return 350.0;
} else {
return 40.0;
}
}
Proyek Sampel
Proyek-proyek ini adalah contoh-contoh tampilan tabel yang berfungsi penuh dengan ketinggian baris variabel karena sel-sel tampilan tabel yang berisi konten dinamis dalam UILabel.
Xamarin (C # /. NET)
Jika Anda menggunakan Xamarin, periksa proyek sampel ini disatukan oleh @KentBoogaart .