-systemLayoutSizeFittingSize:在 iOS 8 下为 tableHeaderView 返回不正确的高度

Posted

技术标签:

【中文标题】-systemLayoutSizeFittingSize:在 iOS 8 下为 tableHeaderView 返回不正确的高度【英文标题】:-systemLayoutSizeFittingSize: returning incorrect height for tableHeaderView under iOS 8 【发布时间】:2014-10-15 20:57:37 【问题描述】:

有很多关于使用自动布局 (one such thread) 正确调整 tableHeaderView 大小的线程,但它们往往早于 ios 8。

我有很多表格视图的情况,所有表格视图都带有标题,在 iOS 7 下大小正确,但在 iOS 8 下使用上述大多数线程支持的代码不正确。在表格的控制器中,我有以下方法:

- (void)rejiggerTableHeaderView

    self.tableView.tableHeaderView = nil;

    UIView *header = self.headerView;

    [header setNeedsLayout];
    [header layoutIfNeeded];

    CGFloat height = [header systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;

    CGRect headerFrame = header.frame;
    headerFrame.size.height = height;

    header.frame = headerFrame;

    self.tableView.tableHeaderView = header;

在 iOS 7 下使用多行标签,这样可以正确调整表格视图的标题,如下所示:

但在 iOS 8 下运行的相同代码会产生以下结果:

获取 -systemLayoutSizeFittingSize: 在 iOS 8 下返回正确大小的技巧是什么? Here's a sample project 证明了这个问题。

【问题讨论】:

【参考方案1】:

将您的 headerview 功能更改为以下对我有用:

- (void)rejiggerTableHeaderView

    self.tableView.tableHeaderView = nil;

    UIView *header = self.headerView;
    CGRect frame = header.frame;
    frame.size.width = self.tableView.frame.size.width;
    header.frame = frame;

    [header setNeedsLayout];
    [header layoutIfNeeded];

    CGFloat height = [header systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;

    CGRect headerFrame = header.frame;
    headerFrame.size.height = height;

    header.frame = headerFrame;

    self.tableView.tableHeaderView = header;

【讨论】:

是的,确实可以解决问题。非常感谢你。值得一提的是,iOS 8.1 修复了一些问题,因此它的行为与 7.1 中的一样(上面的屏幕截图来自 8.0.2)。但是在我的示例项目中进行额外的戳戳后,我意识到上面的两个屏幕截图都是不正确的。如果旋转到横向模式,视图的高度将被设置为正确包含字符串,因此您明确说明视图的实际宽度的修复方法可以修复它。 这对我不起作用。我在标题中有两个标签,其中一个没有正确调整大小。只显示了三行。 我发现罪魁祸首是缺少约束:从UILabel 在所有 4 个方向上对超级视图都有一个支撑是至关重要的。一个缺失的约束将阻止systemLayoutSizeFittingSize:UILayoutFittingCompressedSize 正常工作。 此答案已过时,无法正确处理 Size 类。我已经为现在正在查看的任何人发布了一个更新的答案。【参考方案2】:

问题可能在未设置的preferredMaxLayoutWidth 中。 如果将其设置为正确的 UILabel 宽度,它将正确确定约束。

你可以遍历所有UILabel在header中,设置preferredMaxLayoutWidth为标签宽度。

Swift 3 示例:

extension UITableView 
    public func relayoutTableHeaderView() 
        if let tableHeaderView = tableHeaderView 
            let labels = tableHeaderView.findViewsOfClass(viewClass: UILabel.self)
            for label in labels 
                label.preferredMaxLayoutWidth = label.frame.width
            
            tableHeaderView.setNeedsLayout()
            tableHeaderView.layoutIfNeeded()
            tableHeaderView.frame.height = tableHeaderView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
            self.tableHeaderView = tableHeaderView
        
    

    public func findViewsOfClass<T:UIView>(viewClass: T.Type) -> [T] 
        var views: [T] = []
        for subview in subviews 
            if subview is T 
                views.append(subview as! T)
            
            views.append(contentsOf: subview.findViewsOfClass(viewClass: T.self))
        
        return views
    

更新 2:

如果您的子视图具有纵横比约束并且同时与父视图宽度约束成正比,那么您可能会遇到不正确的高度计算问题

【讨论】:

【参考方案3】:

由于这个问题已经有一年半的历史了,这里有一个更新的完整版本,用 Swift 编写。已接受答案中的某些代码是错误的或过时的。

func fixHeaderHeight() 
    // Set your label text if needed
    // ...
    //

    guard let header = tableView.tableHeaderView else 
        return
        
    let height = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
    header.frame.height = height            
    tableView.tableHeaderView = header

您需要在代码中的其他位置设置标头中标签的preferredMaxLayoutWidth。这应该等于 tableView(或屏幕宽度)减去任何填充。你的标签出口的didSet 方法是个好地方:

@IBOutlet weak var headerMessageLabel: UILabel! 
        didSet 
            headerMessageLabel.preferredMaxLayoutWidth = UIScreen.mainScreen().bounds.width - headerMessageLabelPadding
        
    

注意:如果接受的答案对您有用,则说明您没有正确使用尺码等级。

【讨论】:

【参考方案4】:

如果标题只包含一个标签,那么我使用UILabel 扩展来查找给定宽度的多行标签高度:

public extension UILabel 
    public class func size(withText text: String, forWidth width: CGFloat) -> CGSize 
        let measurementLabel = UILabel()
        measurementLabel.text = text
        measurementLabel.numberOfLines = 0
        measurementLabel.lineBreakMode = .byWordWrapping
        measurementLabel.translatesAutoresizingMaskIntoConstraints = false
        measurementLabel.widthAnchor.constraint(equalToConstant: width).isActive = true
        let size = measurementLabel.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
        return size
    

注意:以上是 Swift 3 语法。

使用上面计算的size,您可以在UITableViewDelegate 方法中返回正确的高度:

func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat

【讨论】:

【参考方案5】:

在 Swift 3 中这个问题的正确解决方案是使用这个类而不是标准的 UILabel:

class UILabelPreferedWidth : UILabel 
    override var bounds: CGRect 
        didSet 
            if (bounds.size.width != oldValue.size.width) 
                self.setNeedsUpdateConstraints()
            
        
    

    override func updateConstraints() 
        if(preferredMaxLayoutWidth != bounds.size.width) 
            preferredMaxLayoutWidth = bounds.size.width
        
        super.updateConstraints()
    

还要确保您没有在 Cell 类中禁用这两个:

translatesAutoresizingMaskIntoConstraints = false
contentView.translatesAutoresizingMaskIntoConstraints = false

【讨论】:

【参考方案6】:

我遇到了同样的问题。 这对我在 iOS 8.4 上很有效。

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 
    [self.myLabel sizeToFit];
    [self.tableView.tableHeaderView layoutIfNeeded];
    return UITableViewAutomaticDimension;

【讨论】:

以上是关于-systemLayoutSizeFittingSize:在 iOS 8 下为 tableHeaderView 返回不正确的高度的主要内容,如果未能解决你的问题,请参考以下文章