是否有可能与UITableView的tableHeaderView使用AutoLayout?
由于我发现AutoLayout
我到处使用它,现在我试图用tableHeaderView
来使用它。
我做了UIView
一个subclass
添加了一切(标签等),我想与他们的约束,然后我把这个CustomView
到UITableView
的tableHeaderView
。
一切工作得很好,除了UITableView
总是显示在CustomView
, 上面的意思是CustomView
在 UITableView
之下 ,所以不能被看到!
看来,无论我做什么, UITableView
的tableHeaderView
的height
总是 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高度的宽度。
以下为我工作。
- 使用普通的旧
UIView
作为标题视图。 - 将子视图添加到该
UIView
- 在子视图上使用自动布局
我看到的主要好处是限制帧计算。 苹果应该真的更新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
首先,我的视图层次结构如下所示:
- 表视图
- 查看
tableHeaderView
- 查看一个名为headerView的出口
- 查看
现在在视图(No.3)中,我设置了所有的约束,正如我通常将底部空间包括在容器中一样。 这将使容器(即3.View,即标题视图)根据其子视图和它们的约束来调整自己的大小。
之后,我在3. View
和2. View
之间设置约束2. View
这些:
- 顶部空间到容器:0
- 领先的空间容器:0
- 尾随空间到容器: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 + horizontal的位置约束来正确地放置它(如果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
的子视图实现的注意事项:
-
您必须100%确定您的标题视图具有正确的自动布局设置。 我build议从简单的页眉视图开始,只有一个高度限制,并尝试上面的设置。
-
覆盖
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到UITableViewHeaderFooterView
的contentView
中即可。 完全像UITableViewCell
作品。