Is it possible to use AutoLayout with UITableView's tableHeaderView?

前端 未结 29 1174
醉梦人生
醉梦人生 2020-11-28 19:51

Since I discovered AutoLayout I use it everywhere, now I\'m trying to use it with a tableHeaderView.

I made a subclass of

相关标签:
29条回答
  • 2020-11-28 20:28

    I have figured out a workaround. wrap your autolayout wrriten xib header view in an empty uiview wrapper, and assign the header view to tableView's tableViewHeader property.

        UIView *headerWrapper = [[UIView alloc] init];
        AXLHomeDriverHeaderView *headerView = [AXLHomeDriverHeaderView loadViewFromNib];
        [headerWrapper addSubview:headerView];
        [headerView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.edges.equalTo(headerWrapper);
        }];
        self.tableView.tableHeaderView = headerView;
    
    0 讨论(0)
  • 2020-11-28 20:29

    I asked and answered a similar question here. In summary, I add the header once and use it to find the required height. That height can then be applied to the header, and the header is set a second time to reflect the change.

    - (void)viewDidLoad
    {
        [super viewDidLoad];
    
        self.header = [[SCAMessageView alloc] init];
        self.header.titleLabel.text = @"Warning";
        self.header.subtitleLabel.text = @"This is a message with enough text to span multiple lines. This text is set at runtime and might be short or long.";
    
        //set the tableHeaderView so that the required height can be determined
        self.tableView.tableHeaderView = self.header;
        [self.header setNeedsLayout];
        [self.header layoutIfNeeded];
        CGFloat height = [self.header systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
    
        //update the header's frame and set it again
        CGRect headerFrame = self.header.frame;
        headerFrame.size.height = height;
        self.header.frame = headerFrame;
        self.tableView.tableHeaderView = self.header;
    }
    

    If you have multi-line labels, this also relies on the custom view setting the preferredMaxLayoutWidth of each label:

    - (void)layoutSubviews
    {
        [super layoutSubviews];
    
        self.titleLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.titleLabel.frame);
        self.subtitleLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.subtitleLabel.frame);
    }
    

    or perhaps more generally:

    override func layoutSubviews() {
        super.layoutSubviews()  
        for view in subviews {
            guard let label = view as? UILabel where label.numberOfLines == 0 else { continue }
            label.preferredMaxLayoutWidth = CGRectGetWidth(label.frame)
        }
    }
    

    Update January 2015

    Unfortunately this still seems necessary. Here is a swift version of the layout process:

    tableView.tableHeaderView = header
    header.setNeedsLayout()
    header.layoutIfNeeded()
    header.frame.size = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
    tableView.tableHeaderView = header
    

    I've found it useful to move this into an extension on UITableView:

    extension UITableView {
        //set the tableHeaderView so that the required height can be determined, update the header's frame and set it again
        func setAndLayoutTableHeaderView(header: UIView) {
            self.tableHeaderView = header
            header.setNeedsLayout()
            header.layoutIfNeeded()
            header.frame.size = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
            self.tableHeaderView = header
        }
    }
    

    Usage:

    let header = SCAMessageView()
    header.titleLabel.text = "Warning"
    header.subtitleLabel.text = "Warning message here."
    tableView.setAndLayoutTableHeaderView(header)
    
    0 讨论(0)
  • 2020-11-28 20:29

    Updated for Swift 4.2

    extension UITableView {
    
        var autolayoutTableViewHeader: UIView? {
            set {
                self.tableHeaderView = newValue
                guard let header = newValue else { return }
                header.setNeedsLayout()
                header.layoutIfNeeded()
                header.frame.size = 
                header.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
                self.tableHeaderView = header
            }
            get {
                return self.tableHeaderView
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-28 20:30

    You can get autolayout to provide you with a size by using the systemLayoutSizeFittingSize method.

    You can then use this to create the frame for your application. This technique works whenever you need to know the size of a view that uses autolayout internally.

    The code in swift looks like

    //Create the view
    let tableHeaderView = CustomTableHeaderView()
    
    //Set the content
    tableHeaderView.textLabel.text = @"Hello world"
    
    //Ask auto layout for the smallest size that fits my constraints    
    let size = tableHeaderView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
    
    //Create a frame    
    tableHeaderView.frame = CGRect(origin: CGPoint.zeroPoint, size: size)
    
    //Set the view as the header    
    self.tableView.tableHeaderView = self.tableHeaderView
    

    Or in Objective-C

    //Create the view
    CustomTableHeaderView *header = [[CustomTableHeaderView alloc] initWithFrame:CGRectZero];
    
    //Set the content
    header.textLabel.text = @"Hello world";
    
    //Ask auto layout for the smallest size that fits my constraints
    CGSize size = [header systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
    
    //Create a frame
    header.frame = CGRectMake(0,0,size.width,size.height);
    
    //Set the view as the header  
    self.tableView.tableHeaderView = header
    

    It should also be noted that in this particular instance, overriding requiresConstraintBasedLayout in your subclass, does result in a layout pass being performed, however the results of this layout pass are ignored and the system frame set to the width of the tableView and 0 height.

    0 讨论(0)
  • 2020-11-28 20:31

    Another solution is to dispatch the header view creation to the next main thread call:

    - (void)viewDidLoad {
        [super viewDidLoad];
    
        // ....
    
        dispatch_async(dispatch_get_main_queue(), ^{
            _profileView = [[MyView alloc] initWithNib:@"MyView.xib"];
            self.tableView.tableHeaderView = self.profileView;
        });
    }
    

    Note: It fix the bug when the loaded view has a fixed height. I haven't tried when the header height only depends on its content.

    EDIT :

    You can find a cleaner solution to this problem by implementing this function, and calling it in viewDidLayoutSubviews

    - (void)viewDidLayoutSubviews {
        [super viewDidLayoutSubviews];
    
        [self sizeHeaderToFit];
    }
    
    0 讨论(0)
  • 2020-11-28 20:31

    Here is how you can do in your UIViewController

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
    
        if headerView.frame.size.height == 0 {
          headerView.label.preferredMaxLayoutWidth = view.bounds.size.width - 20
          let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
    
          headerView.frame.size = CGSize(width: tableView.bounds.size.width, height: height)
        }
      }
    
    0 讨论(0)
提交回复
热议问题