ฉันจะตั้งค่าความสูงของ tableHeaderView (UITableView) ด้วยการจัดวางอัตโนมัติได้อย่างไร


86

ฉันทุบหัวกับกำแพงเป็นเวลา 3 หรือ 4 ชั่วโมงที่ผ่านมาและดูเหมือนจะคิดไม่ออก ฉันมี UIViewController ที่มี UITableView แบบเต็มหน้าจออยู่ข้างใน (มีบางสิ่งอื่น ๆ บนหน้าจอซึ่งเป็นสาเหตุที่ฉันไม่สามารถใช้ UITableViewController) และฉันต้องการให้ tableHeaderView ของฉันปรับขนาดด้วยการจัดวางอัตโนมัติ ไม่จำเป็นต้องพูดว่ามันไม่ร่วมมือ

ดูภาพหน้าจอด้านล่าง

ป้อนคำอธิบายภาพที่นี่

เนื่องจากภาพรวมป้าย (เช่นข้อความ "แสดงข้อมูลภาพรวมที่นี่") มีเนื้อหาแบบไดนามิกฉันใช้การจัดวางอัตโนมัติเพื่อปรับขนาดและเป็น superview ฉันมีทุกอย่างที่ปรับขนาดได้ดียกเว้น tableHeaderView ซึ่งอยู่ด้านล่าง Paralax Table View ใน hiearchy

วิธีเดียวที่ฉันพบในการปรับขนาดมุมมองส่วนหัวนั้นเป็นแบบโปรแกรมโดยใช้รหัสต่อไปนี้:

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

ในกรณีนี้ headerFrameHeight คือการคำนวณความสูง tableViewHeader ด้วยตนเองดังนี้(innerHeaderView คือพื้นที่สีขาวหรือ "View" อันที่สอง headerView คือ 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);

การคำนวณด้วยตนเองใช้งานได้ แต่จะไม่สะดวกและมีแนวโน้มที่จะเกิดข้อผิดพลาดหากมีการเปลี่ยนแปลงในอนาคต สิ่งที่ฉันต้องการทำได้คือให้ tableHeaderView ปรับขนาดอัตโนมัติตามข้อ จำกัด ที่มีให้เช่นเดียวกับที่คุณสามารถทำได้ทุกที่ แต่สำหรับชีวิตของฉันฉันคิดไม่ออก

มีหลายโพสต์เกี่ยวกับ SO เกี่ยวกับเรื่องนี้ แต่ไม่มีอะไรชัดเจนและทำให้ฉันสับสนมากขึ้น นี่คือบางส่วน:

มันไม่สมเหตุสมผลเลยที่จะเปลี่ยนคุณสมบัติ translatesAutoresizingMaskIntoConstraints เป็น NO เนื่องจากนั่นทำให้เกิดข้อผิดพลาดสำหรับฉันและไม่สมเหตุสมผลในแนวความคิด

ความช่วยเหลือใด ๆ ที่จะได้รับการชื่นชมจริงๆ!

แก้ไข 1: ขอบคุณคำแนะนำของ TomSwift ทำให้ฉันเข้าใจได้ แทนที่จะคำนวณความสูงของภาพรวมด้วยตนเองฉันสามารถคำนวณให้ฉันได้ดังนี้แล้วตั้งค่า tableHeaderView ใหม่เหมือนเดิม

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

แก้ไข 2:ตามที่คนอื่น ๆ สังเกตเห็นว่าโซลูชันที่โพสต์ใน Edit 1 ดูเหมือนจะไม่ทำงานใน viewDidLoad อย่างไรก็ตามดูเหมือนว่าจะทำงานใน viewWillLayoutSubviews ตัวอย่างโค้ดด้านล่าง:

// 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
setTableHeaderViewไม่ทำงานบน Xcode6 ปัญหาคือเซลล์ที่ซ้อนทับกันโดย tableHeaderView อย่างไรก็ตามมันใช้ได้กับ Xcode5
DawnSong

@DawnSong รู้วิธีแก้ปัญหาสำหรับ Xcode6 เพื่อให้ฉันสามารถอัปเดตคำตอบได้หรือไม่?
Andrew Cross

1
หลังจากทดลองใช้มาหลายครั้งฉันใช้ UITableViewCell แทน tableHeaderView และใช้งานได้
DawnSong

ฉันทุบหัวด้วย!
Akshit Zaveri

โซลูชันการกำหนดระบบอัตโนมัติที่แท้จริงอยู่ที่นี่
malex

คำตอบ:


35

คุณต้องใช้UIView systemLayoutSizeFittingSize:วิธีนี้เพื่อให้ได้ขนาดขอบเขตขั้นต่ำของมุมมองส่วนหัวของคุณ

ฉันให้การอภิปรายเพิ่มเติมเกี่ยวกับการใช้ API นี้ในคำถาม / คำตอบนี้:

จะปรับขนาดซูเปอร์วิวให้พอดีกับมุมมองย่อยทั้งหมดที่มีการจัดวางอัตโนมัติได้อย่างไร


มันกลับมาพร้อมกับความสูง = 0 แม้ว่าฉันจะไม่แน่ใจ 100% ว่าได้กำหนดค่าถูกต้องเนื่องจากนี่ไม่ใช่ UITableViewCell ดังนั้นจึงไม่มี contentView ที่จะเรียก "systemLayoutSizeFittingSize" บน สิ่งที่ฉันพยายาม: [self.headerView setNeedsLayout]; [self.headerView layoutIfNeeded]; CGFloat height = [self.headerView systemLayoutSizeFittingSize: UILayoutFittingCompressedSize] .height; CGRect headerFrame = self.headerView.frame; headerFrame.size.height = ความสูง; self.headerView.frame = headerFrame; [self.listTableView setTableHeaderView: self.headerView]; ฉันยังยืนยันว่าที่ต้องการ Max ... ถูกต้อง
Andrew Cross

อ๊าาา! คิดออกแล้วฉันต้องทำ [self.innerHeaderView systemLayoutSizeFittingSize ... ] แทน self.headerView เพราะนั่นคือสิ่งที่มีเนื้อหาไดนามิกที่แท้จริง ขอบคุณมาก!
Andrew Cross

ใช้งานได้ แต่ในแอปของฉันมีความคลาดเคลื่อนของความสูงเล็กน้อย อาจเป็นเพราะป้ายกำกับของฉันใช้ข้อความที่เป็นแหล่งที่มาและประเภทไดนามิก
ชีวภาพ

23

ฉันต่อสู้กับสิ่งนี้จริงๆและการตั้งค่าการตั้งค่าลงใน viewDidLoad ไม่ได้ผลสำหรับฉันเนื่องจากไม่ได้ตั้งค่าเฟรมใน viewDidLoad ฉันยังลงเอยด้วยคำเตือนที่ยุ่งเหยิงมากมายซึ่งความสูงของโครงร่างอัตโนมัติที่ห่อหุ้มไว้ของส่วนหัวถูกลดลงเหลือ 0 ฉันสังเกตเห็นปัญหาบน iPad เมื่อนำเสนอ tableView ในงานนำเสนอแบบฟอร์มเท่านั้น

สิ่งที่แก้ไขปัญหาสำหรับฉันคือการตั้งค่า tableViewHeader ใน viewWillLayoutSubviews แทนที่จะอยู่ใน 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
        }
    }

สำหรับโซลูชันที่อัปเดตพร้อมรหัสขั้นต่ำลองสิ่งนี้: stackoverflow.com/a/63594053/3933302
Sourabh Sharma

23

ฉันพบวิธีที่ยอดเยี่ยมในการใช้การจัดวางอัตโนมัติเพื่อปรับขนาดส่วนหัวของตารางโดยมีและไม่มีภาพเคลื่อนไหว

เพียงเพิ่มสิ่งนี้ลงใน View Controller ของคุณ

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

ในการปรับขนาดตามป้ายกำกับที่เปลี่ยนแปลงแบบไดนามิก:

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

หากต้องการปรับขนาดให้เคลื่อนไหวตามการเปลี่ยนแปลงในข้อ จำกัด :

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

ดูโครงการ AutoResizingHeader เพื่อดูการทำงานนี้ https://github.com/p-sun/Swift2-iOS9-UI

AutoResizingHeader


เฮ้ p-sun ขอบคุณสำหรับตัวอย่างที่ดี ดูเหมือนว่าฉันมีปัญหากับ tableViewHeader ฉันพยายามมีข้อ จำกัด เหมือนในตัวอย่างของคุณ แต่ไม่ได้ผล ฉันควรเพิ่มข้อ จำกัด ในป้ายกำกับที่ต้องการเพิ่มความสูงได้อย่างไร? ขอบคุณสำหรับข้อเสนอแนะใด ๆ
Bonnke

1
ตรวจสอบให้แน่ใจว่าคุณติดป้ายที่คุณต้องการทำให้สูงขึ้น@IBOutlet makeThisTallerและ@IBAction fun makeThisTallerเหมือนในตัวอย่าง นอกจากนี้ให้ จำกัด ทุกด้านของป้ายกำกับของคุณไว้ที่ tableViewHeader (เช่นด้านบนด้านล่างซ้ายและขวา)
อาทิตย์

ขอบคุณมาก! แก้ไขในที่สุดโดยเพิ่มบรรทัดนี้: lblFeedDescription.preferredMaxLayoutWidth = lblFeedDescription.bounds.widthโดยที่ป้ายกำกับคือป้ายที่ฉันต้องการเพิ่มขนาด ขอบคุณ!
Bonnke

ขอบคุณ! หากคุณทำสิ่งนี้ภายใน View แทนที่จะเป็น ViewController แทนที่จะแทนที่ viewDidLayoutSubviews คุณสามารถแทนที่ layoutSubviews
Andy Weinstein

4

โซลูชันนี้ปรับขนาด tableHeaderView และหลีกเลี่ยงการวนซ้ำแบบไม่มีที่สิ้นสุดในviewDidLayoutSubviews()วิธีที่ฉันมีกับคำตอบอื่น ๆ ที่นี่:

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

ดูโพสต์นี้: https://stackoverflow.com/a/34689293/1245231


1
ฉันกำลังใช้วิธีนี้เหมือนกัน คิดว่าดีที่สุดเพราะไม่มีข้อ จำกัด ในการเล่นซอ นอกจากนี้ยังมีขนาดกะทัดรัดมาก
ชีวภาพ

3

โซลูชันของคุณโดยใช้ systemLayoutSizeFittingSize: ใช้งานได้หากมุมมองส่วนหัวได้รับการอัปเดตเพียงครั้งเดียวในแต่ละมุมมอง ในกรณีของฉันมุมมองส่วนหัวอัปเดตหลายครั้งเพื่อแสดงการเปลี่ยนแปลงสถานะ แต่ systemLayoutSizeFittingSize: รายงานขนาดเดียวกันเสมอ นั่นคือขนาดที่สอดคล้องกับการอัปเดตครั้งแรก

ในการรับ systemLayoutSizeFittingSize: เพื่อส่งคืนขนาดที่ถูกต้องหลังจากการอัปเดตแต่ละครั้งฉันต้องลบมุมมองส่วนหัวของตารางก่อนที่จะอัปเดตและเพิ่มใหม่:

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

2

สิ่งนี้ใช้ได้ผลกับฉันบน ios10 และ 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

ใช้ได้กับทั้งมุมมองส่วนหัวและส่วนท้ายเพียงแค่แทนที่ส่วนหัวด้วยส่วนท้าย

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

สำหรับ iOS 12 ขึ้นไปขั้นตอนต่อไปนี้จะช่วยให้แน่ใจว่าการจัดวางอัตโนมัติทำงานได้อย่างถูกต้องทั้งในตารางและส่วนหัวของคุณ

  1. สร้าง tableView ของคุณก่อนตามด้วยส่วนหัว
  2. ในตอนท้ายของรหัสการสร้างส่วนหัวของคุณโทร:
[headerV setNeedsLayout];
[headerV layoutIfNeeded];
  1. เมื่อมีการเปลี่ยนแปลงการวางแนวให้ทำเครื่องหมายที่ส่วนหัวของเค้าโครงอีกครั้งและโหลดตารางใหม่สิ่งนี้จะต้องเกิดขึ้นเล็กน้อยหลังจากรายงานการเปลี่ยนแปลงการวางแนว:
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

ในกรณีของฉันviewDidLayoutSubviewsทำงานได้ดีขึ้น viewWillLayoutSubviewsทำให้เส้นสีขาวของ a tableViewปรากฏขึ้น นอกจากนี้ฉันยังเพิ่มการตรวจสอบว่ามีheaderViewวัตถุอยู่แล้วหรือไม่

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

ค่อนข้างเป็นไปได้ที่จะใช้ AutoLayout ทั่วไปUIViewกับโครงสร้างมุมมองย่อยภายในของ AL เป็นไฟล์tableHeaderView .

สิ่งเดียวที่ต้องมีคือการตั้งค่าที่เรียบง่าย tableFooterViewก่อน!

Let บางข้อ จำกัดself.headerView ตาม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;
}

หากมีself.headerViewการเปลี่ยนแปลงความสูงภายใต้การหมุน UI ต้องใช้

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

หนึ่งสามารถใช้ประเภทObjCเพื่อจุดประสงค์นี้

@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

และส่วนขยาย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

วิธีนี้ใช้ได้ผลดีกับฉัน:

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

มันขยาย UITableViewController แต่ถ้าคุณแค่ใช้ UITableView มันจะยังใช้งานได้เพียงแค่ขยาย UITableView แทน UITableViewController เรียกใช้วิธีการsizeHeaderToFit()หรือsizeFooterToFit()เมื่อใดก็ตามที่มีเหตุการณ์ที่เปลี่ยนแปลงtableViewHeaderความสูง


0

คัดลอกมาจากโพสต์นี้

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
        }
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.