Làm cách nào để đặt chiều cao của tableHeaderView (UITableView) với tính năng tự động thanh toán?


86

Tôi đã đập đầu vào tường với thứ này trong 3 hoặc 4 giờ qua và dường như tôi không thể tìm ra. Tôi có UIViewController với UITableView toàn màn hình bên trong nó (có một số thứ khác trên màn hình, đó là lý do tại sao tôi không thể sử dụng UITableViewController) và tôi muốn tableHeaderView của mình thay đổi kích thước bằng tính năng tự động thanh toán. Không cần phải nói, nó không hợp tác.

Xem ảnh chụp màn hình bên dưới.

nhập mô tả hình ảnh ở đây

Bởi vì nhãn OverviewLabel (ví dụ: văn bản "Liệt kê thông tin tổng quan ở đây.") Có nội dung động, tôi đang sử dụng autolayout để thay đổi kích thước và nó là superview. Tôi đã có mọi thứ thay đổi kích thước một cách tuyệt vời, ngoại trừ tableHeaderView, nằm ngay bên dưới Paralax Table View trong quá trình tìm kiếm.

Cách duy nhất tôi đã tìm thấy để thay đổi kích thước chế độ xem tiêu đề đó là theo chương trình, với đoạn mã sau:

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

Trong trường hợp này, headerFrameHeight là phép tính thủ công chiều cao tableViewHeader như sau (innerHeaderView là vùng trắng hoặc "Chế độ xem" thứ hai, headerView là 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);

Tính toán thủ công hoạt động, nhưng nó phức tạp và dễ bị sai nếu mọi thứ thay đổi trong tương lai. Những gì tôi muốn có thể làm là để tableHeaderView tự động thay đổi kích thước dựa trên các ràng buộc được cung cấp, giống như bạn có thể làm ở bất kỳ nơi nào khác. Nhưng đối với cuộc sống của tôi, tôi không thể hình dung được.

Có một số bài đăng trên SO về điều này, nhưng không có bài nào rõ ràng và cuối cùng khiến tôi khó hiểu hơn. Đây là một số:

Không thực sự có ý nghĩa khi thay đổi thuộc tính translateAutoresizingMaskIntoConstraints thành KHÔNG, vì điều đó chỉ gây ra lỗi cho tôi và không có ý nghĩa gì về mặt khái niệm.

Bất kỳ trợ giúp sẽ thực sự được đánh giá cao!

CHỈNH SỬA 1: Nhờ gợi ý của TomSwift, tôi đã có thể tìm ra. Thay vì tính toán chiều cao của tổng quan theo cách thủ công, tôi có thể tính toán nó cho tôi như sau và sau đó thiết lập lại tableHeaderView như trước.

[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];

Chỉnh sửa 2: Như những người khác đã lưu ý, giải pháp được đăng trong Chỉnh sửa 1 dường như không hoạt động trong viewDidLoad. Tuy nhiên, nó dường như hoạt động trong viewWillLayoutSubviews. Mã ví dụ bên dưới:

// 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;
}

2
setTableHeaderViewkhông hoạt động trên Xcode6. Vấn đề là các ô bị chồng chéo bởi tableHeaderView. Tuy nhiên, nó hoạt động trên Xcode5
DawnSong

@DawnSong có biết giải pháp cho Xcode6 để tôi cập nhật câu trả lời không?
Andrew Cross

1
Sau nhiều lần dùng thử, tôi sử dụng UITableViewCell thay vì tableHeaderView và nó hoạt động.
DawnSong

Tôi cũng đập đầu mình!
Akshit Zaveri

Giải pháp tự động thanh toán hoàn chỉnh thực sự là ở đây
malex

Câu trả lời:


35

Bạn cần sử dụng UIView systemLayoutSizeFittingSize:phương pháp này để có được kích thước giới hạn tối thiểu của dạng xem tiêu đề của bạn.

Tôi thảo luận thêm về việc sử dụng API này trong phần Hỏi / Đáp này:

Làm cách nào để thay đổi kích thước superview để phù hợp với tất cả các subview với tính năng tự động thanh toán?


Nó quay trở lại với height = 0, mặc dù tôi không chắc chắn 100% rằng mình đã định cấu hình nó ngay vì đây không phải là UITableViewCell, vì vậy không có contentView để gọi "systemLayoutSizeFittingSize". Những gì tôi đã thử: [self.headerView setNeedsLayout]; [self.headerView layoutIfNeeded]; CGFloat height = [self.headerView systemLayoutSizeFittingSize: UILayoutFittingCompressedSize] .height; CGRect headerFrame = self.headerView.frame; headerFrame.size.height = height; self.headerView.frame = headerFrame; [self.listTableView setTableHeaderView: self.headerView]; Tôi cũng xác nhận rằng ưu tiênMax ... là hợp lệ.
Andrew Cross

Aha! Tìm ra rồi, tôi cần thực hiện [self.innerHeaderView systemLayoutSizeFittingSize ...] thay vì self.headerView vì đó là thứ có nội dung động thực tế. Cảm ơn rất nhiều!
Andrew Cross

Nó hoạt động nhưng trong ứng dụng của tôi có một sự khác biệt nhỏ về chiều cao. Có thể là do các nhãn của tôi đang sử dụng văn bản được phân bổ và Loại động.
sinh học

23

Tôi thực sự đã chiến đấu với cái này và việc thiết lập vào viewDidLoad không hoạt động với tôi vì khung không được đặt trong viewDidLoad, tôi cũng đã nhận được hàng tấn cảnh báo lộn xộn trong đó chiều cao bố cục tự động đóng gói của tiêu đề bị giảm xuống 0 . Tôi chỉ nhận thấy sự cố trên iPad khi trình bày tableView trong bản trình bày Biểu mẫu.

Điều giải quyết được vấn đề đối với tôi là đặt tableViewHeader trong viewWillLayoutSubviews thay vì trong 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
        }
    }

để biết giải pháp cập nhật với mã tối thiểu, hãy thử cái này: stackoverflow.com/a/63594053/3933302
Sourabh Sharma

23

Tôi đã tìm thấy một cách thanh lịch để sử dụng bố cục tự động để thay đổi kích thước tiêu đề bảng, có và không có hoạt ảnh.

Chỉ cần thêm cái này vào View Controller của bạn.

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()
    }
}

Để thay đổi kích thước theo nhãn thay đổi động:

@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)
}

Để tạo hiệu ứng thay đổi kích thước theo những thay đổi trong một ràng buộc:

@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()
    }
}

Xem dự án AutoResizingHeader để biết điều này đang hoạt động. https://github.com/p-sun/Swift2-iOS9-UI

AutoResizingHeader


Này p-sun. Cảm ơn vì ví dụ tuyệt vời. Có vẻ như tôi gặp sự cố với tableViewHeader. Tôi đã cố gắng có các ràng buộc giống như trong ví dụ của bạn, nhưng không hoạt động. Chính xác thì tôi nên thêm các ràng buộc vào nhãn mà tôi muốn tăng chiều cao như thế nào? Cảm ơn vì bất kỳ đề xuất nào
Bonnke

1
Đảm bảo bạn gắn nhãn mà bạn muốn làm cho cao hơn @IBOutlet makeThisTaller@IBAction fun makeThisTallergiống như trong ví dụ. Ngoài ra, giới hạn tất cả các mặt của nhãn của bạn vào tableViewHeader (ví dụ: trên cùng, dưới cùng, trái và phải).
p-sun

Cảm ơn rất nhiều! Cuối cùng được giải quyết bằng cách thêm dòng này: lblFeedDescription.preferredMaxLayoutWidth = lblFeedDescription.bounds.widthnhãn ở đâu là nhãn mà tôi muốn tăng kích thước. Cảm ơn !
Bonnke

Cảm ơn! Nếu bạn đang thực hiện việc này bên trong View thay vì ViewController, thì thay vì ghi đè viewDidLayoutSubviews, bạn có thể ghi đè layoutSubviews
Andy Weinstein

4

Giải pháp này thay đổi kích thước tableHeaderView và tránh vòng lặp vô hạn trong viewDidLayoutSubviews()phương pháp mà tôi gặp phải với một số câu trả lời khác ở đây:

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
        }
    }
}

Xem thêm bài đăng này: https://stackoverflow.com/a/34689293/1245231


1
Tôi cũng đang sử dụng phương pháp này. Hãy nghĩ rằng đó là điều tốt nhất vì không phải lo lắng về những ràng buộc. Nó cũng rất nhỏ gọn.
sinh học

3

Giải pháp của bạn bằng cách sử dụng systemLayoutSizeFittingSize: hoạt động nếu dạng xem tiêu đề chỉ được cập nhật một lần trên mỗi lần xuất hiện dạng xem. Trong trường hợp của tôi, chế độ xem tiêu đề được cập nhật nhiều lần để phản ánh các thay đổi trạng thái. Nhưng systemLayoutSizeFittingSize: luôn báo cáo cùng một kích thước. Đó là, kích thước tương ứng với bản cập nhật đầu tiên.

Để có được systemLayoutSizeFittingSize: để trả về kích thước chính xác sau mỗi lần cập nhật, trước tiên, tôi phải xóa chế độ xem tiêu đề bảng trước khi cập nhật và thêm lại:

self.listTableView.tableHeaderView = nil;
[self.headerView removeFromSuperview];

2

Điều này đã làm việc cho tôi trên ios10 và 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

}

2

Nó hoạt động cho cả chế độ xem đầu trang và chân trang, chỉ cần thay thế đầu trang bằng chân trang

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
    }
}

1

Đối với iOS 12 trở lên, các bước sau sẽ đảm bảo tự động thanh toán hoạt động bình thường trong cả bảng và tiêu đề của bạn.

  1. Đầu tiên hãy tạo Chế độ xem bảng của bạn, sau đó tạo tiêu đề.
  2. Ở cuối mã tạo tiêu đề của bạn, hãy gọi:
[headerV setNeedsLayout];
[headerV layoutIfNeeded];
  1. Khi thay đổi hướng, hãy đánh dấu lại tiêu đề cho bố cục và tải lại bảng, điều này cần xảy ra một chút sau khi thay đổi hướng được báo cáo:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 *NSEC_PER_SEC), dispatch_get_main_queue(), ^{

  [tableV.tableHeaderView setNeedsLayout];
  [tableV.tableHeaderView layoutIfNeeded];
  [tableV reloadData];});

0

Trong trường hợp của tôi viewDidLayoutSubviewshoạt động tốt hơn. viewWillLayoutSubviewslàm tableViewxuất hiện các vạch trắng của a . Ngoài ra, tôi đã thêm kiểm tra xem headerViewđối tượng của tôi đã tồn tại chưa.

- (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];
    }
}

0

Hoàn toàn có thể sử dụng dựa trên Tự động thanh toán UIViewchung với bất kỳ cấu trúc chế độ xem phụ bên trong AL nào dưới dạng a tableHeaderView.

Điều duy nhất người ta cần là đặt đơn giản tableFooterViewtrước!

Hãy để self.headerViewlà một số ràng buộc dựa trên 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;
}

Nếu self.headerViewthay đổi chiều cao trong xoay giao diện người dùng, người ta phải triển khai

- (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];
}

Người ta có thể sử dụng loại objC cho mục đích này

@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

Và cả tiện ích mở rộng 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
    }
}

0

Giải pháp này hoạt động hoàn hảo đối với tôi:

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

Nó mở rộng UITableViewController. Nhưng nếu bạn chỉ đang sử dụng UITableView thì nó vẫn hoạt động, chỉ cần mở rộng UITableView thay vì UITableViewController. Gọi các phương thức sizeHeaderToFit()hoặc sizeFooterToFit()bất cứ khi nào có sự kiện thay đổi tableViewHeaderchiều cao.


0

Đã sao chép từ bài đăng này

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
        }
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.