【问题标题】:When does a UITableView's contentSize get set?UITableView 的 contentSize 何时设置?
【发布时间】:2012-04-15 01:04:41
【问题描述】:

我在UIScrollView 中有一个不可滚动的UITableView。我将UITableView 的帧大小设置为其内容大小。

当我向UITableView 添加一行时,我在UITableView 上调用insertRowsAtIndexPaths:withRowAnimation:。然后我调用一个方法来调整UITableView的框架:

- (void)resizeTableViewFrameHeight
{
    // Table view does not scroll, so its frame height should be equal to its contentSize height
    CGRect frame = self.tableView.frame;
    frame.size = self.tableView.contentSize;
    self.tableView.frame = frame;
}

contentSize 目前似乎还没有更新。如果我在上述方法中根据行数和节数手动计算框架,则该方法可以正常工作。

我的问题是,我怎样才能让UITableView 更新它的contentSize?我想我可以打电话给reloadData 并且可能会这样做,但是当我只插入一个单元格时重新加载整个表格似乎效率低下。

【问题讨论】:

    标签: ios uitableview uiscrollview


    【解决方案1】:

    您可以通过在UITableView 上调用layoutIfNeeded 使UITableView 立即计算内容的大小。这将运行所有必要的计算来布局UITableView

    您希望将UITableViewController 子类放入可变大小的容器视图中的示例:

    Objective-C

    - (CGSize)preferredContentSize
    {
        // Force the table view to calculate its height
        [self.tableView layoutIfNeeded];
        return self.tableView.contentSize;
    }
    

    斯威夫特

    override var preferredContentSize: CGSize {
        get {
            // Force the table view to calculate its height
            self.tableView.layoutIfNeeded()
            return self.tableView.contentSize
        }
        set {}
    }
    

    【讨论】:

    • 我试过这个,但它对我不起作用。但是,我能够想出我在这里发布的另一个解决方案:stackoverflow.com/questions/17634617/…
    • 哦,这只是初始的contentSize。之后更改尺寸时,您的里程可能会因其他情况而异。
    • 请注意,这对那些不起作用的人来说——tableView:estimatedHeightForRowAtIndexPath 会与 contentSize 混淆,因此您只需删除它的实现即可获得好运。
    • 不行!调用 insertRowAtIndexPath 后,我调用 layoutIfNeeded,但是 contentSize 没有得到更新。
    • 如何用swift写这个?
    【解决方案2】:

    虽然我不喜欢 KVO(键值观察),但它是一种很好的方法,可以准确地知道您的表的 contentSize 何时发生变化(而不是仅仅在一堆随机的地方调用您的更新方法)。它使用起来相当简单(尽管有些神秘和隐含)。要观察您表​​的contentSize 的变化,请执行以下操作:

    1) 像这样成为表的 contentSize 属性的观察者:

    [self.tableView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew context:NULL];
    

    这通常在拥有tableView 的视图控制器中完成(例如在viewDidLoad: 中)。

    2) 实现 KVO 观察方法并进行所需的更改:

    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
        if(object == self.tableView && [keyPath isEqualToString:@"contentSize"]) {
            // perform your updates here
        }
    }
    

    3) 在某个逻辑点移除您的视图控制器作为观察者(我在dealloc 中调用它)。你这样做:

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

    【讨论】:

    • 感谢您让我想起了 KVO 人。我发现这是唯一适用于我相当复杂的布局的解决方案。
    • 让我在这里做个笔记。正如您提到的,我曾经使用 KVO 进行滚动。但是,如果您使用 beginUpdates 和 endUpdates,它在 iOS 11 之后将无法正常工作。在 iOS 11 中,contentSize 在更新期间会发生变化,也就是说,如果您在 contentSize 更改时立即滚动,那么您将在更新期间更改 table view 的位置,这将带您到非常奇怪的错误。检查我的问题here,使用 KVO 是我的错误的原因。
    • 这里需要注意:使用Runloop避免递归运行导致抛出bad异常。
    • 使用时要小心,您很容易使应用崩溃:常见的崩溃是 KVO_IS_RETAINING_ALL_OBSERVERS_OF_THIS_OBJECT_IF_IT_CRASHES_AN_OBSERVER_WAS_OVERRELEASED_OR_SMASHED
    【解决方案3】:

    试试这个:

    - (void)resizeTableViewFrameHeight
    {
        UITableView *tableView = self.tableView;
        CGRect frame = tableView.frame;
        frame.size.height = [tableView sizeThatFits:CGSizeMake(frame.size.width, HUGE_VALF)].height;
        tableView.frame = frame;
    }
    

    【讨论】:

    • 我试过了,它确实有效,谢谢!但是,虽然它确实解决了我的问题,但并没有真正回答我何时设置内容大小的问题。
    • 我怀疑它是在layoutSubviews 中设置的,但我目前不想验证这一点。
    【解决方案4】:
    1. 添加观察者(在我的示例中viewDidLoad

      tableView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
      
    2. 观察价值

      override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
          if let obj = object as? UITableView {
              if obj == self.tableView && keyPath == "contentSize" {
                  if let newSize = change?[NSKeyValueChangeKey.newKey] as? CGSize {
                      //do stuff here
                  }
              }
          }
      }
      
    3. 在不需要时移除观察者

      deinit {
          self.tableView.removeObserver(self, forKeyPath: "contentSize")
      }
      

    【讨论】:

    • 使用时要小心,您很容易使应用崩溃:常见的崩溃是 KVO_IS_RETAINING_ALL_OBSERVERS_OF_THIS_OBJECT_IF_IT_CRASHES_AN_OBSERVER_WAS_OVERRELEASED_OR_SMASHED
    • 当然,你需要完全控制它,否则 - 它只是一个“黑匣子”
    【解决方案5】:

    正如@Farthen 建议的那样,您应该始终调用 yourTableView.layoutIfNeeded() 首先重新计算内容大小。

    通过调用 layoutIfNeeded UITableView 将再次计算 contentSize 然后您可以更新框架或常量。

    简单的两行代码将节省更多的精力。

    tableView.layoutIfNeeded()
    tableViewHeight.constant = tableView.contentSize.height
    

    【讨论】:

      【解决方案6】:

      这对我有用,在解决添加/删除行后 tableview 获得正确大小的问题时

      使用

          tableView.layoutIfNeeded()
      

      和设置

          tableView.estimatedRowHeight = UITableViewAutomaticDimension
      

      【讨论】:

        【解决方案7】:

        您可以更改页脚的框架。我在页脚动画出现之前调用。

        if self.newTableView.contentSize.height < self.newTableView.frame.height {
                let additionalHeight: CGFloat = self.newTableView.frame.height - self.newTableView.contentSize.height
        
                let tableFooterView = self.newTableView.tableFooterView!
        
                let x = tableFooterView.frame.origin.x
                let y = tableFooterView.frame.origin.y + additionalHeight
                let width = tableFooterView.frame.width
                let height = tableFooterView.frame.height
        
                tableFooterView.frame = CGRect(x: x, y: y, width: width, height: height)
            }
        

        【讨论】:

          猜你喜欢
          • 2014-10-19
          • 2014-02-11
          • 2021-07-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-03-04
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多