Using autolayout in a tableHeaderView

前端 未结 8 2127
谎友^
谎友^ 2020-12-01 06:03

I have a UIView subclass that contains a multi-line UILabel. This view uses autolayout.

相关标签:
8条回答
  • 2020-12-01 06:29

    I'll add my 2 cents since this question is highly indexed in Google. I think you should be using

    self.tableView.sectionHeaderHeight = UITableViewAutomaticDimension
    self.tableView.estimatedSectionHeaderHeight = 200 //a rough estimate, doesn't need to be accurate
    

    in your ViewDidLoad. Also, to load a custom UIView to a Header you should really be using viewForHeaderInSection delegate method. You can have a custom Nib file for your header (UIView nib). That Nib must have a controller class which subclasses UITableViewHeaderFooterView like-

    class YourCustomHeader: UITableViewHeaderFooterView {
        //@IBOutlets, delegation and other methods as per your needs
    }
    

    Make sure your Nib file name is the same as the class name just so you don't get confused and it's easier to manage. like YourCustomHeader.xib and YourCustomHeader.swift (containing class YourCustomHeader). Then, just assign YourCustomHeader to your Nib file using identity inspector in the interface builder.

    Then register the Nib file as your header view in the main View Controller's viewDidLoad like-

    tableView.register(UINib(nibName: "YourCustomHeader", bundle: nil), forHeaderFooterViewReuseIdentifier: "YourCustomHeader")
    

    And then in your heightForHeaderInSection just return UITableViewAutomaticDimension. This is how the delegates should look like-

    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
         let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "YourCustomHeader") as! YourCustomHeader
         return headerView
    }
    
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
         return UITableViewAutomaticDimension
    }
    

    This is a much simpler and the appropriate way of doing without the "Hackish" ways suggested in the accepted answer since multiple forced layouts could impact your app's performance, especially if you have multiple custom headers in your tableview. Once you do the above method as I suggest, you would notice your Header (and or Footer) view expand and shrink magically based on your custom view's content size (provided you are using AutoLayout in the custom view, i.e. YourCustomHeader, nib file).

    0 讨论(0)
  • 2020-12-01 06:32

    For anyone still looking for a solution, this is for Swift 3 & iOS 9+. Here is one using only AutoLayout. It also updates correctly on device rotation.

    extension UITableView {
        // 1.
        func setTableHeaderView(headerView: UIView) {
            headerView.translatesAutoresizingMaskIntoConstraints = false
    
            self.tableHeaderView = headerView
    
            // ** Must setup AutoLayout after set tableHeaderView.
            headerView.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = true
            headerView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
            headerView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
        }
    
        // 2.
        func shouldUpdateHeaderViewFrame() -> Bool {
            guard let headerView = self.tableHeaderView else { return false }
            let oldSize = headerView.bounds.size        
            // Update the size
            headerView.layoutIfNeeded()
            let newSize = headerView.bounds.size
            return oldSize != newSize
        }
    }
    

    To use:

    override func viewDidLoad() {
        ...
    
        // 1.
        self.tableView.setTableHeaderView(headerView: customView)
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
    
        // 2. Reflect the latest size in tableHeaderView
        if self.tableView.shouldUpdateHeaderViewFrame() {
    
            // **This is where table view's content (tableHeaderView, section headers, cells) 
            // frames are updated to account for the new table header size.
            self.tableView.beginUpdates()
            self.tableView.endUpdates()
        }
    }
    

    The gist is that you should let tableView manage the frame of tableHeaderView the same way as table view cells. This is done through tableView's beginUpdates/endUpdates.

    The thing is that tableView doesn't care about AutoLayout when it updates the children frames. It uses the current tableHeaderView's size to determine where the first cell/section header should be.

    1) Add a width constraint so that the tableHeaderView uses this width whenever we call layoutIfNeeded(). Also add centerX and top constraints to position it correctly relative to the tableView.

    2) To let the tableView knows about the latest size of tableHeaderView, e.g., when the device is rotated, in viewDidLayoutSubviews we can call layoutIfNeeded() on tableHeaderView. Then, if the size is changed, call beginUpdates/endUpdates.

    Note that I don't include beginUpdates/endUpdates in one function, as we might want to defer the call to later.

    Check out a sample project

    0 讨论(0)
提交回复
热议问题