调整UITableView的大小以适应内容

我正在创建一个应用程序,它将在UILabel中有一个问题,在UITableView中显示一个多项选择的答案,每一行显示一个多项选择。问题和答案会有所不同,所以我需要这个UITableView在高度上是动态的。

我想找到一个sizeToFit工作周围的表。其中,表的框架被设置为所有内容的高度。

有人能给我一些建议吗?

162806 次浏览

事实上,我自己找到了答案。

我只是用table.contentSize.heightheighttableView.frame创建了一个新的CGRect

UITableView的高度设置为其内容的height。 因为代码修改了UI,所以不要忘记在主线程中运行它:

dispatch_async(dispatch_get_main_queue(), ^{
//This code will run in the main thread:
CGRect frame = self.tableView.frame;
frame.size.height = self.tableView.contentSize.height;
self.tableView.frame = frame;
});

我在iOS 7中尝试过这个功能,对我来说很有效

- (void)viewDidLoad
{
[super viewDidLoad];
[self.tableView sizeToFit];
}

在表视图上为contentSize属性添加一个观察者,并相应地调整帧大小

[your_tableview addObserver:self forKeyPath:@"contentSize" options:0 context:NULL];

然后在回调中:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
CGRect frame = your_tableview.frame;
frame.size = your_tableview.contentSize;
your_tableview.frame = frame;
}

希望这对你有所帮助。

米姆的回答Anooj VM的回答都很棒,但如果你有一个很大的列表,有一个小问题,可能帧的高度会截断你的一些单元格。

所以。我稍微修改了一下答案:

dispatch_async(dispatch_get_main_queue()) {
//This code will run in the main thread:
CGFloat newHeight=self.tableView.contentSize.height;
CGFloat screenHeightPermissible=(self.view.bounds.size.height-self.tableView.frame.origin.y);
if (newHeight>screenHeightPermissible)
{
//so that table view remains scrollable when 'newHeight'  exceeds the screen bounds
newHeight=screenHeightPermissible;
}


CGRect frame = self.tableView.frame;
frame.size.height = newHeight;
self.tableView.frame = frame;
}

作为Anooj VM回答的扩展,我建议对仅在内容大小发生变化时刷新。执行以下操作

此方法还可以正确地禁用滚动支持更大的列表旋转。不需要dispatch_async,因为contentSize的更改是在主线程上分派的。

- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:NULL];
}




- (void)resizeTableAccordingToContentSize:(CGSize)newContentSize {
CGRect superviewTableFrame  = self.tableView.superview.bounds;
CGRect tableFrame = self.tableView.frame;
BOOL shouldScroll = newContentSize.height > superviewTableFrame.size.height;
tableFrame.size = shouldScroll ? superviewTableFrame.size : newContentSize;
[UIView animateWithDuration:0.3
delay:0
options:UIViewAnimationOptionCurveLinear
animations:^{
self.tableView.frame = tableFrame;
} completion: nil];
self.tableView.scrollEnabled = shouldScroll;
}


- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
if ([change[NSKeyValueChangeKindKey] unsignedIntValue] == NSKeyValueChangeSetting &&
[keyPath isEqualToString:@"contentSize"] &&
!CGSizeEqualToSize([change[NSKeyValueChangeOldKey] CGSizeValue], [change[NSKeyValueChangeNewKey] CGSizeValue])) {
[self resizeTableAccordingToContentSize:[change[NSKeyValueChangeNewKey] CGSizeValue]];
}
}


- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
[self resizeTableAccordingToContentSize:self.tableView.contentSize]; }


- (void)dealloc {
[self.tableView removeObserver:self forKeyPath:@"contentSize"];
}

快速解决方案

遵循以下步骤:

  1. 设置故事板中表格的高度限制。

  2. 从故事板中拖动高度约束,并在视图控制器文件中为它创建@IBOutlet

    @IBOutlet var tableHeight: NSLayoutConstraint!
    
  3. 然后你可以使用下面的代码动态地改变表的高度:

    override func viewWillLayoutSubviews() {
    super.updateViewConstraints()
    self.tableHeight?.constant = self.table.contentSize.height
    }
    

如果最后一行被截断,则尝试在willDisplay cell函数中调用viewWillLayoutSubviews():

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
self.viewWillLayoutSubviews()
}

如果您不想自己跟踪表视图的内容大小变化,您可能会发现这个子类很有用。

protocol ContentFittingTableViewDelegate: UITableViewDelegate {
func tableViewDidUpdateContentSize(_ tableView: UITableView)
}


class ContentFittingTableView: UITableView {


override var contentSize: CGSize {
didSet {
if !constraints.isEmpty {
invalidateIntrinsicContentSize()
} else {
sizeToFit()
}


if contentSize != oldValue {
if let delegate = delegate as? ContentFittingTableViewDelegate {
delegate.tableViewDidUpdateContentSize(self)
}
}
}
}


override var intrinsicContentSize: CGSize {
return contentSize
}


override func sizeThatFits(_ size: CGSize) -> CGSize {
return contentSize
}
}

如果你使用AutoLayout,有一个更好的方法:改变决定高度的约束。只需计算表内容的高度,然后找到约束并更改它。下面是一个例子(假设决定表高度的约束实际上是一个关系为“Equal”的高度约束):

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)


for constraint in tableView.constraints {
if constraint.firstItem as? UITableView == tableView {
if constraint.firstAttribute == .height {
constraint.constant = tableView.contentSize.height
}
}
}
}

Mu解决这个在swift 3:在viewDidAppear中调用此方法

func UITableView_Auto_Height(_ t : UITableView)
{
var frame: CGRect = t.frame;
frame.size.height = t.contentSize.height;
t.frame = frame;
}

Swift 3, iOS 10.3

< >强解决方案1: 只要把self.tableview.sizeToFit()放在cellForRowAt indexPath函数中。确保tableview的高度比你需要的高。 如果tableview下面没有视图,这是一个很好的解决方案。然而,如果你有,底部tableview约束将不会更新(我没有尝试修复它,因为我想出了解决方案2)

例子:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "TestCell", for: indexPath) as? TestCell {
cell.configureCell(data: testArray[indexPath.row])
self.postsTableView.sizeToFit()
return cell
}


return UITableViewCell()
}

< >强解决方案2: 在故事板中设置tableview高度约束,并将其拖到ViewController中。如果你知道单元格的平均高度,也知道数组包含多少元素,你可以这样做:

tableViewHeightConstraint.constant = CGFloat(testArray.count) * 90.0     // Let's say 90 is the average cell height

*编辑:

在我尝试了所有的解决方案之后,他们中的每一个都在修复一些东西,但不完全,是解释和修复这个问题的答案。

Musa almatri的objc版本

(void)viewWillLayoutSubviews
{
[super updateViewConstraints];
CGFloat desiredHeight = self.tableView.contentSize.height;
// clamp desired height, if needed, and, in that case, leave scroll Enabled
self.tableHeight.constant = desiredHeight;
self.tableView.scrollEnabled = NO;
}

我在滚动视图中有一个表格视图,必须计算tableView的高度并相应地调整它的大小。这些是我已经采取的步骤:

0)添加一个UIView到你的scrollView(可能会工作没有这一步,但我这样做是为了避免任何可能的冲突)-这将是一个容器视图为您的表视图。如果你采取了这一步,然后将视图的边界设置为tableview的边界。

1)创建UITableView的子类:

class IntrinsicTableView: UITableView {


override var contentSize:CGSize {
didSet {
self.invalidateIntrinsicContentSize()
}
}


override var intrinsicContentSize: CGSize {
self.layoutIfNeeded()
return CGSize(width: UIViewNoIntrinsicMetric, height: contentSize.height)
}


}

2)在Storyboard中设置一个表视图的类为IntrinsicTableView:截图:http://joxi.ru/a2XEENpsyBWq0A

3)设置heightConstraint到你的表视图

4)拖动你的表的IBoutlet到你的ViewController

5)拖动你的表高度约束的IBoutlet到你的ViewController

6)将这个方法添加到你的ViewController中:

override func viewWillLayoutSubviews() {
super.updateViewConstraints()
self.yourTableViewsHeightConstraint?.constant = self.yourTableView.intrinsicContentSize.height
}

希望这能有所帮助

Swift 5和4.2解决方案没有KVO, DispatchQueue,也没有自己设置约束。

此解决方案基于Gulz的回答

1)创建UITableView的子类:

import UIKit


final class ContentSizedTableView: UITableView {
override var contentSize:CGSize {
didSet {
invalidateIntrinsicContentSize()
}
}


override var intrinsicContentSize: CGSize {
layoutIfNeeded()
return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
}
}

2)在布局中添加UITableView,并在所有边设置约束。将它的类设置为ContentSizedTableView

3)你应该会看到一些错误,因为Storyboard没有考虑我们的子类` intrinsicContentSize。通过打开大小检查器并将intrinsicContentSize重写为一个占位符值来修复此问题。这是设计时的重写。在运行时,它将在ContentSizedTableView类中使用重写


更新: Swift 4.2代码更改。如果您使用的是以前的版本,请使用UIViewNoIntrinsicMetric而不是UIView.noIntrinsicMetric

你可以试试这个自定义AGTableView

使用故事板或编程方式设置TableView高度约束。(这个类自动获取一个高度约束,并将内容视图高度设置为你的tableview高度)。

class AGTableView: UITableView {


fileprivate var heightConstraint: NSLayoutConstraint!


override init(frame: CGRect, style: UITableViewStyle) {
super.init(frame: frame, style: style)
self.associateConstraints()
}


required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.associateConstraints()
}


override open func layoutSubviews() {
super.layoutSubviews()


if self.heightConstraint != nil {
self.heightConstraint.constant = self.contentSize.height
}
else{
self.sizeToFit()
print("Set a heightConstraint to Resizing UITableView to fit content")
}
}


func associateConstraints() {
// iterate through height constraints and identify


for constraint: NSLayoutConstraint in constraints {
if constraint.firstAttribute == .height {
if constraint.relation == .equal {
heightConstraint = constraint
}
}
}
}
}

如果设置高度有任何问题,则yourTableView.layoutSubviews()

如果你的contentSize是不正确的,这是因为它是基于估计的rowheight(自动),使用这个之前

tableView.estimatedRowHeight = 0;

来源:https://forums.developer.apple.com/thread/81895

基于< a href = " https://stackoverflow.com/a/48623673/2885285 " > fl034 < / >的答案。但是对于Xamarin.iOS用户:

    [Register("ContentSizedTableView")]
public class ContentSizedTableView : UITableView
{
public ContentSizedTableView(IntPtr handle) : base(handle)
{
}


public override CGSize ContentSize { get => base.ContentSize; set { base.ContentSize = value; InvalidateIntrinsicContentSize(); } }
public override CGSize IntrinsicContentSize
{
get
{
this.LayoutIfNeeded();
return new CGSize(width: NoIntrinsicMetric, height: ContentSize.Height);
}
}
}

如果您希望您的表是动态的,您将需要使用一个基于上面所详述的表内容的解决方案。如果你只是想显示一个更小的表,你可以使用容器视图并在其中嵌入一个UITableViewController——UITableView将根据容器大小调整大小。

这避免了大量的计算和对布局的调用。

我的Swift 5实现是将tableView的高度约束设置为其内容的大小(contentSize.height)。此方法假定您正在使用自动布局。这段代码应该放在cellForRowAt tableView方法中。

tableView.heightAnchor.constraint(equalToConstant: tableView.contentSize.height).isActive = true

我用了一点不同的方式,实际上我的TableView在scrollview内部,所以我必须给高度约束为0。

然后在运行时,我做了以下更改,

       func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
self.viewWillLayoutSubviews()
}
    

override func viewWillLayoutSubviews() {
super.updateViewConstraints()
DispatchQueue.main.async {
self.tableViewHeightConstraint?.constant = self.myTableView.contentSize.height
self.view.layoutIfNeeded()
}
}

这适用于我使用自动布局,与表视图只有一个部分。

func getTableViewContentHeight(tableView: UITableView) -> CGFloat {
tableView.bounds = CGRect(x: 0, y: 0, width: 300, height: 40)
let rows = tableView.numberOfRows(inSection: 0)
var height = CGFloat(0)
for n in 0...rows - 1 {
height = height + tableView.rectForRow(at: IndexPath(row: n, section: 0)).height
}
return height
}

我在设置自动布局时调用这个函数(这里的示例使用SnapKit,但你知道的):

    let height = getTableViewContentHeight(tableView: myTableView)
myTableView.snp.makeConstraints {
...
...
$0.height.equalTo(height)
}

我希望UITableView只和单元格的总和一样高;我循环遍历单元格,并积累单元格的总高度。因为表格视图的大小是CGRect。此时,我需要设置边界,以便能够尊重单元格定义的自动布局规则。我将大小设置为一个足够大的任意值。实际的大小将在稍后由自动布局系统计算。

Swift 5解决方案

遵循以下四个步骤:

  1. 设置storyboard中tableview的高度约束。

  2. 从故事板中拖动高度约束,并在视图控制器文件中为它创建@IBOutlet

    @IBOutlet var tableViewHeightConstraint: NSLayoutConstraint!
    
  3. override func viewDidLoad()上的contentSize属性添加一个观察者

override func viewDidLoad() {
super.viewDidLoad()
self.tableView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
 

}


  1. 然后你可以使用下面的代码动态地改变表的高度:

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if(keyPath == "contentSize"){
    if let newvalue = change?[.newKey]
    {
    DispatchQueue.main.async {
    let newsize  = newvalue as! CGSize
    self.tableViewHeightConstraint.constant = newsize.height
    }
    
    
    }
    }
    }
    

我使用的是UIView扩展,方法接近上面的@ChrisB方法

 extension UIView {
func updateHeight(_ height:NSLayoutConstraint)
{
    

let newSize = CGSize(width: self.frame.size.width, height: CGFloat(MAXFLOAT))
let fitSize : CGSize = self.sizeThatFits(newSize)
    

height.constant = fitSize.height
    

   

}
}

实现::

@IBOutlet weak var myTableView: UITableView!
@IBOutlet weak var myTableVieweHeight: NSLayoutConstraint!
//(call it whenever tableView is updated inside/outside delegate methods)
myTableView.updateHeight(myTableVieweHeigh)

奖金:可以用于任何其他的uiview,例如:你自己的动态标签

< >强基础 fl034的回答 < / p >

斯威夫特5

var tableViewHeight: NSLayoutConstraint?


tableViewHeight = NSLayoutConstraint(item: servicesTableView,
attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute,
multiplier: 0.0, constant: 10)
tableViewHeight?.isActive = true




func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
tableViewHeight?.constant = tableView.contentSize.height
tableView.layoutIfNeeded()
}

对于我的情况,我是如何管理的。 给定表视图的任意常量高度。创建表视图高度的outlet,然后在你重放tableView的地方调用以下函数

private func manageHeight(){
tableViewHeight.constant=CGFloat.greatestFiniteMagnitude
tableView.reloadData()
tableView.layoutIfNeeded()
tableViewHeight.constant=tableView.contentSize.height
}

注意:tableView是你的表视图的出口,tableViewHeight是tableView高度的出口。