ios 是否可以将 AutoLayout 与 UITableView 的 tableHeaderView 一起使用?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/16471846/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Is it possible to use AutoLayout with UITableView's tableHeaderView?
提问by ItsASecret
Since I discovered AutoLayout
I use it everywhere, now I'm trying to use it with a tableHeaderView
.
因为我发现AutoLayout
我到处都在使用它,现在我尝试将它与tableHeaderView
.
I made a subclass
of UIView
added everything (labels etc...) I wanted with their constraints, then I added this CustomView
to the UITableView
'tableHeaderView
.
我做了一个subclass
的UIView
加入一切(标签等...),我想用自己的约束,那么我将此CustomView
到UITableView
“ tableHeaderView
。
Everything works just fine except the UITableView
always displays abovethe CustomView
, by aboveI mean the CustomView
is underthe UITableView
so it can't be seen !
一切都工作得很好,除了UITableView
始终显示上面的CustomView
,通过以上我指的CustomView
是下的UITableView
,因此它不能被看到!
It seems that no matter what I do, the height
of the UITableView
'tableHeaderView
is always0 (so is the width, x and y).
看来,无论我做什么,对height
的UITableView
“tableHeaderView
是始终0(所以是宽度,x和y)。
My question : is it possible at all to accomplish this without setting the frame manually?
我的问题:是否有可能在不手动设置框架的情况下完成此操作?
EDIT :The CustomView
'subview
that I'm using has these constraints :
编辑:我正在使用
的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]]]];
I'm using a handy library 'KeepLayout' because writing constraints manually takes forever and way too many line for one single constraint but the methods are self-explaining.
我正在使用一个方便的库“KeepLayout”,因为手动编写约束需要永远并且单个约束的行太多,但这些方法是不言自明的。
And the UITableView
has these constraints :
并且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;
I don't know if I have to set some constraints directly on the CustomView
, I think the height of the CustomView is determined by the constraints on the UILabel
"title" in it.
不知道是不是要直接在 上设置一些约束CustomView
,我觉得CustomView的高度是由里面UILabel
“title”的约束决定的。
EDIT 2:After another investigation it seems the height and width of the CustomView are correctly calculated, but the top of the CustomView is still at the same level than the top of the UITableView and they move together when I scroll.
编辑 2:经过另一次调查,似乎 CustomView 的高度和宽度计算正确,但 CustomView 的顶部仍与 UITableView 的顶部处于同一水平,并且当我滚动时它们一起移动。
回答by Ben Packard
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:
如果您有多行标签,这也依赖于自定义视图设置每个标签的 preferredMaxLayoutWidth:
- (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
2015 年 1 月更新
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:
我发现将其移动到 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)
回答by rdelmar
I've been unable to add a header view using constraints (in code). If I give my view a width and/or a height constraint, I get a crash with the message saying:
我一直无法使用约束(在代码中)添加标题视图。如果我给我的视图一个宽度和/或高度约束,我会崩溃并显示以下消息:
"terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Auto Layout still required after executing -layoutSubviews. UITableView's implementation of -layoutSubviews needs to call super."
When I add a view in the storyboard to my table view, it shows no constraints, and it works fine as a header view, so I think that the placement of the header view isn't done using constraints. It doesn't seem to behave like a normal view in that regard.
当我将故事板中的视图添加到我的表视图时,它没有显示任何约束,并且它作为标题视图工作正常,所以我认为标题视图的放置不是使用约束完成的。在这方面,它似乎不像正常视图。
The width is automatically the width of the table view, the only thing you need to set is the height -- the origin values are ignored, so it doesn't matter what you put in for those. For instance, this worked fine (as does 0,0,0,80 for the rect):
宽度自动是表格视图的宽度,你唯一需要设置的就是高度——原始值被忽略,所以你为这些值输入什么并不重要。例如,这很好用(就像 rect 的 0,0,0,80 一样):
UIView *headerview = [[UIView alloc] initWithFrame:CGRectMake(1000,1000, 0, 80)];
headerview.backgroundColor = [UIColor yellowColor];
self.tableView.tableHeaderView = headerview;
回答by Ramon Vasconcelos
I saw a lot of methods here doing so much unnecessary stuff, but you don't need that much to use auto layout in the header view. You just have to create you xib file, put your constraints and instantiate it like this:
我在这里看到很多方法做了很多不必要的事情,但是在标题视图中使用自动布局不需要那么多。你只需要创建你的 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
}
回答by Martin
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
您可以通过实现此函数并在viewDidLayoutSubviews
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[self sizeHeaderToFit];
}
回答by Phil
Code:
代码:
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
}
}
回答by HotJard
Strange things happens. systemLayoutSizeFittingSize works great for iOS9, but doesn't for iOS 8 in my case. So this problems solves quite easy. Just get link to the bottom view in header and in viewDidLayoutSubviews after super call update header view bounds by inserting height as CGRectGetMaxY(yourview.frame) + padding
奇怪的事情发生了。systemLayoutSizeFittingSize 适用于 iOS9,但不适用于 iOS 8。所以这个问题很容易解决。通过插入高度为 CGRectGetMaxY(yourview.frame) + padding,在超级调用更新标题视图边界后,只需在标题和 viewDidLayoutSubviews 中获取底部视图的链接
UPD: The easiest solution ever: So, in header view place subview and pin it to left, right, top. In that subview place your subviews with auto-height constraints. After that give all the job to the autolayout (no calculation required)
UPD:有史以来最简单的解决方案:因此,在标题视图中放置子视图并将其固定到left、right、top。在该子视图中放置具有自动高度约束的子视图。之后将所有工作交给自动布局(无需计算)
- (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;
}
As a result subview is expanding/shrinking like it should, at the end it calls viewDidLayoutSubviews. At the time we know the actual size of the view, so set headerView height and update it by re-assigning. Works like a charm!
结果子视图像它应该的那样扩展/收缩,最后它调用 viewDidLayoutSubviews。当时我们就知道view的实际大小了,所以设置headerView的高度,通过重新赋值来更新。奇迹般有效!
Also works for footer view.
也适用于页脚视图。
回答by Jonathan
You can get autolayout to provide you with a size by using the systemLayoutSizeFittingSizemethod.
您可以使用systemLayoutSizeFittingSize方法让自动布局为您提供尺寸。
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
swift中的代码看起来像
//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
或者在 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.
还应该注意的是,在这个特定实例中,覆盖子类中的 requiresConstraintBasedLayout 确实会导致执行布局传递,但是此布局传递的结果被忽略,并且系统框架设置为 tableView 的宽度和 0 高度。
回答by k06a
Extended this solution http://collindonnell.com/2015/09/29/dynamically-sized-table-view-header-or-footer-using-auto-layout/for table footer view:
扩展此解决方案http://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 footer 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
回答by Caleb Friden
Updated for Swift 4.2
为 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
}
}
}
回答by GreatWiz
you can add top + horizontal location constraint between header and tableview, to place it, correctly (if the header itself contains all the necessary internal layout constraints to have a correct frame)
您可以在标题和 tableview 之间添加顶部 + 水平位置约束,以正确放置它(如果标题本身包含所有必要的内部布局约束以获得正确的框架)
in the tableViewController viewDidLoad method
在 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