23

我正在尝试将自动布局合并到我的 UITableViewHeaderFooterView 子类中。该类非常基础,只有两个标签。这是完整的子类:

@implementation MBTableDetailStyleFooterView

static void MBTableDetailStyleFooterViewCommonSetup(MBTableDetailStyleFooterView *_self) {
    UILabel *rightLabel = [[UILabel alloc] init];
    _self.rightLabel = rightLabel;
    rightLabel.translatesAutoresizingMaskIntoConstraints = NO;
    [_self.contentView addSubview:rightLabel];

    UILabel *leftLabel = [[UILabel alloc] init];
    _self.leftLabel = leftLabel;
    leftLabel.translatesAutoresizingMaskIntoConstraints = NO;
    [_self.contentView addSubview:leftLabel];

    NSDictionary *views = NSDictionaryOfVariableBindings(rightLabel, leftLabel);

    NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|-10-[leftLabel]-(>=10)-[rightLabel]-10-|" options:0 metrics:nil views:views];
    [_self.contentView addConstraints:horizontalConstraints];

    // center views vertically in super view
    NSLayoutConstraint *leftCenterYConstraint = [NSLayoutConstraint constraintWithItem:leftLabel attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:_self.contentView attribute:NSLayoutAttributeCenterY multiplier:1 constant:0];
    [_self.contentView addConstraint:leftCenterYConstraint];
    NSLayoutConstraint *rightCenterYConstraint = [NSLayoutConstraint constraintWithItem:rightLabel attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:_self.contentView attribute:NSLayoutAttributeCenterY multiplier:1 constant:0];
    [_self.contentView addConstraint:rightCenterYConstraint];

    // same height for both labels
    NSLayoutConstraint *sameHeightConstraint = [NSLayoutConstraint constraintWithItem:leftLabel attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:rightLabel attribute:NSLayoutAttributeHeight multiplier:1 constant:0];
    [_self.contentView addConstraint:sameHeightConstraint];
}

+ (BOOL)requiresConstraintBasedLayout {
    return YES;
}

- (id)initWithReuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithReuseIdentifier:reuseIdentifier];
    MBTableDetailStyleFooterViewCommonSetup(self);
    return self;
}

@end

此类用作具有 2 个部分的 tableView 的第一部分的页脚。第一部分包含动态项目。第二部分只有一行,用于向第一部分添加新项目。

如果第一部分中没有项目,我会隐藏 footerView。因此,当我添加第一个新项目时,我必须重新加载该部分,以便显示 footerView。完成这一切的代码如下所示:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    if (indexPath.section == 1) {
        BOOL sectionNeedsReload = ([self.data count] == 0); // reload section when no data (and therefor no footer) was present before the add
        [self.data addObject:[NSDate date]];
        NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:[self.data count]-1 inSection:0];
        if (sectionNeedsReload) {
            [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationAutomatic];
        }
        else {
            [self.tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
        }
        [self configureFooter:(MBTableDetailStyleFooterView *)[tableView footerViewForSection:0] forSection:0];
    }
}

- (void)configureFooter:(MBTableDetailStyleFooterView *)footer forSection:(NSInteger)section {
    footer.leftLabel.text = @"Total";
    footer.rightLabel.text = [NSString stringWithFormat:@"%d", [self.data count]];
}

- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
    MBTableDetailStyleFooterView *footer = nil;
    if (section == 0 && [self.data count]) {
        footer = [tableView dequeueReusableHeaderFooterViewWithIdentifier:@"Footer"];
        [self configureFooter:footer forSection:section];
    }
    return footer;
}

- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
    CGFloat height = 0;
    if (section == 0 && [self.data count]) {
        height = 20.0f;
    }
    return height;
}

没什么特别的。但是,一旦reloadSections:withRowAnimations:在我的 tableView 上调用它就会引发异常,因为它“无法同时满足约束。”。

tableView 的某处向我的页脚添加了翻译后的自动调整大小掩码约束。

(
    "<NSLayoutConstraint:0x718a1f0 H:[UILabel:0x7189130]-(10)-|   (Names: '|':_UITableViewHeaderFooterContentView:0x7188df0 )>",
    "<NSLayoutConstraint:0x7189e30 H:[UILabel:0x71892c0]-(>=10)-[UILabel:0x7189130]>",
    "<NSLayoutConstraint:0x718a0a0 H:|-(10)-[UILabel:0x71892c0]   (Names: '|':_UITableViewHeaderFooterContentView:0x7188df0 )>",
    "<NSAutoresizingMaskLayoutConstraint:0x7591ab0 h=--& v=--& H:[_UITableViewHeaderFooterContentView:0x7188df0(0)]>"
)

当我替换reloadSections:withRowAnimations:reloadData不添加自动调整大小的蒙版约束时,一切正常。

有趣的是,异常告诉我它试图打破约束<NSLayoutConstraint:0x7189e30 H:[UILabel:0x71892c0]-(>=10)-[UILabel:0x7189130]>

但是当我在后续调用中记录约束时,configureFooter:forSection:这个约束仍然存在,但自动调整大小的蒙版约束消失了

约束正是我设置的那些。

(
    "<NSLayoutConstraint:0x718a0a0 H:|-(10)-[UILabel:0x71892c0]   (Names: '|':_UITableViewHeaderFooterContentView:0x7188df0 )>",
    "<NSLayoutConstraint:0x7189e30 H:[UILabel:0x71892c0]-(>=10)-[UILabel:0x7189130]>",
    "<NSLayoutConstraint:0x718a1f0 H:[UILabel:0x7189130]-(10)-|   (Names: '|':_UITableViewHeaderFooterContentView:0x7188df0 )>",
    "<NSLayoutConstraint:0x718a3f0 UILabel:0x71892c0.centerY == _UITableViewHeaderFooterContentView:0x7188df0.centerY>",
    "<NSLayoutConstraint:0x718a430 UILabel:0x7189130.centerY == _UITableViewHeaderFooterContentView:0x7188df0.centerY>",
    "<NSLayoutConstraint:0x718a4b0 UILabel:0x71892c0.height == UILabel:0x7189130.height>"
)

这个自动调整大小的蒙版约束来自哪里?它去哪儿了?

我错过了什么吗?我第一次研究自动布局是在一周前,所以这是完全可能的。

4

11 回答 11

27

从 iOS 9 开始的工作解决方案

在您的 UITableViewHeaderFooterView 子类中放置以下代码。

- (void)setFrame:(CGRect)frame {
    if (frame.size.width == 0) {
        return;
    }
    
    [super setFrame:frame];
}

解释:

表格视图处理标题视图的布局,它通过手动操作框架来实现(即使打开了自动布局也是如此)。

如果您检查页眉/页脚视图上的宽度约束,则有两个,一个包含在超级视图(表格视图)中的宽度,另一个包含在页眉/页脚视图本身的宽度中。

超级视图中包含的约束是一个 NSAutoresizingMaskLayoutConstraint,它是 tableview 依赖于框架来操作标题的赠品。在标题视图上将 translatesAutoresizingMaskIntoConstraints 切换为 NO 有效地破坏了它的外观,这是另一个放弃。

似乎在某些情况下,这些页眉/页脚视图的框架宽度将更改为零,对我来说,这是插入行并重用页眉视图的时候。我的猜测是,在 UITableView 代码的某处,即使您没有使用动画,也可以通过以零宽度开始帧来为动画做准备。

此解决方案应该运行良好,并且不应该影响滚动性能。

于 2016-01-28T05:04:15.197 回答
9

我上周遇到了这个。

我消除警告的方法是将我所需的约束更改为优先级 999。这是一种解决方法,而不是修复方法,但它确实解决了在布局期间抛出、捕获和记录的异常。

对我不起作用的事情。

一个建议是设置estimatedRowHeight . 我试图设置estimatedSectionHeaderHeight,但这没有帮助。在我不想要它们的地方设置一个estimatedSectionFooterHeight创建的空页脚,这有点奇怪。

我还尝试translatesAutoresizingMaskIntoConstraints = NO;在页眉页脚视图及其内容视图上进行设置。两者都没有摆脱警告,并且导致布局完全破坏。

于 2015-07-20T13:20:02.273 回答
4

太奇怪了!感谢@josh-bernfield,这是我为 iOS 11.3 写的:

override var frame: CGRect {
    get {
        return super.frame
    }
    set {
        if newValue.width == 0 { return }
        super.frame = newValue
    }
}
于 2018-06-02T20:33:40.980 回答
3

contentView 中只有一个额外的标签,我遇到了类似的问题。尝试插入

static void MBTableDetailStyleFooterViewCommonSetup(MBTableDetailStyleFooterView *_self) {
    _self.contentView.translatesAutoresizingMaskIntoConstraints = NO
    [...]
}

在你的MBTableDetailStyleFooterViewCommonSetup函数的第一行。对我来说,这与reloadSections:withRowAnimations:.

更新:

我还为 contentView 添加了一个新约束以使用所有宽度:

NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[contentView]|"
                                                                          options:0
                                                                          metrics:nil
                                                                            views:@{@"contentView" : _self.contentView}];
[_self.contentView.superview addConstraints:horizontalConstraints];
于 2013-10-30T16:20:16.443 回答
2

令人讨厌的是,它看起来 UITableViewHeaderFooterView 不喜欢常量约束,例如

|-10-[查看]-10-|

我猜是因为在创建时视图的宽度为零并且不能满足约束,在这种情况下至少需要 20px 的宽度。

对我来说,解决这个问题的方法是将我不断的约束变成类似的东西

|-(<=10)-[查看]-(<=10)-|

它应该满足零宽度的内容,并且在调整大小时应该提供所需的边距。

于 2016-01-21T13:38:08.183 回答
2

在 iOS 11 中,我无法正确UITableViewHeaderFooterView显示。

UITableViewHeaderFooterView问题是当我认为它的边界是空的时我正在设置我的约束,所以它总是会导致冲突。

这是解决方案

override func layoutSubviews() {
    super.layoutSubviews()
    guard bounds != CGRect.zero else {
        return
    }
    makeConstraints()
}
于 2017-10-18T01:10:29.760 回答
0

我看不到您在页脚本身上调用 translatesAutoresizingMaskIntoConstraints = NO 的位置​​。创建时应该这样做吗?

- (id)initWithReuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithReuseIdentifier:reuseIdentifier];
    self.translatesAutoresizingMaskIntoConstraints = NO;
    MBTableDetailStyleFooterViewCommonSetup(self);
    return self;
}
于 2013-07-10T22:05:30.183 回答
0

我有一个类似的问题,contentView中只有一个UILabel,我使用了Masonry:

_titleLabel = ({
    UILabel *label = [MLBUIFactory labelWithType:MALabelTypeB];
    [self.contentView addSubview:label];
    [label mas_makeConstraints:^(MASConstraintMaker */make) {
        make.centerY.equalTo(self.contentView);
        make.left.equalTo(self.contentView).offset(8);
        make.right.equalTo(self.contentView).offset(-8);
    }];

    label;
});

然后我收到警告:

(
"<MASLayoutConstraint:0x1742b2c60 UILabel:0x124557ee0.left == _UITableViewHeaderFooterContentView:0x12454c640.left + 8>",
"<MASLayoutConstraint:0x1742b3080 UILabel:0x124557ee0.right == _UITableViewHeaderFooterContentView:0x12454c640.right - 8>",
"<NSLayoutConstraint:0x174280780 _UITableViewHeaderFooterContentView:0x12454c640.width == 0>"
)
Will attempt to recover by breaking constraint 
<MASLayoutConstraint:0x1742b3080 UILabel:0x124557ee0.right == _UITableViewHeaderFooterContentView:0x12454c640.right - 8>

所以我尝试设置self.translatesAutoresizingMaskIntoConstraints = NO;,但它对我不起作用,所以我专注于警告消息,我很困惑,为什么左约束有效但正确,当我看到 时_UITableViewHeaderFooterContentView:0x12454c640.width == 0,我想通了,也许这就是为什么右约束是破坏约束,然后我更改了代码:

_titleLabel = ({
    UILabel *label = [MLBUIFactory labelWithType:MALabelTypeB];
    [self.contentView addSubview:label];
    [label mas_makeConstraints:^(MASConstraintMaker */make) {
        make.centerY.equalTo(self.contentView);
        make.left.equalTo(self.contentView).offset(8);
        make.width.equalTo(@(SCREEN_WIDTH - 16));
    }];

    label;
});

我用宽度约束替换了正确的约束,警告消失了。

还有其他方法可以让警告消失:self.contentView.translatesAutoresizingMaskIntoConstraints = NO;,但标签的框架是错误的。

于 2015-12-26T08:26:58.533 回答
0

我遇到了这个问题,我发现最好的解决方案是在 init.xml 中设置一个默认框架。无论如何,该帧都会被更改,但它对于在 0,0,0,0 帧上中断的约束很有用。

override init(reuseIdentifier: String?) {
        super.init(reuseIdentifier: reuseIdentifier)        
        frame = CGRect(x: 0, y: 0, width: 100, height: 100)

        //... setup constraints...
}
于 2019-01-14T10:30:32.443 回答
0

需要更新contentView frame,警告消失了。这可能类似于UITableViewCell 中 iOS7 上的自动布局约束问题

public class MyHeaderview: UITableViewHeaderFooterView {
    // Add views to contentView

    public override func layoutSubviews() {
        super.layoutSubviews()
        contentView.frame = bounds
    }
}
于 2016-03-04T13:29:20.967 回答
0

我认为我们可以放心地忽略控制台警告。

我怀疑控制台日志是由表视图布局过程的某些中间状态引起的。

当我添加带有自动布局内容的页脚部分视图时,我也会在控制台中收到无法满足的约束警告。

从下面的日志中,导致问题的约束应该是_UITableViewHeaderFooterContentView:0x7ff2115778d0.height == 0

2018-08-23 16:01:27.036159-0700 Attendee[45370:2760310] [LayoutConstraints] Unable to simultaneously satisfy constraints.
    Probably at least one of the constraints in the following list is one you don't want. 
    Try this: 
        (1) look at each constraint and try to figure out which you don't expect; 
        (2) find the code that added the unwanted constraint or constraints and fix it. 
(
    "<NSLayoutConstraint:0x60000029d7e0 UILayoutGuide:0x6000003b98a0'UIViewLayoutMarginsGuide'.bottom == UIButton:0x7ff2115770a0.bottom + 20   (active)>",
    "<NSLayoutConstraint:0x604000481d60 UILayoutGuide:0x6000003b98a0'UIViewLayoutMarginsGuide'.top == UIButton:0x7ff211576790'Forgot password?'.top   (active)>",
    "<NSLayoutConstraint:0x604000481c70 UIButton:0x7ff211576790'Forgot password?'.bottom == UIButton:0x7ff2115770a0.top - 8   (active)>",
    "<NSLayoutConstraint:0x60000029d600 'UIView-bottomMargin-guide-constraint' V:[UILayoutGuide:0x6000003b98a0'UIViewLayoutMarginsGuide']-(8)-|   (active, names: '|':_UITableViewHeaderFooterContentView:0x7ff2115778d0 )>",
    "<NSLayoutConstraint:0x60000029da10 'UIView-Encapsulated-Layout-Height' _UITableViewHeaderFooterContentView:0x7ff2115778d0.height == 0   (active)>",
    "<NSLayoutConstraint:0x60000029d560 'UIView-topMargin-guide-constraint' V:|-(8)-[UILayoutGuide:0x6000003b98a0'UIViewLayoutMarginsGuide']   (active, names: '|':_UITableViewHeaderFooterContentView:0x7ff2115778d0 )>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x60000029d7e0 UILayoutGuide:0x6000003b98a0'UIViewLayoutMarginsGuide'.bottom == UIButton:0x7ff2115770a0.bottom + 20   (active)>

如果我覆盖layoutSubviews页脚视图以转储影响垂直布局的约束:

override func layoutSubviews() {
    super.layoutSubviews()

    print("after layout")
    dump(contentView.constraintsAffectingLayout(for: .vertical))
}

我得到以下输出:

▿ 2 elements
  - <NSLayoutConstraint:0x600000297110 'UIView-Encapsulated-Layout-Height' _UITableViewHeaderFooterContentView:0x7ffc22d88c20.height == 101   (active)> #0
    - super: NSObject
  - <NSAutoresizingMaskLayoutConstraint:0x6000002971b0 h=--& v=--& 'UIView-Encapsulated-Layout-Top' _UITableViewHeaderFooterContentView:0x7ffc22d88c20.minY == 0   (active, names: '|':Attendee.SearchFormButtonFooter:0x7ffc22d20da0 )> #1
    - super: NSLayoutConstraint
      - super: NSObject

没有明确的布局约束将内容视图的高度设置为 0。它可能只是内容视图的自动调整大小掩码导致 UITableView 布局过程的某些中间状态出现问题。

于 2018-08-23T23:21:39.397 回答