Overview

近期在开发中遇到一个奇葩问题:UITableViewHeaderFooterView使用autolayout时报约束异常,但显示上一切正常。经过一番排查,约束并无冲突。网上有看到说因为使用了复用才会出现这问题,这里并不是通过取消复用来解决此问题。虽问题解决,但甚是不解。望大神答疑解惑。

异常示例

上图红框部分用到了UITableViewHeaderFooterView,作为section的头,并不是UITableView的headerView。

这是初始化了UITableViewHeaderFooterView子视图的代码

- (void)initUI {

    self.contentView.backgroundColor = [UIColor whiteColor];

    //商家logo
    self.logoImageView = [[UIImageView alloc] init];
    self.logoImageView.backgroundColor = RGB_HEX(0xffffff);
    self.logoImageView.layer.borderWidth = 1.f;
    self.logoImageView.layer.borderColor = RGB_HEX(0xbfbfbf).CGColor;
    self.logoImageView.layer.cornerRadius = 50/2.f;
    self.logoImageView.clipsToBounds = YES;
    [self addSubview:self.logoImageView];

    //店铺名
    self.storeNameLabel = [[UILabel alloc] init];
    self.storeNameLabel.font = [UIFont boldSystemFontOfSize:kHeight(32)];
    self.storeNameLabel.textAlignment = NSTextAlignmentCenter;
    [self addSubview:self.storeNameLabel];

    //套餐背景视图
    self.comboImageView = [[UIImageView alloc] init];
//    self.comboImageView.contentMode = UIViewContentModeScaleAspectFit;
    self.comboImageView.image = [UIImage imageNamed:@"order_rectangle"];
    [self addSubview:self.comboImageView];

    //套餐视图
    self.comboView = [[UIView alloc] init];
    self.comboView.clipsToBounds = YES;
    self.comboView.layer.cornerRadius = 4.f;
    [self addSubview:self.comboView];

    //商品图片
    self.merchandiseImageView = [[UIImageView alloc] init];
    self.merchandiseImageView.clipsToBounds = YES;
    self.merchandiseImageView.contentMode = UIViewContentModeScaleAspectFill;
//    self.merchandiseImageView.backgroundColor = [UIColor randomColor];
    [self.comboView addSubview:self.merchandiseImageView];

    //商品标题
    self.titleLabel = [[UILabel alloc] init];
    self.titleLabel.textAlignment = NSTextAlignmentCenter;
    self.titleLabel.font = [UIFont boldSystemFontOfSize:kHeight(32)];
    [self.comboView addSubview:self.titleLabel];

    //有效期
    self.expirationTimeLabel = [[UILabel alloc] init];
    self.expirationTimeLabel.textAlignment = NSTextAlignmentCenter;
    self.expirationTimeLabel.font = [UIFont pingFangRegularFontOfSize:kHeight(24)];
    [self.comboView addSubview:self.expirationTimeLabel];

    //左线条
    self.leftLineView = [[UIView alloc] init];
    self.leftLineView.backgroundColor = RGB_HEX(0x000000);
    [self.comboView addSubview:self.leftLineView];

    //右线条
    self.rightLineView = [[UIView alloc] init];
    self.rightLineView.backgroundColor = RGB_HEX(0x000000);
    [self.comboView addSubview:self.rightLineView];

    //当前砍价人数
    self.currentBargainLabel = [[UILabel alloc] init];
    self.currentBargainLabel.textAlignment = NSTextAlignmentCenter;
    self.currentBargainLabel.font = [UIFont pingFangRegularFontOfSize:kHeight(24)];
    [self.comboView addSubview:self.currentBargainLabel];

    //当前价格
    self.bargainPriceLabel = [[UILabel alloc] init];
    self.bargainPriceLabel.textColor = RGB_HEX(0xb4282d);
    self.bargainPriceLabel.font = [UIFont pingFangMediumFontOfSize:kHeight(26)];
    [self addSubview:self.bargainPriceLabel];

    //进度条倒三角
    self.progressTriangleImageView = [[UIImageView alloc] init];
//    self.progressTriangleImageView.image = [UIImage imageNamed:@"order_progress_triangle"];
    [self addSubview:self.progressTriangleImageView];

    //进度条
    //左右轨的图片
    UIImage *stetchLeftTrack= [UIImage imageNamed:@"brightness_bar"];
    UIImage *stetchRightTrack = [UIImage imageNamed:@"brightness_bar_bg"];
    //滑块图片
    UIImage *thumbImage = [UIImage imageNamed:@"order_progress_round"];

    self.slider = [[UISlider alloc] init];
    self.slider.userInteractionEnabled = NO;
//    self.slider.enabled = NO; //禁止滑动
    [self.slider setMinimumTrackImage:stetchLeftTrack forState:UIControlStateNormal];
    [self.slider setMaximumTrackImage:stetchRightTrack forState:UIControlStateNormal];
    //注意这里要加UIControlStateHightlighted的状态,否则当拖动滑块时滑块将变成原生的控件
    [self.slider setThumbImage:thumbImage forState:UIControlStateHighlighted];
    [self.slider setThumbImage:thumbImage forState:UIControlStateNormal];
    [self addSubview:self.slider];

    //优选价
    self.originalPriceLabel = [[UILabel alloc] init];
    self.originalPriceLabel.numberOfLines = 2;
    self.originalPriceLabel.textAlignment = NSTextAlignmentCenter;
    self.originalPriceLabel.font = [UIFont pingFangRegularFontOfSize:kHeight(26)];

    [self addSubview:self.originalPriceLabel];

    //最惠价
    self.preferentialPriceLabel = [[UILabel alloc] init];
    self.preferentialPriceLabel.numberOfLines = 2;
    self.preferentialPriceLabel.textAlignment = NSTextAlignmentCenter;
    self.preferentialPriceLabel.font = [UIFont pingFangRegularFontOfSize:kHeight(26)];
    [self addSubview:self.preferentialPriceLabel];

    //底部分割线
    self.bottomLineView = [[UIView alloc] init];
    self.bottomLineView.backgroundColor = RGB_HEX(0xF2F2F2);
    [self addSubview:self.bottomLineView];

    //砍价人数
    self.bargainNumLabel = [[UILabel alloc] init];
    self.bargainNumLabel.font = [UIFont pingFangMediumFontOfSize:kHeight(30)];
    [self addSubview:self.bargainNumLabel];

    //约束

    //头像
    [self.logoImageView makeConstraints:^(MASConstraintMaker *make) {
        make.centerX.equalTo(self);
        make.top.equalTo(self).offset(kHeight(30));
        make.width.height.equalTo(50);
    }];

    //店铺名
    [self.storeNameLabel makeConstraints:^(MASConstraintMaker *make) {
        make.left.right.equalTo(self);
        make.top.equalTo(self.logoImageView.bottom).offset(kHeight(26));
    }];

    //套餐视图
    [self.comboView makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(self.storeNameLabel.bottom).offset(kHeight(34));
        make.left.equalTo(self).offset(kWidth(52));
        make.right.equalTo(self).offset(-kWidth(52));
        make.height.equalTo(kHeight(586));
    }];

    //套餐背景视图
    [self.comboImageView makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(self.comboView).offset(-kHeight(10));
        make.left.equalTo(self.comboView).offset(-kWidth(16));
        make.right.equalTo(self.comboView).offset(kWidth(16));
        make.bottom.equalTo(self.comboView).offset(kHeight(10));
    }];

    //商品图片
    [self.merchandiseImageView makeConstraints:^(MASConstraintMaker *make) {
        make.top.left.right.equalTo(self.comboView);
        make.height.equalTo(kHeight(308));
    }];

    //标题
    [self.titleLabel makeConstraints:^(MASConstraintMaker *make) {
        make.left.right.equalTo(self.comboView);
        make.top.equalTo(self.merchandiseImageView.bottom).offset(kHeight(46));
    }];

    //截止日期
    [self.expirationTimeLabel makeConstraints:^(MASConstraintMaker *make) {
        make.centerX.equalTo(self.titleLabel);
        make.top.equalTo(self.titleLabel.bottom).offset(kHeight(26));
    }];

    //左线条
    [self.leftLineView makeConstraints:^(MASConstraintMaker *make) {
        make.right.equalTo(self.expirationTimeLabel.left).offset(-kWidth(28));
        make.centerY.equalTo(self.expirationTimeLabel);
        make.width.equalTo(kWidth(20));
        make.height.equalTo(1);
    }];

    //右线条
    [self.rightLineView makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self.expirationTimeLabel.right).offset(14);
        make.centerY.equalTo(self.expirationTimeLabel);
        make.width.equalTo(kWidth(20));
        make.height.equalTo(1);
    }];

    //当前参与砍价人数
    [self.currentBargainLabel makeConstraints:^(MASConstraintMaker *make) {
        make.left.right.equalTo(self);
        make.top.equalTo(self.expirationTimeLabel.bottom).offset(kHeight(30));
    }];

    //进度条
    [self.slider makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(self.comboView.bottom).offset(kHeight(110));
        make.left.equalTo(self).offset(kWidth(50));
        make.right.equalTo(self).offset(-kWidth(50));
        make.height.equalTo(3);
    }];

    //倒三角
    [self.progressTriangleImageView makeConstraints:^(MASConstraintMaker *make) {
        make.bottom.equalTo(self.slider.top).equalTo(-kHeight(16));
        make.left.equalTo(self.slider.left);
    }];

    //当前价格
    [self.bargainPriceLabel makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self.slider.left);
        make.bottom.equalTo(self.progressTriangleImageView.top).offset(-kHeight(10));
    }];

    //优选价
    [self.originalPriceLabel makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self.slider);
        make.top.equalTo(self.slider.bottom).offset(kHeight(16));
    }];

    //最惠价
    [self.preferentialPriceLabel makeConstraints:^(MASConstraintMaker *make) {
        make.right.equalTo(self.slider);
        make.top.equalTo(self.slider.bottom).offset(kHeight(16));
    }];

    //底部分割线
    [self.bottomLineView makeConstraints:^(MASConstraintMaker *make) {
        make.left.right.equalTo(self);
        make.top.equalTo(self.preferentialPriceLabel.bottom).offset(kHeight(70));
        make.height.equalTo(kHeight(20));
    }];

    //砍价好友
    [self.bargainNumLabel makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self).offset(kWidth(30));
        make.right.equalTo(self).offset(-kWidth(30));
        make.top.equalTo(self.bottomLineView.bottom).offset(kHeight(30));
        make.bottom.equalTo(self);
    }];
}

这是初始化UITableView的代码

- (void)initUI {

    self.title = @"砍价";

    self.view.backgroundColor = [UIColor whiteColor];

    self.table = [UITableView customTableViewWithFrame:CGRectZero
                                                 style:UITableViewStyleGrouped
                                        separatorColor:[UIColor clearColor]
                          estimatedSectionHeaderHeight:UITableViewAutomaticDimension
                          estimatedSectionFooterHeight:UITableViewAutomaticDimension];
    self.table.hidden = YES;
    self.table.backgroundColor = [UIColor whiteColor];
    self.table.showsVerticalScrollIndicator = NO;
    self.table.showsHorizontalScrollIndicator = NO;
    self.table.delegate = self;
    self.table.dataSource = self;
    [self.table registerClass:[VCBargainHeaderView class] forHeaderFooterViewReuseIdentifier:@"VCBargainHeaderView"];
    [self.table registerClass:[VCBargainFriendsCell class] forCellReuseIdentifier:@"VCBargainFriendsCell"];
    [self.view addSubview:self.table];

    //底部视图
    self.bottomView = [[UIView alloc] init];
    self.bottomView.backgroundColor = [UIColor whiteColor];
    self.bottomView.hidden = YES;
    [self.view addSubview:self.bottomView];

    //分享按钮
    self.shareBargainBtn = [[UIButton alloc] init];
    self.shareBargainBtn.layer.cornerRadius = 4.f;
    self.shareBargainBtn.titleLabel.font = [UIFont pingFangRegularFontOfSize:kHeight(30)];
    self.shareBargainBtn.backgroundColor = RGB_HEX(0xcc5253);
    [self.shareBargainBtn setTitle:@"分享砍价" forState:UIControlStateNormal];
    [self.bottomView addSubview:self.shareBargainBtn];

    //立即使用
    self.useBtn = [[UIButton alloc] init];
    self.useBtn.layer.cornerRadius = 4.f;
    self.useBtn.layer.borderColor = RGB_HEX(0xb4282d).CGColor;
    self.useBtn.layer.borderWidth = 1.f;
    self.useBtn.titleLabel.font = [UIFont pingFangRegularFontOfSize:kHeight(30)];
    [self.useBtn setTitle:@"立即使用" forState:UIControlStateNormal];
    [self.useBtn setTitleColor:RGB_HEX(0xb4282d) forState:UIControlStateNormal];
    [self.bottomView addSubview:self.useBtn];

    //约束
    [self.bottomView makeConstraints:^(MASConstraintMaker *make) {

        if (@available(iOS 11.0, *)) {
            make.left.equalTo(self.view.safeAreaLayoutGuideLeft).offset(0);
            make.right.equalTo(self.view.safeAreaLayoutGuideRight).offset(0);
            make.bottom.equalTo(self.view.safeAreaLayoutGuideBottom).offset(0);
        } else {
            // Fallback on earlier versions
            make.left.right.bottom.equalTo(self.view);
        }

        make.height.equalTo(73);
    }];

    [self.table makeConstraints:^(MASConstraintMaker *make) {

        if (@available(iOS 11.0, *)) {
            make.left.equalTo(self.view.safeAreaLayoutGuideLeft).offset(0);
            make.right.equalTo(self.view.safeAreaLayoutGuideRight).offset(0);
            make.top.equalTo(self.view.safeAreaLayoutGuideTop).offset(0);

        } else {
            // Fallback on earlier versions
            make.left.right.top.equalTo(self.view);
        }

        make.bottom.equalTo(self.bottomView.top).offset(0);
    }];


}

UITableViewHeaderFooterView代码

static NSString * headerViewStr = @"VCBargainHeaderView";
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {

    VCBargainHeaderView * bargainHeaderView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:headerViewStr];

    if (bargainHeaderView == nil) {
        bargainHeaderView = (VCBargainHeaderView *)[[UITableViewHeaderFooterView alloc] initWithReuseIdentifier:headerViewStr];
    }

    [bargainHeaderView setModel:self.viewModel.couponItem];

    //砍价人数
    bargainHeaderView.bargainNumLabel.text = [NSString stringWithFormat:@"砍价好友(%d)",self.viewModel.cutPriceList.count];

    return bargainHeaderView;
}

运行后约束报错

解决办法

解决方法1:

异常代码(约束报错)

正常代码(约束不报错)

找到与UITableViewHeaderFooterView底部有约束的控件将他与底部的约束优先级改低。如上图(正常代码)所示 即可解决问题

2、解决方法2(网上看到的,亲测无效):

使用UITableView的header或footer复用时,如果采用自动布局,你会发现有约束冲突,下面这样写可以消除约束冲突:

 #import 

 @interface SectionView : UITableViewHeaderFooterView

 @property (nonatomic, copy) NSString *sectionTitle;

 @end

 ####import "SectionView.h"

 @interface SectionView ()
 {
     UIImageView *titleBgImageView;
     UIImageView *timePonitImageView;
     UIImageView *circleImageView;
     UILabel *titleLabe;
 }
 @end

 @implementation SectionView

 // 带有复用
 - (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier
 {
     self = [super initWithReuseIdentifier:reuseIdentifier];
     if (self) {
         [self createUI];
     }
     return self;
 }


 - (void)createUI
 {
     titleBgImageView = [[UIImageView alloc] initForAutoLayout];
     titleBgImageView.userInteractionEnabled = NO;
     UIImage *image = [UIImage imageNamed:@"event_bottom_line"];
     image = [image stretchableImageWithLeftCapWidth:image.size.width*0.5 topCapHeight:image.size.height*0.5];
     titleBgImageView.image = image;
     [self.contentView addSubview:titleBgImageView];


     circleImageView = [[UIImageView alloc] initForAutoLayout];
     UIImage *circleImage = [UIImage imageNamed:@"event_blue1"];
     circleImage = [circleImage stretchableImageWithLeftCapWidth:circleImage.size.width*0.5 topCapHeight:circleImage.size.height*0.5];

     circleImageView.image = circleImage;
     [self.contentView addSubview:circleImageView];


     timePonitImageView = [[UIImageView alloc] initForAutoLayout];
     timePonitImageView.image = [UIImage imageNamed:@"event_write_line"];
     [self.contentView addSubview:timePonitImageView];


     titleLabe = [[UILabel alloc] initForAutoLayout];
     titleLabe.font = [UIFont systemFontOfSize:13];
     titleLabe.textColor = [UIColor whiteColor];
     titleLabe.textAlignment = NSTextAlignmentCenter;
     [circleImageView addSubview:titleLabe];

}

把布局代码写到这里
 - (void)layoutSubviews
 {
     [super layoutSubviews];

     [titleBgImageView autoPinEdgesToSuperviewEdgesWithInsets:UIEdgeInsetsMake(0, 0, 0, 0)];

     [circleImageView autoPinEdgeToSuperviewEdge:ALEdgeLeading withInset:8];
     [circleImageView autoPinEdgeToSuperviewEdge:ALEdgeTrailing withInset:8];
     [circleImageView autoSetDimension:ALDimensionHeight toSize:23];
     [circleImageView autoAlignAxisToSuperviewAxis:ALAxisHorizontal];

    [timePonitImageView autoPinEdgeToSuperviewEdge:ALEdgeLeading withInset:16];
     [timePonitImageView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:39/2.0-4];
    [timePonitImageView autoSetDimensionsToSize:CGSizeMake(9, 23.5)];

     [titleLabe autoCenterInSuperview];
     [titleLabe autoSetDimension:ALDimensionWidth toSize:200];

 }

该代码来自csdn用户王素燕的博客《UITableViewHeaderFooterView的使用+自动布局》。侵删!