我正在创建一个应用程序,该应用程序将在 a 中显示一个问题,在 中UILabel
显示一个多项选择答案UITableView
,每行显示一个多项选择。问题和答案会有所不同,所以我需要UITableView
在高度上保持动态。
我想为这张sizeToFit
桌子找个工作。表格的框架设置为其所有内容的高度。
谁能建议我如何实现这一目标?
我正在创建一个应用程序,该应用程序将在 a 中显示一个问题,在 中UILabel
显示一个多项选择答案UITableView
,每行显示一个多项选择。问题和答案会有所不同,所以我需要UITableView
在高度上保持动态。
我想为这张sizeToFit
桌子找个工作。表格的框架设置为其所有内容的高度。
谁能建议我如何实现这一目标?
没有 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
其实我自己找到了答案。
CGRect
我只是为tableView.frame
with创建一个新height
的table.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;
});
迅捷解决方案
跟着这些步骤:
从情节提要中设置表格的高度约束。
从情节提要中拖动高度约束并@IBOutlet
在视图控制器文件中为其创建。
@IBOutlet var tableHeight: NSLayoutConstraint!
然后您可以使用以下代码动态更改表格的高度:
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()
}
我已经在 iOS 7 中尝试过,它对我有用
- (void)viewDidLoad
{
[super viewDidLoad];
[self.tableView sizeToFit];
}
在 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;
}
希望这会帮助你。
我在滚动视图中有一个表格视图,必须计算 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
}
希望这可以帮助
如果您不想自己跟踪表格视图的内容大小变化,您可能会发现这个子类很有用。
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
}
}
如果您的 contentSize 不正确,这是因为它基于estimatedRowHeight(自动),请在此之前使用
tableView.estimatedRowHeight = 0;
斯威夫特 5 解决方案
请遵循以下四个步骤:
从情节提要中设置 tableview 的高度约束。
从情节提要中拖动高度约束并@IBOutlet
在视图控制器文件中为其创建。
@IBOutlet var tableViewHeightConstraint: NSLayoutConstraint!
为 contentSize 属性添加一个观察者override func viewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
}
然后您可以使用以下代码动态更改表格的高度:
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
}
}
}
}
斯威夫特 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
*编辑:
在我尝试了所有解决方案并且每个解决方案都在解决某些问题之后,但并不完全,这是完全解释和解决这个问题的答案。
我做了一点不同的方式,实际上我的 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()
}
}
如果您使用 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
}
}
}
}
这适用于我使用自动布局,表格视图只有一个部分。
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,因此我需要设置边界以便能够遵守单元格定义的自动布局规则。我将大小设置为一个足够大的任意值。实际大小将由自动布局系统稍后计算。
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;
}
基于 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()
}
作为 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"];
}
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;
}
你可以试试这个自定义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()
。
基于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);
}
}
}
我的 Swift 5 实现是将 tableView 的高度约束设置为其内容的大小 ( contentSize.height
)。此方法假定您正在使用自动布局。这段代码应该放在cellForRowAt
tableView 方法中。
tableView.heightAnchor.constraint(equalToConstant: tableView.contentSize.height).isActive = true
我正在使用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,例如:您自己的动态标签
如果您希望您的表格是动态的,您将需要使用基于上述表格内容的解决方案。如果您只是想显示一个较小的表格,您可以使用容器视图并在其中嵌入一个 UITableViewController - UITableView 将根据容器大小调整大小。
这样可以避免大量计算和布局调用。
swift 3 中的 Mu 解决方案:调用此方法viewDidAppear
func UITableView_Auto_Height(_ t : UITableView)
{
var frame: CGRect = t.frame;
frame.size.height = t.contentSize.height;
t.frame = frame;
}