调整UITableView的大小以适合内容


170

我正在创建一个应用程序,它将在中有一个问题,UILabel并在中显示选择题答案UITableView,每行显示一个选择题。问题和答案会有所不同,因此我需要UITableView保持高度变化。

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

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


对于任何人都希望做OSX类似的事情,看到了这个问题:stackoverflow.com/questions/11322139/...
迈克尔Teper

1
@MiMo嗨,您能解释一下“ sizeToFit”方法有什么问题吗?:)
iamdavidlam

“我想找到一个“ sizeToFit”解决方法”。那么您是否尝试过UIView的- sizeToFit方法?
斯利普·汤普森

Answers:


155

其实我自己找到了答案。

我只是创建一个新CGRecttableView.frameheighttable.contentSize.height

这台的高度UITableViewheight它的内容。由于代码修改了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;
    });

3
这种方法可能会遇到问题。如果列表很大,框架的高度可能会切断某些单元格。如果启用了反弹功能,然后滚动到底部以查看一些单元格反弹,则可以看到这种情况。
whyoz 2012年

您到底在哪里指定高度?您是否致电reloadData并调整后置字号的大小?
詹姆斯

27
放置此代码的最佳位置在哪里?我在viewDidLoad中尝试了它,但它大概无法正常工作,因为还没有触发tableview委托方法。哪种委托方法最好?
凯尔·克莱格

4
我这样做是viewDidAppear,它似乎已经起作用。请注意,tableview可能比其内容高一些,因为它为节的页眉和页脚留了一些空间
Jameo

2
我发现在所有初始tableView cellForRowAtIndexPath调用完成后,viewDidAppear是放置想要发生的事情的好地方
rigdonmr

120

无需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)将a添加UITableView到您的布局中,并在所有侧面设置约束。将其类别设置为ContentSizedTableView

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


更新:更改了Swift 4.2的代码。如果您使用的是早期版本,请使用UIViewNoIntrinsicMetric代替UIView.noIntrinsicMetric


1
很好的答案,对我有用,谢谢。但有一两件事-在我来说,我需要改变的类tableViewIntrinsicTableView在故事板。我不需要将表格视图嵌入另一个视图中UIView
dvp.petrov

是啊,你说得对。我更新了答案。步骤2可以像您说的那样读取。我当然打算将设置tableViewIntrinsicTableView。另外,不需要额外包装UIView。您当然可以使用任何现有的父视图:)
fl034

1
斯威夫特4(Swift 4)这对我来说tableView.sizeToFit()
还算

是的,如果您的内容是静态的,那可能是一个很好的解决方案。使用我的方法,您可以使用自动布局在其他视图中嵌入具有动态内容的tableView,并始终将其保持全高,而无需考虑需要在什么地方sizeToFit()打电话
fl034

2
太好了,谢谢!最后匹配父项并包装表视图的内容...
Amber K

79

快速解决方案

跟着这些步骤:

1-在情节提要中为表格设置高度限制。

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

    @IBOutlet weak var tableHeight: NSLayoutConstraint!

3-然后,您可以使用以下代码动态更改桌子的高度:

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

更新资料

如果为您剪切了最后一行,请尝试在willDisplay单元格函数中调用viewWillLayoutSubviews():

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

1
为我工作!thnx
Pushpa Y

5
对于我来说,此解决方案始终切断Xcode 8.3的最后一行。
Jen C

@Jec C:不是对我也
KMC

也为我工作!
安东尼·拉普尔

1
我必须将其放在cellForRowAtIndexPath:方法中。如果将其放在viewWillLayoutSubviews方法中,则计算出的contentSize高度基于估计的高度,而不是实际的内容高度。
Manu Mateos

21

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

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

3
如果您的UITableView是动态的(意味着信息正在动态加载),则需要将其放置在其他位置,而不是放置在viewDidLoad中。对我来说,我将其放在cellForRowAtIndexPath中。还必须将“ tableView”更改为我的IBOutlet属性的名称,以避免旧的“找不到属性tableView”错误。
jeremytripp 2014年

thx,在popover内部调整tableview的大小有问题,它起作用了
Zaporozhchenko Oleksandr

19

在表视图上为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;
    }

希望这会帮助你。


2
您应该再添加一件事。您应该删除表格视图的观察者。因为如果释放单元,它将崩溃。
Mahesh Agrawal

12

我在滚动视图中有一个表格视图,不得不计算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
    }

希望这可以帮助


请参阅我的答案,该答案基于您的子类,但不需要任何高度限制设置。
fl034

@ fl034提供链接?
Gulz

@ fl034我认为将元素嵌入到Scroll View中时不能避免使用约束。)我的案例是ScrollView
Gulz,

我也有一个滚动视图内的表视图,它完美地工作:)
fl034

在iOS 13 / Xcode 11上为我工作。您已经结束了3天的痛苦,先生,谢谢
Mehdi S.

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

1
对于swift 3,intrinsicContentSize已变成一个override public var intrinsicContentSize: CGSize { //... return someCGSize }
变量

不为我工作。仍然藏在桌子的底部
Gulz


4

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

*编辑:

在我尝试了所有解决方案之后,每个解决方案都在解决问题,但是还没有完全解决,就是可以完全解释和解决此问题的答案。


这是一个非常简单的解决方案,它为我工作,谢谢@Dorde Nilovic
R. Mohan

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

1

如果使用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
            }
        }
    }
}

您可以对此进行改进。在您的表视图上添加一个额外的约束,例如height <=10000。使用从Interface Builder拖拽控制到.h文件,使该约束成为视图控制器的属性。然后,您可以避免遍历约束和搜索。刚刚设置好myTableHeightConstraint.constant = tableView.contentSize.height
RobP

1

这对于使用自动版式的我来说是有效的,并且只有一个部分的表格视图。

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,因此我需要设置范围以遵守单元格定义的自动布局规则。我将大小设置为应该足够大的任意值。实际尺寸将在以后由自动版面系统计算。


1

我做了一些不同的事情,实际上我的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()
          }
       }

0

作为Anooj VM答案的扩展,我建议以下内容仅在内容大小更改时刷新。

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

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

0

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

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


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

0

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

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

-1

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

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


1
不要使用单词,above因为答案可能会被打乱。
Nik Kov

-3

快速解决此问题的方法3:在viewDidAppear

func UITableView_Auto_Height(_ t : UITableView)
{
        var frame: CGRect = t.frame;
        frame.size.height = t.contentSize.height;
        t.frame = frame;        
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.