【问题标题】:How do I set the height of tableHeaderView (UITableView) with autolayout?如何使用自动布局设置 tableHeaderView (UITableView) 的高度?
【发布时间】:2014-01-25 18:44:14
【问题描述】:

在过去的 3 或 4 个小时里,我一直把头撞在墙上,我似乎无法弄清楚。我有一个 UIViewController,里面有一个全屏 UITableView(屏幕上还有一些其他的东西,这就是我不能使用 UITableViewController 的原因),我想让我的 tableHeaderView 使用自动布局调整大小。不用说,它不合作。

请看下面的截图。

因为overviewLabel(例如“在此处列出概述信息。”文本)具有动态内容,我正在使用自动布局来调整它的大小并且它是超级视图。我已经很好地调整了所有内容,除了 tableHeaderView,它位于层次结构中 Paralax Table View 的正下方。

我发现调整标题视图大小的唯一方法是以编程方式使用以下代码:

CGRect headerFrame = self.headerView.frame;
headerFrame.size.height = headerFrameHeight;
self.headerView.frame = headerFrame;
[self.listTableView setTableHeaderView:self.headerView];

这里headerFrameHeight是手动计算tableViewHeader高度如下(innerHeaderView是白色区域,或者第二个“View”,headerView是tableHeaderView)

CGFloat startingY = self.innerHeaderView.frame.origin.y + self.overviewLabel.frame.origin.y;
CGRect overviewSize = [self.overviewLabel.text
                       boundingRectWithSize:CGSizeMake(290.f, CGFLOAT_MAX)
                       options:NSStringDrawingUsesLineFragmentOrigin
                       attributes:@{NSFontAttributeName: self.overviewLabel.font}
                       context:nil];
CGFloat overviewHeight = overviewSize.size.height;
CGFloat overviewPadding = ([self.overviewLabel.text length] > 0) ? 10 : 0; // If there's no overviewText, eliminate the padding in the overall height.
CGFloat headerFrameHeight = ceilf(startingY + overviewHeight + overviewPadding + 21.f + 10.f);

手动计算是可行的,但是如果将来发生变化,它会很笨重并且容易出错。我想要做的是让 tableHeaderView 根据提供的约束自动调整大小,就像在其他任何地方一样。但是对于我的生活,我无法弄清楚。

关于这个有几篇关于这个的帖子,但没有一个是清楚的,最终让我更加困惑。这里有一些:

将 translatesAutoresizingMaskIntoConstraints 属性更改为 NO 并没有什么意义,因为这只会导致错误,而且在概念上也没有任何意义。

任何帮助将不胜感激!

编辑 1: 感谢 TomSwift 的建议,我能够弄清楚。无需手动计算概览的高度,我可以按如下方式为我计算它,然后像以前一样重新设置 tableHeaderView。

[self.headerView setNeedsLayout];
[self.headerView layoutIfNeeded];
CGFloat height = [self.innerHeaderView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height + self.innerHeaderView.frame.origin.y; // adding the origin because innerHeaderView starts partway down headerView.

CGRect headerFrame = self.headerView.frame;
headerFrame.size.height = height;
self.headerView.frame = headerFrame;
[self.listTableView setTableHeaderView:self.headerView];

编辑 2: 正如其他人所指出的,编辑 1 中发布的解决方案似乎不适用于 viewDidLoad。但是,它似乎在 viewWillLayoutSubviews 中有效。示例代码如下:

// Note 1: The variable names below don't match the variables above - this is intended to be a simplified "final" answer.
// Note 2: _headerView was previously assigned to tableViewHeader (in loadView in my case since I now do everything programatically).
// Note 3: autoLayout code can be setup programatically in updateViewConstraints.
- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];

    [_headerWrapper setNeedsLayout];
    [_headerWrapper layoutIfNeeded];
    CGFloat height = [_headerWrapper systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;

    CGRect headerFrame = _headerWrapper.frame;
    headerFrame.size.height = height;
    _headerWrapper.frame = headerFrame;
    _tableView.tableHeaderView = _headerWrapper;
}

【问题讨论】:

  • setTableHeaderView 在 Xcode6 上不起作用。问题是单元格与 tableHeaderView 重叠。但是,它适用于 Xcode5
  • @DawnSong 知道 Xcode6 的解决方案,以便我可以更新答案?
  • 经过大量试用,我使用UITableViewCell代替tableHeaderView,它可以工作。
  • 我的头也砸了!
  • 真正完整的自动布局解决方案是here

标签: ios ios7 autolayout uitableview


【解决方案1】:

您需要使用UIView systemLayoutSizeFittingSize: 方法来获取标题视图的最小边界大小。

我在此 Q/A 中提供了有关使用此 API 的进一步讨论:

How to resize superview to fit all subviews with autolayout?

【讨论】:

  • 它以 height = 0 返回,尽管我不是 100% 确定我是否已正确配置它,因为这不是 UITableViewCell,因此没有 contentView 可以调用“systemLayoutSizeFittingSize”在。我尝试了什么:[self.headerView setNeedsLayout]; [self.headerView layoutIfNeeded]; CGFloat height = [self.headerView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height; CGRect headerFrame = self.headerView.frame; headerFrame.size.height = 高度; self.headerView.frame = headerFrame; [self.listTableView setTableHeaderView:self.headerView];我还确认了 preferredMax... 是有效的。
  • 啊哈!想通了,我需要做 [self.innerHeaderView systemLayoutSizeFittingSize...] 而不是 self.headerView 因为那是实际的动态内容。非常感谢!
  • 它可以工作,但在我的应用程序中存在小的高度差异。可能是因为我的标签使用了属性文本和动态类型。
【解决方案2】:

我找到了一种优雅的方式来使用自动布局来调整表格标题的大小,无论是否带有动画。

只需将其添加到您的视图控制器。

func sizeHeaderToFit(tableView: UITableView) {
    if let headerView = tableView.tableHeaderView {
        let height = headerView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
        var frame = headerView.frame
        frame.size.height = height
        headerView.frame = frame
        tableView.tableHeaderView = headerView
        headerView.setNeedsLayout()
        headerView.layoutIfNeeded()
    }
}

根据动态变化的标签调整大小:

@IBAction func addMoreText(sender: AnyObject) {
    self.label.text = self.label.text! + "\nThis header can dynamically resize according to its contents."
}

override func viewDidLayoutSubviews() {
    // viewDidLayoutSubviews is called when labels change.
    super.viewDidLayoutSubviews()
    sizeHeaderToFit(tableView)
}

根据约束的变化为调整大小设置动画:

@IBOutlet weak var makeThisTallerHeight: NSLayoutConstraint!

@IBAction func makeThisTaller(sender: AnyObject) {

    UIView.animateWithDuration(0.3) {
        self.tableView.beginUpdates()
        self.makeThisTallerHeight.constant += 20
        self.sizeHeaderToFit(self.tableView)
        self.tableView.endUpdates()
    }
}

查看 AutoResizingHeader 项目以了解其实际效果。 https://github.com/p-sun/Swift2-iOS9-UI

【讨论】:

  • 嘿,太阳。谢谢你的好例子。似乎我对 tableViewHeader 有问题。我试图像你的例子一样有相同的约束,但没有工作。我应该如何为要增加高度的标签添加约束?感谢您的任何建议
  • 确保将您想要提高的标签钩在@IBOutlet makeThisTaller@IBAction fun makeThisTaller 上,就像示例中一样。此外,将标签的所有面都限制为 tableViewHeader(例如顶部、底部、左侧和右侧)。
  • 非常感谢!最后通过添加这一行来解决:lblFeedDescription.preferredMaxLayoutWidth = lblFeedDescription.bounds.width 其中标签是我想要增加大小的那个。谢谢!
  • 谢谢!如果您在 View 而不是 ViewController 中执行此操作,那么您可以覆盖 layoutSubviews 而不是覆盖 viewDidLayoutSubviews
【解决方案3】:

我真的与这个进行了斗争,将设置插入 viewDidLoad 对我来说不起作用,因为框架没有在 viewDidLoad 中设置,我还收到了大量混乱的警告,其中标题的封装自动布局高度正在减少到 0。我只在 iPad 上在 Form 演示文稿中显示 tableView 时注意到了这个问题。

为我解决这个问题的方法是在 viewWillLayoutSubviews 中而不是在 viewDidLoad 中设置 tableViewHeader。

func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        if tableView.tableViewHeaderView == nil {
            let header: MyHeaderView = MyHeaderView.createHeaderView()
            header.setNeedsUpdateConstraints()
            header.updateConstraintsIfNeeded()
            header.frame = CGRectMake(0, 0, CGRectGetWidth(tableView.bounds), CGFloat.max)
            var newFrame = header.frame
            header.setNeedsLayout()
            header.layoutIfNeeded()
            let newSize = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
            newFrame.size.height = newSize.height
            header.frame = newFrame
            self.tableView.tableHeaderView = header
        }
    }

【讨论】:

【解决方案4】:

此解决方案调整 tableHeaderView 的大小并避免viewDidLayoutSubviews() 方法中的无限循环,我在这里有一些其他答案:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    if let headerView = tableView.tableHeaderView {
        let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
        var headerFrame = headerView.frame

        // comparison necessary to avoid infinite loop
        if height != headerFrame.size.height {
            headerFrame.size.height = height
            headerView.frame = headerFrame
            tableView.tableHeaderView = headerView
        }
    }
}

另请参阅此帖子:https://stackoverflow.com/a/34689293/1245231

【讨论】:

  • 我也在用这个方法。认为这是最好的,因为没有摆弄约束。它也非常紧凑。
【解决方案5】:

使用 systemLayoutSizeFittingSize 的解决方案:如果标题视图在每个视图外观上仅更新一次,则有效。就我而言,标题视图多次更新以反映状态变化。但是 systemLayoutSizeFittingSize: 总是报告相同的大小。也就是第一次更新对应的大小。

要获得 systemLayoutSizeFittingSize: 在每次更新后返回正确的大小,我必须先删除表头视图,然后再更新并重新添加它:

self.listTableView.tableHeaderView = nil;
[self.headerView removeFromSuperview];

【讨论】:

    【解决方案6】:

    这在 ios10 和 Xcode 8 上对我有用

    func layoutTableHeaderView() {
    
        guard let headerView = tableView.tableHeaderView else { return }
        headerView.translatesAutoresizingMaskIntoConstraints = false
    
        let headerWidth = headerView.bounds.size.width;
        let temporaryWidthConstraints = NSLayoutConstraint.constraintsWithVisualFormat("[headerView(width)]", options: NSLayoutFormatOptions(rawValue: UInt(0)), metrics: ["width": headerWidth], views: ["headerView": headerView])
    
        headerView.addConstraints(temporaryWidthConstraints)
    
        headerView.setNeedsLayout()
        headerView.layoutIfNeeded()
    
        let headerSize = headerView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
        let height = headerSize.height
        var frame = headerView.frame
    
        frame.size.height = height
        headerView.frame = frame
    
        self.tableView.tableHeaderView = headerView
    
        headerView.removeConstraints(temporaryWidthConstraints)
        headerView.translatesAutoresizingMaskIntoConstraints = true
    
    }
    

    【讨论】:

      【解决方案7】:

      它适用于页眉视图和页脚,只需将页眉替换为页脚

      func sizeHeaderToFit() {
          if let headerView = tableView.tableHeaderView {
      
              headerView.setNeedsLayout()
              headerView.layoutIfNeeded()
      
              let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
              var frame = headerView.frame
              frame.size.height = height
              headerView.frame = frame
      
              tableView.tableHeaderView = headerView
          }
      }
      

      【讨论】:

        【解决方案8】:

        对于 iOS 12 及更高版本,以下步骤将确保自动布局在您的表格和标题中正常工作。

        1. 先创建 tableView,然后创建表头。
        2. 在标头创建代码的末尾,调用:
        [headerV setNeedsLayout];
        [headerV layoutIfNeeded];
        
        1. 在方向改变时,再次标记布局标题并重新加载表格,这需要在报告方向改变后稍微发生:
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 *NSEC_PER_SEC), dispatch_get_main_queue(), ^{
        
          [tableV.tableHeaderView setNeedsLayout];
          [tableV.tableHeaderView layoutIfNeeded];
          [tableV reloadData];});
        

        【讨论】:

          【解决方案9】:

          就我而言,viewDidLayoutSubviews 效果更好。 viewWillLayoutSubviews 导致出现tableView 的白线。我还添加了检查我的 headerView 对象是否已经存在。

          - (void)viewDidLayoutSubviews
          {
              [super viewDidLayoutSubviews];
          
              if ( ! self.userHeaderView ) {
                  // Setup HeaderView
                  self.userHeaderView = [[[NSBundle mainBundle] loadNibNamed:@"SSUserHeaderView" owner:self options:nil] objectAtIndex:0];
                  [self.userHeaderView setNeedsLayout];
                  [self.userHeaderView layoutIfNeeded];
                  CGFloat height = [self.userHeaderView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
                  CGRect headerFrame = self.userHeaderView.frame;
                  headerFrame.size.height = height;
                  self.userHeaderView.frame = headerFrame;
                  self.tableView.tableHeaderView = self.userHeaderView;
          
                  // Update HeaderView with data
                  [self.userHeaderView updateWithProfileData];
              }
          }
          

          【讨论】:

            【解决方案10】:

            很有可能将基于 AutoLayout 的通用 UIView 与任何 AL 内部子视图结构一起用作 tableHeaderView

            唯一需要做的就是在之前设置一个简单的tableFooterView

            self.headerView 是一些基于约束的UIView

            - (void)viewDidLoad {
            
                ........................
            
                self.tableView.tableFooterView = [UIView new];
            
                [self.headerView layoutIfNeeded]; // to set initial size
            
                self.tableView.tableHeaderView = self.headerView;
            
                [self.tableView.leadingAnchor constraintEqualToAnchor:self.headerView.leadingAnchor].active = YES;
                [self.tableView.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor].active = YES;
                [self.tableView.topAnchor constraintEqualToAnchor:self.headerView.topAnchor].active = YES;
            
                // and the key constraint
                [self.tableFooterView.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor].active = YES;
            }
            

            如果self.headerView 在 UI 旋转下改变高度,则必须实现

            - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
                [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
            
                [coordinator animateAlongsideTransition: ^(id<UIViewControllerTransitionCoordinatorContext> context) {
                    // needed to resize header height
                    self.tableView.tableHeaderView = self.headerView;
                } completion: NULL];
            }
            

            为此可以使用 ObjC 类别

            @interface UITableView (AMHeaderView)
            - (void)am_insertHeaderView:(UIView *)headerView;
            @end
            
            @implementation UITableView (AMHeaderView)
            
            - (void)am_insertHeaderView:(UIView *)headerView {
            
                NSAssert(self.tableFooterView, @"Need to define tableFooterView first!");
            
                [headerView layoutIfNeeded];
            
                self.tableHeaderView = headerView;
            
                [self.leadingAnchor constraintEqualToAnchor:headerView.leadingAnchor].active = YES;
                [self.trailingAnchor constraintEqualToAnchor:headerView.trailingAnchor].active = YES;
                [self.topAnchor constraintEqualToAnchor:headerView.topAnchor].active = YES;
            
                [self.tableFooterView.trailingAnchor constraintEqualToAnchor:headerView.trailingAnchor].active = YES;
            }
            @end
            

            还有 Swift 扩展

            extension UITableView {
            
                func am_insertHeaderView2(_ headerView: UIView) {
            
                    assert(tableFooterView != nil, "Need to define tableFooterView first!")
            
                    headerView.layoutIfNeeded()
            
                    tableHeaderView = headerView
            
                    leadingAnchor.constraint(equalTo: headerView.leadingAnchor).isActive = true
                    trailingAnchor.constraint(equalTo: headerView.trailingAnchor).isActive = true
                    topAnchor.constraint(equalTo: headerView.topAnchor).isActive = true
            
                    tableFooterView?.trailingAnchor.constraint(equalTo: headerView.trailingAnchor).isActive = true
                }
            }
            

            【讨论】:

              【解决方案11】:

              这个解决方案非常适合我:

              https://spin.atomicobject.com/2017/08/11/swift-extending-uitableviewcontroller/

              它扩展了 UITableViewController。但是如果你只是使用 UITableView,它仍然可以工作,只需扩展 UITableView 而不是 UITableViewController。 每当发生改变tableViewHeader 高度的事件时,调用方法sizeHeaderToFit()sizeFooterToFit()

              【讨论】:

                【解决方案12】:

                复制自this post

                override func viewDidLayoutSubviews() {
                    super.viewDidLayoutSubviews()
                    
                    if let headerView = tableView.tableHeaderView {
                
                        let height = headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
                        var headerFrame = headerView.frame
                        
                        //Comparison necessary to avoid infinite loop
                        if height != headerFrame.size.height {
                            headerFrame.size.height = height
                            headerView.frame = headerFrame
                            tableView.tableHeaderView = headerView
                        }
                    }
                }
                

                【讨论】:

                  猜你喜欢
                  • 2015-03-10
                  • 1970-01-01
                  • 2016-04-25
                  • 2016-04-12
                  • 1970-01-01
                  • 2015-01-19
                  • 2013-08-19
                  • 2013-09-01
                  相关资源
                  最近更新 更多