197

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

我想为这张sizeToFit桌子找个工作。表格的框架设置为其所有内容的高度。

谁能建议我如何实现这一目标?

4

23 回答 23

172

没有 KVO、DispatchQueue 或自己设置约束的 Swift 5 和 4.2 解决方案。

该解决方案基于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

于 2018-02-05T13:28:40.767 回答
166

其实我自己找到了答案。

CGRect我只是为tableView.framewith创建一个新heighttable.contentSize.height

这会将 的高度设置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;
    });
于 2010-04-07T19:29:24.643 回答
110

迅捷解决方案

跟着这些步骤:

  1. 从情节提要中设置表格的高度约束。

  2. 从情节提要中拖动高度约束并@IBOutlet在视图控制器文件中为其创建。

    @IBOutlet var tableHeight: NSLayoutConstraint!
    
  3. 然后您可以使用以下代码动态更改表格的高度:

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

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

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    self.viewWillLayoutSubviews()
}
于 2016-09-27T15:33:46.563 回答
21

我已经在 iOS 7 中尝试过,它对我有用

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.tableView sizeToFit];
}
于 2013-10-24T15:21:05.877 回答
19

在 table view 上为 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;
    }

希望这会帮助你。

于 2014-08-21T05:15:03.227 回答
15

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

0) 将 UIView 添加到您的滚动视图(可能无需此步骤即可工作,但我这样做是为了避免任何可能的冲突) - 这将是您的表格视图的容器视图。如果您采取此步骤,则将视图边框设置为 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
    }

希望这可以帮助

于 2018-02-02T10:04:55.010 回答
8

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

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
    }
}
于 2017-02-17T09:42:06.840 回答
6

如果您的 contentSize 不正确,这是因为它基于estimatedRowHeight(自动),请在此之前使用

tableView.estimatedRowHeight = 0;

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

于 2018-04-13T11:45:34.013 回答
5

斯威夫特 5 解决方案

请遵循以下四个步骤:

  1. 从情节提要中设置 tableview 的高度约束。

  2. 从情节提要中拖动高度约束并@IBOutlet在视图控制器文件中为其创建。

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

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
                 }
    
             }
         }
     }
    
于 2020-11-13T02:38:49.617 回答
4

斯威夫特 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: 在storyboard中设置tableview高度约束,拖拽到ViewController上。如果您知道单元格的平均高度并且知道数组包含多少元素,则可以执行以下操作:

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

*编辑:

在我尝试了所有解决方案并且每个解决方案都在解决某些问题之后,但并不完全,是完全解释和解决这个问题的答案。

于 2017-08-31T08:12:10.473 回答
4

我做了一点不同的方式,实际上我的 TableView 在滚动视图内,所以我必须将高度约束设置为 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()
          }
       }
于 2019-12-04T08:08:08.950 回答
2

如果您使用 AutoLayout,有一个更好的方法来做到这一点:更改确定高度的约束。只需计算表格内容的高度,然后找到约束并更改它。这是一个示例(假设确定表格高度的约束实际上是具有“相等”关系的高度约束):

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
            }
        }
    }
}
于 2017-05-09T09:09:30.793 回答
2

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

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.zero,因此我需要设置边界以便能够遵守单元格定义的自动布局规则。我将大小设置为一个足够大的任意值。实际大小将由自动布局系统稍后计算。

于 2020-03-26T17:47:10.800 回答
1

Mimo 的回答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;
}
于 2015-10-07T05:02:59.047 回答
1

基于 fl034 的回答

斯威夫特 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()
}
于 2021-02-17T09:00:54.843 回答
0

作为 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"];
}
于 2016-03-11T16:47:49.290 回答
0

objc版本的Musa almatri

(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;
}
于 2017-09-06T11:57:34.660 回答
0

你可以试试这个自定义AGTableView

使用情节提要或以编程方式设置 TableView 高度约束。(此类自动获取高度约束并将内容视图高度设置为 yourtableview 高度)。

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

于 2018-03-31T05:28:55.767 回答
0

基于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);
            }
        }
    }
于 2019-07-03T22:06:57.283 回答
0

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

tableView.heightAnchor.constraint(equalToConstant: tableView.contentSize.height).isActive = true
于 2019-12-04T01:23:36.417 回答
0

我正在使用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,例如:您自己的动态标签

于 2020-11-16T01:03:37.570 回答
-1

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

这样可以避免大量计算和布局调用。

于 2019-08-05T09:09:26.160 回答
-3

swift 3 中的 Mu 解决方案:调用此方法viewDidAppear

func UITableView_Auto_Height(_ t : UITableView)
{
        var frame: CGRect = t.frame;
        frame.size.height = t.contentSize.height;
        t.frame = frame;        
}
于 2017-08-17T18:05:22.540 回答