Mengubah ukuran UITableView agar sesuai dengan konten

170

Saya membuat aplikasi yang akan memiliki pertanyaan UILabeldan jawaban pilihan ganda ditampilkan di UITableView, setiap baris menunjukkan pilihan ganda. Pertanyaan dan jawaban akan berbeda-beda, jadi saya perlu UITableViewketinggian dinamis.

Saya ingin mencari sizeToFitpekerjaan di sekitar meja. Di mana bingkai tabel diatur ke ketinggian semua kontennya.

Adakah yang bisa memberi saran tentang bagaimana saya bisa mencapai ini?

MiMo
sumber
Bagi siapa pun yang ingin melakukan hal serupa di OSX, lihat pertanyaan ini: stackoverflow.com/questions/11322139/…
Michael Teper
1
@MiMo hai, bisakah Anda menjelaskan apa yang salah dengan pendekatan "sizeToFit"? :)
iamdavidlam
"Saya ingin mencari" sizeToFit "di sekitar" . Jadi, apakah Anda sudah atau belum pernah mencoba - sizeToFitmetode UIView ?
Slipp D. Thompson

Jawaban:

155

Sebenarnya saya menemukan jawabannya sendiri.

Saya cukup membuat baru CGRectuntuk tableView.framedengan heightdaritable.contentSize.height

Yang menentukan ketinggian UITableViewke heightkontennya. Karena kode memodifikasi UI, jangan lupa untuk menjalankannya di utas utama:

dispatch_async(dispatch_get_main_queue(), ^{
        //This code will run in the main thread:
        CGRect frame = self.tableView.frame;
        frame.size.height = self.tableView.contentSize.height;
        self.tableView.frame = frame;
    });
MiMo
sumber
3
Dimungkinkan untuk mengalami masalah dengan metode ini. Jika Anda memiliki daftar besar, ada kemungkinan bahwa ketinggian bingkai akan memotong beberapa sel Anda. Anda dapat melihat ini terjadi jika Anda memantulkan diaktifkan dan gulir ke bawah untuk melihat beberapa sel terpental keluar dari pandangan.
whyoz
Di mana tepatnya Anda menentukan ketinggian? Apakah Anda membuat panggilan untuk memuat ulang Data dan mengubah ukurannya setelah kata-kata?
James
27
Di mana tempat terbaik untuk meletakkan kode ini? Saya mencobanya di viewDidLoad dan itu mungkin tidak berhasil karena metode delegasi tableview belum dipecat. Metode delegasi mana yang terbaik?
Kyle Clegg
4
Saya melakukan ini adalah viewDidAppear, dan tampaknya berhasil. Perhatikan bahwa tampilan tabel mungkin sedikit lebih tinggi daripada kontennya karena menyisakan sedikit ruang untuk header dan footer bagian
Jameo
2
Saya telah menemukan bahwa viewDidAppear adalah tempat yang tepat untuk meletakkan hal-hal yang Anda inginkan terjadi setelah semua panggilan awal tableView cellForRowAtIndexPath selesai
rigdonmr
120

Solusi Swift 5 dan 4.2 tanpa KVO, DispatchQueue, atau menetapkan batasan sendiri.

Solusi ini didasarkan pada jawaban Gulz .

1) Buat subkelas dari UITableView:

import UIKit

final class ContentSizedTableView: UITableView {
    override var contentSize:CGSize {
        didSet {
            invalidateIntrinsicContentSize()
        }
    }

    override var intrinsicContentSize: CGSize {
        layoutIfNeeded()
        return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
    }
}

2) Tambahkan UITableViewke tata letak Anda dan atur batasan di semua sisi. Atur kelasnya menjadi ContentSizedTableView.

3) Anda akan melihat beberapa kesalahan, karena Storyboard tidak memperhitungkan subclass kami intrinsicContentSize. Perbaiki ini dengan membuka inspektur ukuran dan menimpa intrinsicContentSize ke nilai placeholder. Ini merupakan penggantian untuk waktu desain. Saat runtime akan menggunakan override di ContentSizedTableViewkelas kami


Pembaruan: Kode yang diubah untuk Swift 4.2. Jika Anda menggunakan versi sebelumnya, gunakan UIViewNoIntrinsicMetricsebagai gantiUIView.noIntrinsicMetric

fl034
sumber
1
Jawaban yang sangat bagus, itu berhasil untuk saya, terima kasih. Satu hal - dalam kasus saya, saya perlu mengubah kelas tableViewmenjadi IntrinsicTableViewdi storyboard. Saya tidak perlu menyematkan tampilan meja saya di tempat lain UIView.
dvp.petrov
Ya kamu benar. Saya memperbarui jawaban saya. Langkah 2 bisa dibaca seperti yang Anda katakan. Tentu saja saya dimaksudkan untuk mengatur tableViewke IntrinsicTableView. Juga UIViewtidak diperlukan pembungkus tambahan . Anda tentu saja dapat menggunakan tampilan induk apa pun yang ada :)
fl034
1
Swift 4 Ini bekerja dengan baik untuk sayatableView.sizeToFit()
Souf ROCHDI
Ya itu mungkin solusi yang bagus, jika konten Anda statis. Dengan pendekatan saya, Anda dapat menggunakan Tata Letak Otomatis untuk menyematkan tableView dengan konten dinamis di tampilan lain dan menjaganya agar tetap tinggi setiap saat, tanpa memikirkan di tempat apa sizeToFit()perlu dipanggil
fl034
2
Bagus sekali, terima kasih!. Akhirnya cocok dengan induk dan bungkus konten untuk tampilan tabel ...
Amber K
79

Solusi Cepat

Ikuti langkah ini:

1- Atur batasan ketinggian untuk tabel dari storyboard.

2- Seret batasan ketinggian dari storyboard dan buat @IBOutlet untuknya dalam file view controller.

    @IBOutlet weak var tableHeight: NSLayoutConstraint!

3 - Kemudian Anda dapat mengubah ketinggian untuk tabel secara dinamis menggunakan kode ini:

override func viewWillLayoutSubviews() {
    super.updateViewConstraints()
    self.tableHeight?.constant = self.table.contentSize.height
}

Memperbarui

Jika baris terakhir dipotong untuk Anda, coba panggil viewWillLayoutSubviews () di fungsi sel willDisplay:

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    self.viewWillLayoutSubviews()
}
Musa almatri
sumber
1
Bekerja untukku !! thnx
Pushpa Y
5
Solusi ini selalu memotong baris terakhir pada Xcode 8.3 untuk saya.
Jen C
@ Jec C: Tidak untuk saya juga
KMC
Bekerja untukku juga !!
Antony Raphel
1
Saya harus meletakkan ini di metode cellForRowAtIndexPath: Jika saya memasukkannya ke dalam metode viewWillLayoutSubviews, tinggi contentSize yang dikomputasi didasarkan pada estimasi tinggi, bukan tinggi konten aktual.
Manu Mateos
21

Saya sudah mencoba ini di iOS 7 dan itu berhasil untuk saya

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.tableView sizeToFit];
}
Alexey
sumber
3
Jika UITableView Anda dinamis (artinya memuat informasi ke dan darinya dengan cepat), Anda harus meletakkan ini di tempat lain, bukan di viewDidLoad. Bagi saya, saya letakkan di cellForRowAtIndexPath. Juga harus mengubah "tableView" ke nama properti IBOutlet saya untuk menghindari kesalahan "properti tableView tidak ditemukan" yang lama.
jeremytripp
thx, punya masalah dengan mengubah ukuran tampilan tabel di dalam popover, itu berhasil
Zaporozhchenko Oleksandr
19

Tambahkan pengamat untuk properti contentSize pada tampilan tabel, dan sesuaikan ukuran bingkai sesuai

[your_tableview addObserver:self forKeyPath:@"contentSize" options:0 context:NULL];

lalu di callback:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
    {
         CGRect frame = your_tableview.frame;
         frame.size = your_tableview.contentSize;
         your_tableview.frame = frame;
    }

Semoga ini bisa membantu Anda.

Anooj VM
sumber
2
Anda harus menambahkan satu hal lagi. Anda harus menghapus pengamat untuk tampilan tabel. Karena itu akan macet jika sel di-deallocated.
Mahesh Agrawal
12

Saya memiliki tampilan tabel di dalam tampilan gulir dan harus menghitung tinggi tableView dan mengubah ukurannya. Itu adalah langkah-langkah yang telah saya ambil:

0) tambahkan UIView ke scrollView Anda (mungkin akan bekerja tanpa langkah ini, tetapi saya melakukannya untuk menghindari kemungkinan konflik) - ini akan menjadi tampilan containr untuk tampilan tabel Anda. Jika Anda mengambil langkah ini, maka atur batas tampilan langsung ke tampilan tabel.

1) membuat subkelas dari UITableView:

class IntrinsicTableView: UITableView {

    override var contentSize:CGSize {
        didSet {
            self.invalidateIntrinsicContentSize()
        }
    }

    override var intrinsicContentSize: CGSize {
        self.layoutIfNeeded()
        return CGSize(width: UIViewNoIntrinsicMetric, height: contentSize.height)
    }

}

2) mengatur kelas tampilan tabel di Storyboard ke IntrinsicTableView: tangkapan layar: http://joxi.ru/a2XEENpsyBWq0A

3) Setel batasan ketinggian ke tampilan tabel Anda

4) seret IBoutlet tabel Anda ke ViewController Anda

5) seret IBoutlet dari batasan ketinggian tabel Anda ke ViewController Anda

6) tambahkan metode ini ke dalam ViewController Anda:

override func viewWillLayoutSubviews() {
        super.updateViewConstraints()
        self.yourTableViewsHeightConstraint?.constant = self.yourTableView.intrinsicContentSize.height
    }

Semoga ini membantu

Gulz
sumber
Lihat jawaban saya yang didasarkan pada subkelas Anda tetapi tidak memerlukan pengaturan batasan ketinggian.
fl034
@ fl034 berikan tautan?
Gulz
@ fl034 Saya tidak berpikir Anda bisa menghindari menggunakan kendala ketika elemen Anda tertanam ke dalam Scroll View)
Kasing
Saya juga memiliki tampilan tabel di dalam tampilan gulir dan berfungsi dengan baik :)
fl034
bekerja untuk saya di iOS 13 / Xcode 11 Anda telah mengakhiri 3 hari penderitaan, Pak, terima kasih
Mehdi S.
8

Jika Anda tidak ingin melacak ukuran konten tampilan tabel berubah sendiri, Anda mungkin menemukan subkelas ini berguna.

protocol ContentFittingTableViewDelegate: UITableViewDelegate {
    func tableViewDidUpdateContentSize(_ tableView: UITableView)
}

class ContentFittingTableView: UITableView {

    override var contentSize: CGSize {
        didSet {
            if !constraints.isEmpty {
                invalidateIntrinsicContentSize()
            } else {
                sizeToFit()
            }

            if contentSize != oldValue {
                if let delegate = delegate as? ContentFittingTableViewDelegate {
                    delegate.tableViewDidUpdateContentSize(self)
                }
            }
        }
    }

    override var intrinsicContentSize: CGSize {
        return contentSize
    }

    override func sizeThatFits(_ size: CGSize) -> CGSize {
        return contentSize
    }
}
xinatanil
sumber
1
Untuk Swift 3, intrinsicContentSize telah menjadi var, sepertioverride public var intrinsicContentSize: CGSize { //... return someCGSize }
xaphod
tidak bekerja untukku. masih bersembunyi di bawah meja
Gulz
5

Jika konten Anda Ukuran tidak benar, ini karena didasarkan pada taksiranRowHeight (otomatis), gunakan ini sebelum

tableView.estimatedRowHeight = 0;

sumber: https://forums.developer.apple.com/thread/81895

Yohan Dahmani
sumber
4

Swift 3, iOS 10.3

Solusi 1: Hanya menempatkanself.tableview.sizeToFit()dicellForRowAt indexPath fungsi. Pastikan untuk mengatur tinggi tampilan tabel lebih tinggi dari yang Anda butuhkan. Ini adalah solusi yang baik jika Anda tidak memiliki pandangan di bawah tampilan tabel. Namun, jika sudah, batasan tableview bawah tidak akan diperbarui (saya tidak mencoba memperbaikinya karena saya datang dengan solusi 2)

Contoh:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    if let cell = tableView.dequeueReusableCell(withIdentifier: "TestCell", for: indexPath) as? TestCell {
        cell.configureCell(data: testArray[indexPath.row])
        self.postsTableView.sizeToFit()
        return cell
    }

    return UITableViewCell()
}

Solusi 2: Atur batasan ketinggian tampilan tabel di storyboard dan seret ke ViewController. Jika Anda tahu tinggi rata-rata sel Anda dan Anda tahu berapa banyak elemen yang terdapat dalam array Anda, Anda dapat melakukan sesuatu seperti ini:

tableViewHeightConstraint.constant = CGFloat(testArray.count) * 90.0     // Let's say 90 is the average cell height

* EDIT:

Setelah semua solusi saya coba dan semuanya memperbaiki sesuatu, tetapi tidak sepenuhnya, ini adalah jawaban yang menjelaskan dan memperbaiki masalah ini sepenuhnya.

Norđe Nilović
sumber
Ini adalah solusi yang sangat sederhana, ini berhasil untuk saya, Terima kasih @Dorde Nilovic
R. Mohan
1

Jawaban Mimo dan jawaban Anooj VM keduanya mengagumkan, tetapi ada masalah kecil jika Anda memiliki daftar besar, mungkin tinggi frame akan memotong beberapa sel Anda.

Begitu. Saya telah memodifikasi sedikit jawabannya:

dispatch_async(dispatch_get_main_queue()) {
    //This code will run in the main thread:
    CGFloat newHeight=self.tableView.contentSize.height;
    CGFloat screenHeightPermissible=(self.view.bounds.size.height-self.tableView.frame.origin.y);
    if (newHeight>screenHeightPermissible)
    {
        //so that table view remains scrollable when 'newHeight'  exceeds the screen bounds
        newHeight=screenHeightPermissible;
    }

    CGRect frame = self.tableView.frame;
    frame.size.height = newHeight;
    self.tableView.frame = frame;
}
Sandeep Aggarwal
sumber
1

Ada cara yang lebih baik untuk melakukannya jika Anda menggunakan AutoLayout: ubah kendala yang menentukan ketinggian. Hitung saja tinggi isi tabel Anda, kemudian temukan kendala dan ubahlah. Berikut ini sebuah contoh (dengan asumsi bahwa batasan yang menentukan tinggi tabel Anda sebenarnya adalah batasan ketinggian dengan relasi "Equal"):

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    for constraint in tableView.constraints {
        if constraint.firstItem as? UITableView == tableView {
            if constraint.firstAttribute == .height {
                constraint.constant = tableView.contentSize.height
            }
        }
    }
}
ChrisB
sumber
Anda dapat memperbaiki ini. Tambahkan batasan tambahan pada tampilan tabel Anda, katakanlah, tinggi <= 10000. Gunakan kontrol-tarik dari Interface Builder ke file .h Anda untuk menjadikan kendala ini sebagai properti dari pengontrol tampilan Anda. Maka Anda dapat menghindari iterasi melalui kendala dan pencarian. Langsung saja diaturmyTableHeightConstraint.constant = tableView.contentSize.height
RobP
1

Ini berfungsi untuk saya menggunakan Tata Letak Otomatis, dengan tampilan tabel dengan hanya satu bagian.

func getTableViewContentHeight(tableView: UITableView) -> CGFloat {
        tableView.bounds = CGRect(x: 0, y: 0, width: 300, height: 40)
        let rows = tableView.numberOfRows(inSection: 0)
        var height = CGFloat(0)
        for n in 0...rows - 1 {
            height = height + tableView.rectForRow(at: IndexPath(row: n, section: 0)).height
        }
        return height
    }

Saya memanggil fungsi ini ketika mengatur Tata Letak Otomatis (Sampel di sini menggunakan SnapKit, tetapi Anda mendapatkan idenya):

    let height = getTableViewContentHeight(tableView: myTableView)
    myTableView.snp.makeConstraints {
        ...
        ...
        $0.height.equalTo(height)
    }

Saya ingin UITableView hanya setinggi gabungan sel; Saya memutar melalui sel dan mengakumulasi tinggi total sel. Karena ukuran tampilan tabel adalah CGRect.zero pada titik ini, saya perlu mengatur batas untuk dapat menghormati aturan Tata Letak Otomatis yang ditentukan oleh sel. Saya mengatur ukuran ke nilai arbitrer yang seharusnya cukup besar. Ukuran aktual akan dihitung kemudian oleh sistem Tata Letak Otomatis.

André
sumber
1

Saya lakukan dengan cara yang sedikit berbeda, Sebenarnya TableView saya ada di dalam scrollview jadi saya harus memberikan batasan ketinggian 0.

Kemudian pada saat runtime saya membuat perubahan berikut,

       func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
            self.viewWillLayoutSubviews()
       }
    
       override func viewWillLayoutSubviews() {
            super.updateViewConstraints()
             DispatchQueue.main.async {
               self.tableViewHeightConstraint?.constant = self.myTableView.contentSize.height
               self.view.layoutIfNeeded()
          }
       }
Niki
sumber
0

Sebagai perpanjangan dari jawaban Anooj VM, saya menyarankan yang berikut untuk me - refresh ukuran konten hanya ketika itu berubah.

Pendekatan ini juga menonaktifkan gulir dengan benar dan mendukung daftar dan rotasi yang lebih besar . Tidak perlu dispatch_async karena perubahan contentSize dikirim pada utas utama.

- (void)viewDidLoad {
        [super viewDidLoad];
        [self.tableView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:NULL]; 
}


- (void)resizeTableAccordingToContentSize:(CGSize)newContentSize {
        CGRect superviewTableFrame  = self.tableView.superview.bounds;
        CGRect tableFrame = self.tableView.frame;
        BOOL shouldScroll = newContentSize.height > superviewTableFrame.size.height;
        tableFrame.size = shouldScroll ? superviewTableFrame.size : newContentSize;
        [UIView animateWithDuration:0.3
                                    delay:0
                                    options:UIViewAnimationOptionCurveLinear
                                    animations:^{
                            self.tableView.frame = tableFrame;
        } completion: nil];
        self.tableView.scrollEnabled = shouldScroll;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    if ([change[NSKeyValueChangeKindKey] unsignedIntValue] == NSKeyValueChangeSetting &&
        [keyPath isEqualToString:@"contentSize"] &&
        !CGSizeEqualToSize([change[NSKeyValueChangeOldKey] CGSizeValue], [change[NSKeyValueChangeNewKey] CGSizeValue])) {
        [self resizeTableAccordingToContentSize:[change[NSKeyValueChangeNewKey] CGSizeValue]];
    } 
}

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
    [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
    [self resizeTableAccordingToContentSize:self.tableView.contentSize]; }

- (void)dealloc {
    [self.tableView removeObserver:self forKeyPath:@"contentSize"];
}
Ramiro
sumber
0

Versi objc dari Musa almatri

(void)viewWillLayoutSubviews
{
    [super updateViewConstraints];
    CGFloat desiredHeight = self.tableView.contentSize.height;
    // clamp desired height, if needed, and, in that case, leave scroll Enabled
    self.tableHeight.constant = desiredHeight;
    self.tableView.scrollEnabled = NO;
}
Anton Tropashko
sumber
0

Anda dapat mencoba kebiasaan ini AGTableView

Untuk Mengatur Kendala Ketinggian TableView Menggunakan storyboard atau secara terprogram. (Kelas ini secara otomatis mengambil batasan ketinggian dan mengatur tinggi tampilan konten ke tinggi tampilan Anda).

class AGTableView: UITableView {

    fileprivate var heightConstraint: NSLayoutConstraint!

    override init(frame: CGRect, style: UITableViewStyle) {
        super.init(frame: frame, style: style)
        self.associateConstraints()
    }

    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.associateConstraints()
    }

    override open func layoutSubviews() {
        super.layoutSubviews()

        if self.heightConstraint != nil {
            self.heightConstraint.constant = self.contentSize.height
        }
        else{
            self.sizeToFit()
            print("Set a heightConstraint to Resizing UITableView to fit content")
        }
    }

    func associateConstraints() {
        // iterate through height constraints and identify

        for constraint: NSLayoutConstraint in constraints {
            if constraint.firstAttribute == .height {
                if constraint.relation == .equal {
                    heightConstraint = constraint
                }
            }
        }
    }
}

Catatan Jika ada masalah untuk mengatur Ketinggian lalu yourTableView.layoutSubviews().

AshvinGudaliya
sumber
0

Berdasarkan jawaban dari fl034 . Tetapi untuk pengguna Xamarin.iOS :

    [Register("ContentSizedTableView")]
    public class ContentSizedTableView : UITableView
    {
        public ContentSizedTableView(IntPtr handle) : base(handle)
        {
        }

        public override CGSize ContentSize { get => base.ContentSize; set { base.ContentSize = value; InvalidateIntrinsicContentSize(); } }
        public override CGSize IntrinsicContentSize
        {
            get
            {
                this.LayoutIfNeeded();
                return new CGSize(width: NoIntrinsicMetric, height: ContentSize.Height);
            }
        }
    }
Jelle
sumber
0

Implementasi Swift 5 saya adalah mengatur batasan hight dari tableView ke ukuran kontennya ( contentSize.height). Metode ini mengasumsikan Anda menggunakan tata letak otomatis. Kode ini harus ditempatkan di dalam cellForRowAtmetode tableView.

tableView.heightAnchor.constraint(equalToConstant: tableView.contentSize.height).isActive = true
Dominic Egginton
sumber
-1

Jika Anda ingin tabel Anda menjadi dinamis, Anda harus menggunakan solusi berdasarkan konten tabel seperti yang dijelaskan di atas. Jika Anda hanya ingin menampilkan tabel yang lebih kecil, Anda dapat menggunakan tampilan kontainer dan menanamkan UITableViewController di dalamnya - UITableView akan diubah ukurannya sesuai dengan ukuran wadah.

Ini menghindari banyak perhitungan dan panggilan ke tata letak.

green_knight
sumber
1
Jangan gunakan kata abovekarena jawaban bisa dikocok.
Nik Kov
-3

Solusi mu untuk ini dalam cepat 3 : Panggil metode ini masukviewDidAppear

func UITableView_Auto_Height(_ t : UITableView)
{
        var frame: CGRect = t.frame;
        frame.size.height = t.contentSize.height;
        t.frame = frame;        
}
brahim
sumber