是否有可能与UITableView的tableHeaderView使用AutoLayout?

由于我发现AutoLayout我到处使用它,现在我试图用tableHeaderView来使用它。

我做了UIView一个subclass添加了一切(标签等),我想与他们的约束,然后我把这个CustomViewUITableViewtableHeaderView

一切工作得很好,除了UITableView总是显示在CustomView上面的意思是CustomView UITableView 之下 ,所以不能被看到!

看来,无论我做什么, UITableViewtableHeaderViewheight 总是 0(宽度,x和y也是这样)。

我的问题:是否有可能完成这个手动设置框架

编辑:我正在使用的CustomView subview有这些约束:

 _title = [[UILabel alloc]init]; _title.text = @"Title"; [self addSubview:_title]; [_title keep:[KeepTopInset rules:@[[KeepEqual must:5]]]]; // title has to stay at least 5 away from the supperview Top [_title keep:[KeepRightInset rules:@[[KeepMin must:5]]]]; [_title keep:[KeepLeftInset rules:@[[KeepMin must:5]]]]; [_title keep:[KeepBottomInset rules:@[[KeepMin must:5]]]]; 

我正在使用一个方便的库“KeepLayout”,因为手动编写约束需要一个单一的约束永远和太多的路线,但方法是自我解释。

UITableView有这些约束:

 _tableView = [[UITableView alloc]init]; _tableView.translatesAutoresizingMaskIntoConstraints = NO; _tableView.delegate = self; _tableView.dataSource = self; _tableView.backgroundColor = [UIColor clearColor]; [self.view addSubview:_tableView]; [_tableView keep:[KeepTopInset rules:@[[KeepEqual must:0]]]];// These 4 constraints make the UITableView stays 0 away from the superview top left right and bottom. [_tableView keep:[KeepLeftInset rules:@[[KeepEqual must:0]]]]; [_tableView keep:[KeepRightInset rules:@[[KeepEqual must:0]]]]; [_tableView keep:[KeepBottomInset rules:@[[KeepEqual must:0]]]]; _detailsView = [[CustomView alloc]init]; _tableView.tableHeaderView = _detailsView; 

我不知道是否必须直接在CustomView上设置一些约束,我认为CustomView的高度是由UILabel “标题”的约束决定的。

编辑2:经过另一次调查后,似乎正确计算CustomView的高度和宽度,但CustomView的顶部仍然在UITableView的顶部相同的水平,他们一起移动时,我滚动。

我在这里问及回答了一个类似的问题。 总之,我添加标题一次,并使用它来find所需的高度。 然后可以将该高度应用于标题,并且将标题第二次设置以反映该变化。

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

如果您有多行标签,这也依赖于自定义视图设置每个标签的preferredMaxLayoutWidth:

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

或者更一般地说:

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

2015年1月更新

不幸的是,这仍然是必要的 这是一个布局过程的快速版本:

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

我发现将它移动到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 } } 

用法:

 let header = SCAMessageView() header.titleLabel.text = "Warning" header.subtitleLabel.text = "Warning message here." tableView.setAndLayoutTableHeaderView(header) 

我一直无法添加使用约束(在代码中)的标题视图。 如果我给我的观点一个宽度和/或高度的限制,我得到一个崩溃的消息说:

  "terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Auto Layout still required after executing -layoutSubviews. UITableView's implementation of -layoutSubviews needs to call super." 

当我在故事板中添加一个视图到我的表格视图时,它没有显示任何约束,并且它作为一个标题视图工作正常,所以我认为标题视图的位置不是使用约束来完成的。 在这方面似乎并不像一个正常的观点。

宽度自动是表格视图的宽度,你需要设置的唯一的事情就是高度 – 原点值被忽略,所以你放入的东西并不重要。 例如,这工作得很好(正如0,0,0,80为矩形):

 UIView *headerview = [[UIView alloc] initWithFrame:CGRectMake(1000,1000, 0, 80)]; headerview.backgroundColor = [UIColor yellowColor]; self.tableView.tableHeaderView = headerview; 

另一个解决scheme是将头部视图创build分派给下一个主线程调用:

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

注意:当加载的视图具有固定的高度时,它修复了bug。 我还没有尝试,只有头的高度取决于其内容。

编辑:

你可以通过实现这个函数 ,并在viewDidLayoutSubviews调用它,find一个更干净的解决scheme

 - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; [self sizeHeaderToFit]; } 

我在这里看到很多方法做这么多不必要的东西,但是你不需要那么多在标题视图中使用自动布局。 你只需要创build你的xib文件,把你的约束,并像这样实例化:

 func loadHeaderView () { guard let headerView = Bundle.main.loadNibNamed("CourseSearchHeader", owner: self, options: nil)?[0] as? UIView else { return } headerView.autoresizingMask = .flexibleWidth headerView.translatesAutoresizingMaskIntoConstraints = true tableView.tableHeaderView = headerView } 

奇怪的事情发生。 systemLayoutSizeFittingSize适用于iOS9,但在我的情况下不适用于iOS 8。 所以这个问题解决起来很容易。 只需要通过插入高度为CGRectGetMaxY(yourview.frame)+ padding在超级调用更新标题视图边界后,在header和viewDidLayoutSubviews中获取链接

UPD:有史以来最简单的解决scheme :所以,在标题视图放置子视图,并将其钉在左侧右侧顶部 。 在该子视图中,将自动高度约束放在子视图中。 之后,将所有工作交给自动布局(不需要计算)

 - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; CGFloat height = CGRectGetMaxY(self.tableView.tableHeaderView.subviews.firstObject.frame); self.tableView.tableHeaderView.bounds = CGRectMake(0, 0, CGRectGetWidth(self.tableView.bounds), height); self.tableView.tableHeaderView = self.tableView.tableHeaderView; } 

因此子视图正在扩大/缩小,最后调用viewDidLayoutSubviews。 当时我们知道视图的实际大小,所以设置headerView的高度,并通过重新分配来更新它。 奇迹般有效!

也适用于页脚视图。

您可以通过使用systemLayoutSizeFittingSize方法来获取自动布局以提供大小。

然后,您可以使用它为您的应用程序创build框架。 只要您需要知道在内部使用自动布局的视图的大小,此技术就可以工作。

快速的代码看起来像

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

或者在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 

还应该注意的是,在这个特定的实例中,覆盖需要在你的子类中的ConstraintBasedLayout,确实导致布局传递被执行,但是这个布局传递的结果被忽略,并且系统框架被设置为tableView和0高度的宽度。

以下为我工作。

  1. 使用普通的旧UIView作为标题视图。
  2. 将子视图添加到该UIView
  3. 在子视图上使用自动布局

我看到的主要好处是限制帧计算。 苹果应该真的更新UITableView的API来使这更容易。

使用SnapKit的示例:

 let layoutView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.bounds.width, height: 60)) layoutView.backgroundColor = tableView.backgroundColor tableView.tableHeaderView = layoutView let label = UILabel() layoutView.addSubview(label) label.text = "I'm the view you really care about" label.snp_makeConstraints { make in make.edges.equalTo(EdgeInsets(top: 10, left: 15, bottom: -5, right: -15)) } 

扩展此解决schemehttp://collindonnell.com/2015/09/29/dynamically-sized-table-view-header-or-footer-using-auto-layout/用于表格页脚视图:

 @interface AutolayoutTableView : UITableView @end @implementation AutolayoutTableView - (void)layoutSubviews { [super layoutSubviews]; // Dynamic sizing for the header view if (self.tableHeaderView) { CGFloat height = [self.tableHeaderView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height; CGRect headerFrame = self.tableHeaderView.frame; // If we don't have this check, viewDidLayoutSubviews() will get // repeatedly, causing the app to hang. if (height != headerFrame.size.height) { headerFrame.size.height = height; self.tableHeaderView.frame = headerFrame; self.tableHeaderView = self.tableHeaderView; } [self.tableHeaderView layoutIfNeeded]; } // Dynamic sizing for the header view if (self.tableFooterView) { CGFloat height = [self.tableFooterView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height; CGRect footerFrame = self.tableFooterView.frame; // If we don't have this check, viewDidLayoutSubviews() will get // repeatedly, causing the app to hang. if (height != footerFrame.size.height) { footerFrame.size.height = height; self.tableFooterView.frame = footerFrame; self.tableFooterView = self.tableFooterView; } self.tableFooterView.transform = CGAffineTransformMakeTranslation(0, self.contentSize.height - footerFrame.size.height); [self.tableFooterView layoutIfNeeded]; } } @end 

我的表头视图是一个UIView子类 – 我在初始化程序中创build了一个contentView UIView,其边界与表头视图的框架相同,并将所有对象添加为子视图。

然后在表头视图的layoutSubviews方法中添加对象的约束,而不是在初始化程序中。 这解决了崩溃。

 - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:CGRectMake(0, 0, 0, 44.0)]; if (self) { UIView *contentView = [[UIView alloc] initWithFrame:self.bounds]; contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth; // add other objects as subviews of content view } return self; } - (void)layoutSubviews { [super layoutSubviews]; // remake constraints here } 

码:

  extension UITableView { func sizeHeaderToFit(preferredWidth: CGFloat) { guard let headerView = self.tableHeaderView else { return } headerView.translatesAutoresizingMaskIntoConstraints = false let layout = NSLayoutConstraint( item: headerView, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: preferredWidth) headerView.addConstraint(layout) let height = headerView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height headerView.frame = CGRectMake(0, 0, preferredWidth, height) headerView.removeConstraint(layout) headerView.translatesAutoresizingMaskIntoConstraints = true self.tableHeaderView = headerView } } 

我知道这是一个旧的职位,但经过所有关于这个SO的post,并通过一整个下午玩这个,我终于想出了一个干净,但非常简单的解决scheme

首先,我的视图层次结构如下所示:

  1. 表视图
    1. 查看tableHeaderView
      1. 查看一个名为headerView的出口

现在在视图(No.3)中,我设置了所有的约束,正如我通常将底部空间包括在容器中一样。 这将使容器(即3.View,即标题视图)根据其子视图和它们的约束来调整自己的大小。

之后,我在3. View2. View之间设置约束2. View这些:

  1. 顶部空间到容器:0
  2. 领先的空间容器:0
  3. 尾随空间到容器:0

请注意,我故意省略了底部空间。

一旦所有这些都在故事板中完成,剩下要做的就是粘贴这三行代码:

 if (self.headerView.frame.size.height != self.tableView.tableHeaderView.frame.size.height) { UIView *header = self.tableView.tableHeaderView; CGRect frame = self.tableView.tableHeaderView.frame; frame.size.height = self.headerView.frame.size.height + frame.origin.y; header.frame = frame; self.tableView.tableHeaderView = header; } 

提示:如果使用方法setAndLayoutTableHeaderView,则应该更新子视图的框架,所以在这种情况下,UILabel的preferredMaxLayoutWidth应该在调用systemLayoutSizeFittingSize之前调用,不要在layoutSubview中调用。

代码展示

分享我的方法。

UITableView+XXXAdditions.m

 - (void)xxx_setTableHeaderView:(UIView *)tableHeaderView layoutBlock:(void(^)(__kindof UIView *tableHeaderView, CGFloat *containerViewHeight))layoutBlock { CGFloat containerViewHeight = 0; UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectZero]; [backgroundView addSubview:tableHeaderView]; layoutBlock(tableHeaderView, &containerViewHeight); backgroundView.frame = CGRectMake(0, 0, 0, containerViewHeight); self.tableHeaderView = backgroundView; } 

用法。

 [self.tableView xxx_setTableHeaderView:myView layoutBlock:^(__kindof UIView * _Nonnull tableHeaderView, CGFloat *containerViewHeight) { *containerViewHeight = 170; [tableHeaderView mas_makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(@20); make.centerX.equalTo(@0); make.size.mas_equalTo(CGSizeMake(130, 130)); }]; }]; 

在我的情况下用systemLayoutSizeFittingSize由于某种原因的方法不起作用。 对我而言,HotJard发布的解决scheme的修改(他原来的解决scheme也不适用于我的情况在iOS 8)。 我需要做的是在标题视图中放置一个子视图,并将其固定到左侧,右侧,顶部(不要钉到底部)。 把所有使用autolayout的子视图和代码做到这一点:

 - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; [self resizeHeaderToFitSubview]; } - (void)resizeHeaderToFitSubview { UIView *header = self.tableView.tableHeaderView; [header setNeedsLayout]; [header layoutIfNeeded]; CGFloat height = CGRectGetHeight(header.subviews.firstObject.bounds); header.bounds = CGRectMake(0, 0, CGRectGetWidth(self.tableView.bounds), height); self.tableView.tableHeaderView = nil; self.tableView.tableHeaderView = header; } 

一个旧的职位。 但是一个好post。 这是我的2美分。

首先,确保你的头部视图有自己的约束,以便它可以支持它自己的内容大小。 然后执行以下操作。

 //ViewDidLoad headerView.translatesAutoresizingMaskIntoConstraints = false headerView.configure(title: "Some Text A") //Somewhere else headerView.update(title: "Some Text B) private var widthConstrained = false override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() if widthConstrained == false { widthConstrained = true tableView.addConstraint(NSLayoutConstraint(item: headerView, attribute: .width, relatedBy: .equal, toItem: tableView, attribute: .width, multiplier: 1, constant: 0)) headerView.layoutIfNeeded() tableView.layoutIfNeeded() } } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) coordinator.animate(alongsideTransition: { (context) in self.headerView.layoutIfNeeded() self.tableView.layoutIfNeeded() }, completion: nil) } 

你可以在header和tableview之间添加top + horizo​​ntal的位置约束来正确地放置它(如果header本身包含所有必要的内部布局约束来获得正确的帧)

在tableViewController的viewDidLoad方法中

  headerView.translatesAutoresizingMaskIntoConstraints = false tableView.tableHeaderView = headerView headerView.widthAnchor.constraint(equalTo: tableView.widthAnchor).isActive = true headerView.topAnchor.constraint(equalTo: tableView.topAnchor).isActive = true headerView.centerXAnchor.constraint(equalTo: tableView.centerXAnchor).isActive = true 

我能够通过以下方法实现它(这对于页脚以相同的方式工作)。

首先,你将需要小的UITableView扩展:

Swift 3

 extension UITableView { fileprivate func adjustHeaderHeight() { if let header = self.tableHeaderView { adjustFrame(header) } } private func adjustFrame(_ view: UIView) { view.frame.size.height = calculatedViewHeight(view) } fileprivate func calculatedHeightForHeader() -> CGFloat { if let header = self.tableHeaderView { return calculatedViewHeight(header) } return 0.0 } private func calculatedViewHeight(_ view: UIView) -> CGFloat { view.setNeedsLayout() let height = view.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height return height } } 

在你的视图控制器类实现中:

 // this is a UIView subclass with autolayout private var headerView = MyHeaderView() override func loadView() { super.loadView() // ... self.tableView.tableHeaderView = headerView self.tableView.sectionHeaderHeight = UITableViewAutomaticDimension // ... } override func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() // this is to prevent recursive layout calls let requiredHeaderHeight = self.tableView.calculatedHeightForHeader() if self.headerView.frame.height != requiredHeaderHeight { self.tableView.adjustHeaderHeight() } } 

有关头部UIView的子视图实现的注意事项:

  1. 您必须100%确定您的标题视图具有正确的自动布局设置。 我build议从简单的页眉视图开始,只有一个高度限制,并尝试上面的设置。

  2. 覆盖requiresConstraintBasedLayout并返回true

 class MyHeaderView: UIView { // ... override static var requiresConstraintBasedLayout : Bool { return true } // ... } 

我的AutoLayout工作非常好:

 CGSize headerSize = [headerView systemLayoutSizeFittingSize:CGSizeMake(CGRectGetWidth([UIScreen mainScreen].bounds), 0) withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel]; headerView.frame = CGRectMake(0, 0, headerSize.width, headerSize.height); self.tableView.tableHeaderView = headerView; 

接受的答案仅适用于具有单节的表格。 对于多节UITableView只要确保您的标头inheritance自UITableViewHeaderFooterView ,你会没事的。

作为替代,只需将当前的头部embedded到UITableViewHeaderFooterViewcontentView中即可。 完全像UITableViewCell作品。