Bagaimana cara mengatur tinggi tableHeaderView (UITableView) dengan autolayout?

86

Saya telah membenturkan kepala saya ke dinding dengan ini selama 3 atau 4 jam terakhir dan sepertinya saya tidak bisa memahaminya. Saya memiliki UIViewController dengan UITableView layar penuh di dalamnya (ada beberapa hal lain di layar, itulah sebabnya saya tidak dapat menggunakan UITableViewController) dan saya ingin tableHeaderView saya diubah ukurannya dengan autolayout. Tak perlu dikatakan, itu tidak bekerja sama.

Lihat gambar di bawah.

masukkan deskripsi gambar di sini

Karena label overview (mis. Teks "Cantumkan informasi ikhtisar di sini.") Memiliki konten dinamis, saya menggunakan autolayout untuk mengubah ukurannya dan ini adalah tampilan super. Saya telah mengubah ukurannya dengan baik, kecuali untuk tableHeaderView, yang berada tepat di bawah Paralax Table View di hiearchy.

Satu-satunya cara yang saya temukan untuk mengubah ukuran tampilan header itu secara terprogram, dengan kode berikut:

CGRect headerFrame = self.headerView.frame;
headerFrame.size.height = headerFrameHeight;
self.headerView.frame = headerFrame;
[self.listTableView setTableHeaderView:self.headerView];

Dalam kasus ini, headerFrameHeight adalah kalkulasi manual dari tinggi tableViewHeader sebagai berikut (innerHeaderView adalah area putih, atau "View" kedua, headerView adalah tableHeaderView) :

CGFloat startingY = self.innerHeaderView.frame.origin.y + self.overviewLabel.frame.origin.y;
CGRect overviewSize = [self.overviewLabel.text
                       boundingRectWithSize:CGSizeMake(290.f, CGFLOAT_MAX)
                       options:NSStringDrawingUsesLineFragmentOrigin
                       attributes:@{NSFontAttributeName: self.overviewLabel.font}
                       context:nil];
CGFloat overviewHeight = overviewSize.size.height;
CGFloat overviewPadding = ([self.overviewLabel.text length] > 0) ? 10 : 0; // If there's no overviewText, eliminate the padding in the overall height.
CGFloat headerFrameHeight = ceilf(startingY + overviewHeight + overviewPadding + 21.f + 10.f);

Penghitungan manual berfungsi, tetapi kikuk dan rawan kesalahan jika ada perubahan di masa mendatang. Apa yang ingin saya lakukan adalah mengubah ukuran otomatis tableHeaderView berdasarkan batasan yang disediakan, seperti yang Anda bisa lakukan di tempat lain. Tapi untuk hidupku, aku tidak bisa memahaminya.

Ada beberapa posting di SO tentang ini, tetapi tidak ada yang jelas dan akhirnya lebih membingungkan saya. Ini beberapa:

Tidaklah masuk akal untuk mengubah properti translatesAutoresizingMaskIntoConstraints menjadi NO, karena hal itu hanya menyebabkan kesalahan bagi saya dan tidak masuk akal secara konseptual.

Bantuan apapun akan sangat dihargai!

EDIT 1: Berkat saran TomSwift, saya dapat mengetahuinya. Alih-alih menghitung secara manual ketinggian ikhtisar, saya dapat menghitungnya untuk saya sebagai berikut dan kemudian mengatur ulang tableHeaderView seperti sebelumnya.

[self.headerView setNeedsLayout];
[self.headerView layoutIfNeeded];
CGFloat height = [self.innerHeaderView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height + self.innerHeaderView.frame.origin.y; // adding the origin because innerHeaderView starts partway down headerView.

CGRect headerFrame = self.headerView.frame;
headerFrame.size.height = height;
self.headerView.frame = headerFrame;
[self.listTableView setTableHeaderView:self.headerView];

Sunting 2: Seperti yang telah dicatat orang lain, solusi yang diposting di Sunting 1 tampaknya tidak berfungsi di viewDidLoad. Namun, tampaknya berfungsi di viewWillLayoutSubviews. Contoh kode di bawah ini:

// Note 1: The variable names below don't match the variables above - this is intended to be a simplified "final" answer.
// Note 2: _headerView was previously assigned to tableViewHeader (in loadView in my case since I now do everything programatically).
// Note 3: autoLayout code can be setup programatically in updateViewConstraints.
- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];

    [_headerWrapper setNeedsLayout];
    [_headerWrapper layoutIfNeeded];
    CGFloat height = [_headerWrapper systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;

    CGRect headerFrame = _headerWrapper.frame;
    headerFrame.size.height = height;
    _headerWrapper.frame = headerFrame;
    _tableView.tableHeaderView = _headerWrapper;
}
Andrew Cross
sumber
2
setTableHeaderViewtidak berfungsi di Xcode6. Masalahnya adalah sel-sel tersebut tumpang tindih dengan tableHeaderView. Namun, ini berfungsi pada Xcode5
DawnSong
@DawnSong tahu solusi untuk Xcode6 sehingga saya dapat memperbarui jawabannya?
Andrew Cross
1
Setelah banyak uji coba, saya menggunakan UITableViewCell sebagai ganti tableHeaderView, dan berhasil.
DawnSong
Aku juga menghancurkan kepalaku!
Akshit Zaveri
Solusi autolayout lengkap yang sebenarnya ada di sini
malex

Jawaban:

35

Anda perlu menggunakan UIView systemLayoutSizeFittingSize: metode ini untuk mendapatkan ukuran batas minimum tampilan header Anda.

Saya memberikan pembahasan lebih lanjut tentang penggunaan API ini dalam Tanya Jawab ini:

Bagaimana cara mengubah ukuran superview agar sesuai dengan semua subview dengan autolayout?

TomSwift
sumber
Ini kembali dengan height = 0, meskipun saya tidak 100% yakin saya telah mengonfigurasinya dengan benar karena ini bukan UITableViewCell, jadi tidak ada contentView untuk memanggil "systemLayoutSizeFittingSize". Apa yang saya coba: [self.headerView setNeedsLayout]; [self.headerView layoutIfNeeded]; Tinggi CGFloat = [self.headerView systemLayoutSizeFittingSize: UILayoutFittingCompressedSize] .height; CGRect headerFrame = self.headerView.frame; headerFrame.size.height = tinggi; self.headerView.frame = headerFrame; [self.listTableView setTableHeaderView: self.headerView]; Saya juga mengonfirmasi bahwa preferMax ... valid.
Andrew Cross
Aha! Setelah mengetahuinya, saya perlu melakukan [self.innerHeaderView systemLayoutSizeFittingSize ...] daripada self.headerView karena itulah yang memiliki konten dinamis aktual. Terima kasih banyak!
Andrew Cross
Ini berfungsi tetapi di aplikasi saya ada perbedaan ketinggian kecil. Mungkin karena label saya menggunakan teks yang dikaitkan dan Jenis Dinamis.
bio
23

Saya benar-benar berjuang dengan yang satu ini dan memasukkan pengaturan ke viewDidLoad tidak berfungsi untuk saya karena bingkai tidak disetel di viewDidLoad, saya juga berakhir dengan banyak peringatan yang berantakan di mana tinggi tata letak otomatis yang dienkapsulasi dari header dikurangi menjadi 0 Saya hanya memperhatikan masalah di iPad saat mempresentasikan tableView dalam presentasi Formulir.

Apa yang memecahkan masalah bagi saya adalah menyetel tableViewHeader di viewWillLayoutSubviews daripada di viewDidLoad.

func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        if tableView.tableViewHeaderView == nil {
            let header: MyHeaderView = MyHeaderView.createHeaderView()
            header.setNeedsUpdateConstraints()
            header.updateConstraintsIfNeeded()
            header.frame = CGRectMake(0, 0, CGRectGetWidth(tableView.bounds), CGFloat.max)
            var newFrame = header.frame
            header.setNeedsLayout()
            header.layoutIfNeeded()
            let newSize = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
            newFrame.size.height = newSize.height
            header.frame = newFrame
            self.tableView.tableHeaderView = header
        }
    }
Daniel Galasko
sumber
untuk solusi yang diperbarui dengan kode minimal coba ini: stackoverflow.com/a/63594053/3933302
Sourabh Sharma
23

Saya telah menemukan cara elegan untuk menggunakan tata letak otomatis untuk mengubah ukuran header tabel, dengan dan tanpa animasi.

Cukup tambahkan ini ke Pengontrol Tampilan Anda.

func sizeHeaderToFit(tableView: UITableView) {
    if let headerView = tableView.tableHeaderView {
        let height = headerView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
        var frame = headerView.frame
        frame.size.height = height
        headerView.frame = frame
        tableView.tableHeaderView = headerView
        headerView.setNeedsLayout()
        headerView.layoutIfNeeded()
    }
}

Untuk mengubah ukuran sesuai dengan label yang berubah secara dinamis:

@IBAction func addMoreText(sender: AnyObject) {
    self.label.text = self.label.text! + "\nThis header can dynamically resize according to its contents."
}

override func viewDidLayoutSubviews() {
    // viewDidLayoutSubviews is called when labels change.
    super.viewDidLayoutSubviews()
    sizeHeaderToFit(tableView)
}

Untuk menganimasikan perubahan ukuran sesuai dengan perubahan dalam batasan:

@IBOutlet weak var makeThisTallerHeight: NSLayoutConstraint!

@IBAction func makeThisTaller(sender: AnyObject) {

    UIView.animateWithDuration(0.3) {
        self.tableView.beginUpdates()
        self.makeThisTallerHeight.constant += 20
        self.sizeHeaderToFit(self.tableView)
        self.tableView.endUpdates()
    }
}

Lihat proyek AutoResizingHeader untuk melihat ini beraksi. https://github.com/p-sun/Swift2-iOS9-UI

AutoResizingHeader

p-sun
sumber
Hei p-sun. Terima kasih untuk contoh yang bagus. Sepertinya saya memiliki masalah dengan tableViewHeader. Saya mencoba memiliki kendala yang sama seperti pada contoh Anda, tetapi tidak berhasil. Bagaimana tepatnya saya harus menambahkan batasan ke label yang ingin saya tambah tinggi? Terima kasih atas saran apa pun
Bonnke
1
Pastikan Anda mengaitkan label yang ingin Anda buat lebih tinggi @IBOutlet makeThisTallerdan @IBAction fun makeThisTallersuka dalam contoh. Juga, batasi semua sisi label Anda ke tableViewHeader (misalnya atas, bawah, kiri, dan kanan).
p-sun
Terima kasih banyak! Akhirnya diselesaikan dengan menambahkan baris ini: di lblFeedDescription.preferredMaxLayoutWidth = lblFeedDescription.bounds.widthmana label adalah salah satu yang ingin saya tingkatkan ukurannya. Terima kasih!
Bonnke
Terima kasih! Jika Anda melakukan ini di dalam View alih-alih ViewController, maka alih-alih menimpa viewDidLayoutSubviews, Anda dapat mengganti layoutSubviews
Andy Weinstein
4

Solusi ini mengubah ukuran tableHeaderView dan menghindari loop tak terbatas dalam viewDidLayoutSubviews()metode yang saya alami dengan beberapa jawaban lain di sini:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    if let headerView = tableView.tableHeaderView {
        let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
        var headerFrame = headerView.frame

        // comparison necessary to avoid infinite loop
        if height != headerFrame.size.height {
            headerFrame.size.height = height
            headerView.frame = headerFrame
            tableView.tableHeaderView = headerView
        }
    }
}

Lihat juga posting ini: https://stackoverflow.com/a/34689293/1245231

petrsyn.dll
sumber
1
Saya menggunakan metode ini juga. Pikirkan itu yang terbaik karena tidak ada kendala yang mengotak-atik. Ini juga sangat kompak.
bio
3

Solusi Anda menggunakan systemLayoutSizeFittingSize: berfungsi jika tampilan header hanya diperbarui satu kali pada setiap tampilan tampilan. Dalam kasus saya, tampilan tajuk diperbarui beberapa kali untuk mencerminkan perubahan status. Tetapi systemLayoutSizeFittingSize: selalu melaporkan ukuran yang sama. Artinya, ukurannya sesuai dengan pembaruan pertama.

Untuk mendapatkan systemLayoutSizeFittingSize: untuk mengembalikan ukuran yang benar setelah setiap pembaruan, saya harus terlebih dahulu menghapus tampilan header tabel sebelum memperbaruinya dan menambahkannya kembali:

self.listTableView.tableHeaderView = nil;
[self.headerView removeFromSuperview];
Kim André Sand
sumber
2

Ini berfungsi untuk saya di ios10 dan Xcode 8

func layoutTableHeaderView() {

    guard let headerView = tableView.tableHeaderView else { return }
    headerView.translatesAutoresizingMaskIntoConstraints = false

    let headerWidth = headerView.bounds.size.width;
    let temporaryWidthConstraints = NSLayoutConstraint.constraintsWithVisualFormat("[headerView(width)]", options: NSLayoutFormatOptions(rawValue: UInt(0)), metrics: ["width": headerWidth], views: ["headerView": headerView])

    headerView.addConstraints(temporaryWidthConstraints)

    headerView.setNeedsLayout()
    headerView.layoutIfNeeded()

    let headerSize = headerView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
    let height = headerSize.height
    var frame = headerView.frame

    frame.size.height = height
    headerView.frame = frame

    self.tableView.tableHeaderView = headerView

    headerView.removeConstraints(temporaryWidthConstraints)
    headerView.translatesAutoresizingMaskIntoConstraints = true

}
Ravi Randeria
sumber
2

Ini berfungsi untuk tampilan header dan footer, cukup ganti header dengan footer

func sizeHeaderToFit() {
    if let headerView = tableView.tableHeaderView {

        headerView.setNeedsLayout()
        headerView.layoutIfNeeded()

        let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
        var frame = headerView.frame
        frame.size.height = height
        headerView.frame = frame

        tableView.tableHeaderView = headerView
    }
}
Sai kumar Reddy
sumber
1

Untuk iOS 12 dan yang lebih baru, langkah-langkah berikut akan memastikan tata letak otomatis berfungsi dengan baik di tabel dan header Anda.

  1. Buat tableView Anda terlebih dahulu, lalu header.
  2. Di akhir kode pembuatan header Anda, panggil:
[headerV setNeedsLayout];
[headerV layoutIfNeeded];
  1. Setelah orientasi berubah, tandai lagi tajuk untuk tata letak dan muat ulang tabel, ini perlu dilakukan sedikit setelah perubahan orientasi dilaporkan:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 *NSEC_PER_SEC), dispatch_get_main_queue(), ^{

  [tableV.tableHeaderView setNeedsLayout];
  [tableV.tableHeaderView layoutIfNeeded];
  [tableV reloadData];});
RunLoop
sumber
0

Dalam kasus saya viewDidLayoutSubviewsbekerja lebih baik. viewWillLayoutSubviewsmenyebabkan garis putih a tableViewmuncul. Juga saya menambahkan memeriksa apakah headerViewobjek saya sudah ada.

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];

    if ( ! self.userHeaderView ) {
        // Setup HeaderView
        self.userHeaderView = [[[NSBundle mainBundle] loadNibNamed:@"SSUserHeaderView" owner:self options:nil] objectAtIndex:0];
        [self.userHeaderView setNeedsLayout];
        [self.userHeaderView layoutIfNeeded];
        CGFloat height = [self.userHeaderView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
        CGRect headerFrame = self.userHeaderView.frame;
        headerFrame.size.height = height;
        self.userHeaderView.frame = headerFrame;
        self.tableView.tableHeaderView = self.userHeaderView;

        // Update HeaderView with data
        [self.userHeaderView updateWithProfileData];
    }
}
Denis Kutlubaev
sumber
0

Sangat mungkin untuk menggunakan berbasis AutoLayout generik UIViewdengan struktur subview dalam AL apa pun sebagai tableHeaderView.

Satu-satunya hal yang dibutuhkan adalah mengatur yang sederhana tableFooterViewsebelumnya!

Mari self.headerViewadalah beberapa berbasis kendala UIView.

- (void)viewDidLoad {

    ........................

    self.tableView.tableFooterView = [UIView new];

    [self.headerView layoutIfNeeded]; // to set initial size

    self.tableView.tableHeaderView = self.headerView;

    [self.tableView.leadingAnchor constraintEqualToAnchor:self.headerView.leadingAnchor].active = YES;
    [self.tableView.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor].active = YES;
    [self.tableView.topAnchor constraintEqualToAnchor:self.headerView.topAnchor].active = YES;

    // and the key constraint
    [self.tableFooterView.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor].active = YES;
}

Jika self.headerViewmengubah ketinggian di bawah rotasi UI, kita harus mengimplementasikannya

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];

    [coordinator animateAlongsideTransition: ^(id<UIViewControllerTransitionCoordinatorContext> context) {
        // needed to resize header height
        self.tableView.tableHeaderView = self.headerView;
    } completion: NULL];
}

Seseorang dapat menggunakan kategori ObjC untuk tujuan ini

@interface UITableView (AMHeaderView)
- (void)am_insertHeaderView:(UIView *)headerView;
@end

@implementation UITableView (AMHeaderView)

- (void)am_insertHeaderView:(UIView *)headerView {

    NSAssert(self.tableFooterView, @"Need to define tableFooterView first!");

    [headerView layoutIfNeeded];

    self.tableHeaderView = headerView;

    [self.leadingAnchor constraintEqualToAnchor:headerView.leadingAnchor].active = YES;
    [self.trailingAnchor constraintEqualToAnchor:headerView.trailingAnchor].active = YES;
    [self.topAnchor constraintEqualToAnchor:headerView.topAnchor].active = YES;

    [self.tableFooterView.trailingAnchor constraintEqualToAnchor:headerView.trailingAnchor].active = YES;
}
@end

Dan juga ekstensi Swift

extension UITableView {

    func am_insertHeaderView2(_ headerView: UIView) {

        assert(tableFooterView != nil, "Need to define tableFooterView first!")

        headerView.layoutIfNeeded()

        tableHeaderView = headerView

        leadingAnchor.constraint(equalTo: headerView.leadingAnchor).isActive = true
        trailingAnchor.constraint(equalTo: headerView.trailingAnchor).isActive = true
        topAnchor.constraint(equalTo: headerView.topAnchor).isActive = true

        tableFooterView?.trailingAnchor.constraint(equalTo: headerView.trailingAnchor).isActive = true
    }
}
malex
sumber
0

Solusi ini bekerja dengan sempurna untuk saya:

https://spin.atomicobject.com/2017/08/11/swift-extending-uitableviewcontroller/

Ini memperluas UITableViewController. Tetapi jika Anda hanya menggunakan UITableView, ini akan tetap berfungsi, cukup perpanjang UITableView, bukan UITableViewController. Panggil metode sizeHeaderToFit()atau sizeFooterToFit()setiap kali ada kejadian yang mengubah tableViewHeaderketinggian.

meow2x
sumber
0

Disalin dari postingan ini

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    
    if let headerView = tableView.tableHeaderView {

        let height = headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
        var headerFrame = headerView.frame
        
        //Comparison necessary to avoid infinite loop
        if height != headerFrame.size.height {
            headerFrame.size.height = height
            headerView.frame = headerFrame
            tableView.tableHeaderView = headerView
        }
    }
}
Sourabh Sharma
sumber