具有自动布局和部分重新加载的UITableViewHeaderFooterView子类将无法很好地协同工作

时间:2013-07-10 21:51:08

标签: ios cocoa-touch uitableview autolayout

我正在尝试将自动布局合并到我的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

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

如果第一部分中没有项目,我隐藏了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;
}

没什么好看的。但是,只要在我的tableView上调用reloadSections:withRowAnimations:,就会抛出异常,因为它“无法同时满足约束条件。”。

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>"
)

这个自动调整大小掩码约束来自哪里?它去哪儿了?

我错过了什么吗?我第一次看到汽车布局就像一周前,所以这是完全可能的。

11 个答案:

答案 0 :(得分:23)

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

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

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

    [super setFrame:frame];
}

<强>解释

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

如果检查页眉/页脚视图上的宽度约束,则有两个,一个包含在超视图(表视图)中,宽度为一个,页眉/页脚视图中包含一个宽度。 / p>

超级视图中包含的约束是NSAutoresizingMaskLayoutConstraint,它是tableview依赖于操作标题的帧的赠品。在标题视图上将translatesAutoresizingMaskIntoConstraints切换为NO会有效地破坏它的外观,这是另一种消除。

看来在某些情况下,这些页眉/页脚视图的帧会更改为宽度为零,对我而言,插入行并重新使用标题视图时。我的猜测是,在UITableView代码的某处,即使您没有使用动画,也可以通过以零宽度启动帧来制作动画。

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

答案 1 :(得分:7)

上周我跑到了这里。

我消除警告的方式是change my required constraints to have a priority of 999。这是一个解决方法而不是修复,但它确实可以解决在布局期间抛出,捕获和记录的异常。

对我来说不起作用的事情。

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

我还尝试在页眉页脚视图及其内容视图中设置translatesAutoresizingMaskIntoConstraints = NO;。既没有摆脱警告,也没有导致布局彻底破坏。

答案 2 :(得分: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];

答案 3 :(得分:2)

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

override var frame: CGRect {
    get {
        return super.frame
    }
    set {
        if newValue.width == 0 { return }
        super.frame = newValue
    }
}

答案 4 :(得分:1)

令人讨厌的是,UITableViewHeaderFooterView看起来并不像常量约束,例如。

  

| -10- [图] -10- |

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

对我来说,解决这个问题的方法是将我的常量约束改为

  

| - (小于= 10) - [图] - (小于= 10) - |

哪个应该满足零宽度内容,并且在调整大小时应该给出所需的余量。

答案 5 :(得分:1)

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

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

这是解决方案

override func layoutSubviews() {
    super.layoutSubviews()
    guard bounds != CGRect.zero else {
        return
    }
    makeConstraints()
}

答案 6 :(得分:0)

我没有在页脚本身看到你在哪里调用translatesAutoresizingMaskIntoConstraints = NO。你应该在创建时这样做吗?

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

答案 7 :(得分:0)

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

findButton
然后我收到了警告:

_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; }); ,但标签的框架是错误的。

答案 8 :(得分:0)

需要更新docker run -v volumename:/data -it my_image $query->select('id, CONCAT_WS(' ', Name, Surname) as name') ->from('customer') ->where('CONCAT_WS(' ', Name, Surname) LIKE "%' . $search .'%"') ->limit(10),警告消失了。这可能类似于Auto layout constraints issue on iOS7 in UITableViewCell

contentView

答案 9 :(得分: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布局过程的某些中间状态出现问题。

答案 10 :(得分:0)

我遇到了这个问题,发现的最佳解决方案是在init中设置默认框架。该框架无论如何都会改变,但是对于在0,0,0,0框架上打破的约束很有用。

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

        //... setup constraints...
}