【问题标题】:Resizing UITableView to fit content调整 UITableView 的大小以适应内容
【发布时间】:2011-02-05 09:51:50
【问题描述】:

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

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

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

【问题讨论】:

  • 对于任何想要在 OSX 上做类似事情的人,请参阅这个问题:stackoverflow.com/questions/11322139/…
  • @MiMo 嗨,你能解释一下“sizeToFit”方法有什么问题吗?:)
  • “我想找到一个“sizeToFit”的解决方法”。那么你有没有尝试过 UIView 的- sizeToFit 方法?
  • 我可以问与此相关的问题吗?我已经使用了您的方法,但没有将父 tableview 高度设置为子 tableview 内容大小

标签: ios objective-c uitableview sizetofit


【解决方案1】:

无需 KVO、DispatchQueue 或自行设置约束的 Swift 5 和 4.2 解决方案。

此解决方案基于Gulz's answer

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

【讨论】:

  • 很好的答案,对我有用,谢谢。不过有一件事 - 在我的情况下,我需要在情节提要中将 tableView 的类更改为 IntrinsicTableView。我不需要在另一个 UIView 中嵌入我的表格视图。
  • 是的,你是对的。我更新了我的答案。可以像您说的那样阅读第2步。当然,我的意思是将tableView 设置为IntrinsicTableView。也不需要额外的包装UIView。当然,您可以使用任何现有的父视图 :)
  • Swift 4 这对我来说很好tableView.sizeToFit()
  • 是的,如果您的内容是静态的,这可能是一个很好的解决方案。通过我的方法,您可以使用 Auto-Layout 在其他视图中嵌入具有动态内容的 tableView 并始终保持全高,而无需考虑在哪些地方需要调用 sizeToFit()
  • 非常感谢!最后匹配父级并为 tableview 包装内容...
【解决方案2】:

其实我自己找到了答案。

我只是用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;
    });

【讨论】:

  • 这种方法可能会遇到问题。如果您的列表很大,则框架的高度可能会切断您的一些单元格。如果您启用了弹跳并滚动到底部以查看一些单元格弹跳出视图,您会看到这种情况。
  • 你具体在哪里指定高度?你有没有调用 reloadData 并在之后调整它的大小?
  • 哪里是放置此代码的最佳位置?我在 viewDidLoad 中尝试过,它可能没有工作,因为 tableview 委托方法还没有触发。哪种委托方法最好?
  • 我这样做是 viewDidAppear,它似乎奏效了。请注意,tableview 可能比其内容高一点,因为它为节页眉和页脚留出了一点空间
  • 我发现 viewDidAppear 是在所有初始 tableView cellForRowAtIndexPath 调用完成后放置您想要发生的事情的好地方
【解决方案3】:

快速解决方案

按照以下步骤操作:

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

【讨论】:

  • 这个解决方案总是为我切断 Xcode 8.3 上的最后一行。
  • @Jec C:也不适合我
  • 我必须把它放在 cellForRowAtIndexPath: 方法中。如果我把它放在 viewWillLayoutSubviews 方法中,计算出来的 contentSize 高度是基于估计的高度,而不是实际的内容高度。
  • 完美答案。
  • 绝妙的答案!谢谢。
【解决方案4】:

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

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

【讨论】:

  • 如果您的 UITableView 是动态的(意味着动态加载和加载信息),您需要将它放在其他地方,而不是 viewDidLoad。对我来说,我把它放在 cellForRowAtIndexPath 中。还必须将“tableView”更改为我的 IBOutlet 属性的名称,以避免旧的“未找到属性 tableView”错误。
  • thx,在弹出框内调整 tableview 大小时遇到​​问题,它有效
【解决方案5】:

为table view的contentSize属性添加一个观察者,并相应调整frame size

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

希望这会对你有所帮助。

【讨论】:

  • 您应该再添加一件事。您应该删除表视图的观察者。因为如果单元被释放,它会崩溃。
【解决方案6】:

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

希望对你有帮助

【讨论】:

  • 查看我的答案,该答案基于您的子类,但不需要任何高度限制设置。
  • @fl034 提供链接?
  • @fl034 当你的元素嵌入到滚动视图中时,我认为你不能避免使用约束)我的情况是滚动视图
  • 我在滚动视图中也有我的表格视图,它工作得很好:)
  • 在 iOS 13/Xcode 11 上为我工作 您已经结束了 3 天的痛苦,先生,谢谢您
【解决方案7】:

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

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

【讨论】:

  • 对于 swift 3,intrinsicContentSize 变成了一个 var,比如override public var intrinsicContentSize: CGSize { //... return someCGSize }
  • 不适合我。仍然隐藏在桌子的底部
【解决方案8】:

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

tableView.estimatedRowHeight = 0;

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

【讨论】:

    【解决方案9】:

    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
    

    *编辑:

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

    【讨论】:

    • 这是一个非常简单的解决方案,它对我有用,谢谢@Dorde Nilovic
    • @DoedeNilovic 我正在尝试使用您的第二个解决方案,因为我在 tableviewcell 中使用 tableviewcell。我无法获得 insidetableviewcell 高度,因为 90.0 是静态的,我需要它是动态的。你能分享一些代码吗?请
    【解决方案10】:

    我的做法有点不同,实际上我的 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()
              }
           }
    

    【讨论】:

      【解决方案11】:

      Swift 5 解决方案

      遵循以下四个步骤:

      1. 从情节提要中为 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
                     }
        
                 }
             }
         }
        

      【讨论】:

        【解决方案12】:

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

        【讨论】:

        • 您可以对此进行改进。在你的 tableview 上添加一个额外的约束,比如说,height myTableHeightConstraint.constant = tableView.contentSize.height
        【解决方案13】:

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

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

        【讨论】:

          【解决方案14】:

          Mimo's answerAnooj VM 's answer 都很棒,但是如果你有一个很大的列表会有一个小问题,框架的高度可能会切断你的一些单元格。

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

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

          【讨论】:

            【解决方案15】:

            基于 fl034's answer

            斯威夫特 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()
            }
            

            【讨论】:

              【解决方案16】:

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

              【讨论】:

                【解决方案17】:

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

                【讨论】:

                  【解决方案18】:

                  你可以试试这个自定义AGTableView

                  使用情节提要或以编程方式设置 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()

                  【讨论】:

                    【解决方案19】:

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

                    【讨论】:

                      【解决方案20】:

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

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

                      【讨论】:

                        【解决方案21】:

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

                        【讨论】:

                          【解决方案22】:

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

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

                          【讨论】:

                          • 不要使用单词above,因为答案可能会被打乱。
                          【解决方案23】:

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

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

                          【讨论】:

                            猜你喜欢
                            • 2018-06-08
                            • 2011-08-20
                            • 1970-01-01
                            • 2020-10-09
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 2012-07-22
                            • 2011-08-12
                            相关资源
                            最近更新 更多