不能满足约束 - 视觉格式语言

Posted

技术标签:

【中文标题】不能满足约束 - 视觉格式语言【英文标题】:Cannot satisfy constraints - Visual Format Language 【发布时间】:2015-03-07 16:03:23 【问题描述】:

我开始以编程方式使用自动布局和约束,但我不明白为什么会收到以下警告

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. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "<NSLayoutConstraint:0x7fbbd142ead0 'UIView-Encapsulated-Layout-Height' V:[UITableViewCellContentView:0x7fbbd162ec70(44)]>",
    "<NSLayoutConstraint:0x7fbbd1431740 V:|-(20)-[UILabel:0x7fbbd174e520'Liked']   (Names: '|':UITableViewCellContentView:0x7fbbd162ec70 )>",
    "<NSLayoutConstraint:0x7fbbd1431790 V:[UILabel:0x7fbbd174e520'Liked']-(NSSpace(8))-[UILabel:0x7fbbd174e7e0's']>",
    "<NSLayoutConstraint:0x7fbbd14317e0 V:[UILabel:0x7fbbd174e7e0's']-(20)-|   (Names: '|':UITableViewCellContentView:0x7fbbd162ec70 )>"
)

我知道导致错误的代码行是:

constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-20-[nameLbl]-[notificationLbl]-[textLabel]-20-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:views];

但我不明白为什么不能满足约束。这可能是一个愚蠢的问题,但我是自动布局的新手,而且由于文档不完整,这个话题令人困惑。我尝试了不同的变体和解决方案,但未能找到正确的。我错过了什么?我还粘贴了我的自定义表格视图单元格的代码以进行更详细的检查。

@implementation NotificationCell
@synthesize nameLbl,notificationLbl,textLabel,timeLbl,profileImgView,notificationImgView,clockImgView,didSetupConstraints;
-(id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier 
    if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) 


        nameLbl = [[UILabel alloc]init];
        [nameLbl setTextColor:[UIColor blueColor]];
        nameLbl.translatesAutoresizingMaskIntoConstraints = NO;
        [nameLbl setTextAlignment:NSTextAlignmentLeft];
        [nameLbl setNumberOfLines:1];

        [nameLbl setBackgroundColor:[UIColor redColor]];
        [self.contentView addSubview:nameLbl];

        notificationLbl = [[UILabel alloc]init];
        [notificationLbl setTextColor:[UIColor lightGrayColor]];
        notificationLbl.translatesAutoresizingMaskIntoConstraints = NO;
        [notificationLbl setTextAlignment:NSTextAlignmentLeft];
        [notificationLbl setNumberOfLines:1];
        [self.contentView addSubview:notificationLbl];

        textLabel = [[UILabel alloc]init];
        [textLabel setTextColor:[UIColor blueColor]];
        textLabel.translatesAutoresizingMaskIntoConstraints = NO;
        [textLabel setTextAlignment:NSTextAlignmentLeft];
        [textLabel setNumberOfLines:0];
        [textLabel setBackgroundColor:[UIColor grayColor]];
        [self.contentView addSubview:textLabel];

        timeLbl = [[UILabel alloc]init];
        [timeLbl setTextColor:[UIColor grayColor]];
        timeLbl.translatesAutoresizingMaskIntoConstraints = NO;
        [timeLbl setTextAlignment:NSTextAlignmentLeft];
        [timeLbl setNumberOfLines:1];

        [self.contentView addSubview:timeLbl];

        profileImgView = [[UIImageView alloc]init];
        [profileImgView setTranslatesAutoresizingMaskIntoConstraints:NO];
        [profileImgView setBackgroundColor:[UIColor redColor]];
        [profileImgView.layer setCornerRadius:20];
        [profileImgView.layer setMasksToBounds:YES];
        [self.contentView addSubview:profileImgView];

        clockImgView = [[UIImageView alloc]init];
        [clockImgView setTranslatesAutoresizingMaskIntoConstraints:NO];
        [clockImgView setBackgroundColor:[UIColor blueColor]];
        [clockImgView.layer setCornerRadius:10];
        [clockImgView.layer setMasksToBounds:YES];
        [self.contentView addSubview:clockImgView];

        notificationImgView = [[UIImageView alloc]init];
        [notificationImgView setTranslatesAutoresizingMaskIntoConstraints:NO];
        [notificationImgView setBackgroundColor:[UIColor purpleColor]];
        [notificationImgView.layer setCornerRadius:10];
        [notificationImgView.layer setMasksToBounds:YES];
        [self.contentView addSubview:notificationImgView];

        /*[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[_headerView(==80)]-0-[_tableView]-|"
                                                                          options:NSLayoutFormatDirectionLeadingToTrailing
                                                                          metrics:nil
                                                                            views:elementsDict]];*/



    
    return self;

-(void)updateConstraints
    if (self.didSetupConstraints == NO) 

        NSDictionary *views = NSDictionaryOfVariableBindings(nameLbl,notificationLbl,notificationImgView,textLabel,timeLbl,profileImgView,clockImgView);
        NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[profileImgView(40)]-10-[textLabel]-[clockImgView(20)]-[timeLbl(20)]-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:views];
        [self.contentView addConstraints:constraints];

        constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:[profileImgView]-10-[nameLbl]-[clockImgView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:views];
        [self.contentView addConstraints:constraints];

        constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:[profileImgView]-10-[notificationImgView(20)]-[notificationLbl]-[clockImgView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:views];
        [self.contentView addConstraints:constraints];

        constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-20-[nameLbl]-[notificationLbl]-[textLabel]-20-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:views];
        [self.contentView addConstraints:constraints];

        constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[notificationImgView(20)]-[textLabel]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:views];
        [self.contentView addConstraints:constraints];

        constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-15-[profileImgView(40)]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:views];
        [self.contentView addConstraints:constraints];

        constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-15-[clockImgView(20)]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:views];
        [self.contentView addConstraints:constraints];

        constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-15-[timeLbl]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:views];
        [self.contentView addConstraints:constraints];
        self.didSetupConstraints = YES;
    
    [super updateConstraints];

- (void)layoutSubviews

    [super layoutSubviews];

    [self.contentView setNeedsLayout];
    [self.contentView layoutIfNeeded];

    self.textLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.textLabel.frame);

【问题讨论】:

您需要支持哪些ios版本? (此行为在 iOS 8 中有所改变。)当您收到上述警告时,您运行的是哪个 iOS 版本?另外,我假设您没有使用单元原型,而是有一个 cellForRowAtIndexPath 手动实例化它?也许分享单元格的创建方式,因为行为因单元格的创建方式而异。 我有一个原型单元,但我正在以编程方式创建它,正如您从上面的代码中看到的那样,您的观点是什么?我正在 iOS 8 上进行测试。 如果你使用单元原型,它会用initWithCoder 实例化单元,而不是initWithStyle,这就是我问这个问题的原因。我只询问原型是因为(尤其是 iOS 7),行为很大程度上取决于您如何创建单元格,而您没有与我们分享。在一个单独的主题上,我很惊讶您在添加子视图后没有调用setNeedsUpdateConstraints(这是一个通常调用的方法,以便操作系统为您调用updateConstraints;但是无论如何该方法是如何被调用的)。 其实我在cellForRowAtIndexPath方法中调用setNeedsUpdateConstraints 【参考方案1】:

一些想法:

    确保您既没有在表格视图上显式设置rowHeight 属性,也没有在UITableViewDelegate 中实现tableView:heightForRowAtIndexPath:。其中任何一个都会与执行基于约束的高度计算的单元建立冲突的约束。

    虽然我认为没有必要,但“Luke 说”(在 WWDC 2014 视频中 What's New in Table and Collection Views)在使用约束生成的行高时,应该为表格视图(例如在 viewDidLoad 中)设置 estimatedRowHeight

    self.tableView.estimatedRowHeight = 44;
    

    我也没有发现有必要这样做,但是在同一个 WWDC 2014 视频中,演示者 Luke 遇到了与您类似的奇怪约束错误,直到他在他的代码中添加了以下行:

    self.tableView.rowHeight = UITableViewAutomaticDimension;
    

    他说下个种子不需要这个,但在他的演示中,这是必要的。

    我注意到您仍然经常会收到无法满足的约束错误(即使您已完成上述操作并且所有约束都很好)。我发现,如果您将单元格的垂直约束之一更改为优先级小于 1000,则似乎可以解决此问题(并且单元格的格式似乎正确。

请注意,这种自动计算行高的行为在 iOS 7 中有所不同。但在 iOS 8 中,上述内容应该可以做到。如果您仍然遇到问题,请创建一个显示问题的测试项目并将其上传到我们可以查看的地方。

【讨论】:

我尝试了以上所有方法都没有成功。我将estimatedRowHeight 设置为100.0,但没有成功,它仍然是44。rowHeight 也设置为UITableViewAutomaticDimension。一切都在 iOS 8 和 iOS 7.1 上正确显示,但由于某种原因我仍然收到此警告......也许我真的应该将我的测试项目上传到某个地方以便你可以检查它。 我通过在 updateConstraints 方法中添加 self.frame = CGRectMake(0, 0, self.frame.size.width, 100.0f); self.contentView.frame = CGRectMake(0, 0, self.contentView.frame.size.width, 100.0f); 来修复它...【参考方案2】:

这是因为你的单元格的高度是 44 磅,但是你有垂直放置的标签,其中只有空格是 20 + 标准间距 + 标准间距 + 20 这肯定大于 44,并且仍然有标签高度,您必须考虑到:V:|-20-[nameLbl]-[notificationLbl]-[textLabel]-20-|。所以你应该增加你的细胞的高度。

【讨论】:

我的单元格高度是动态的,这意味着它取决于我的标签之一中添加的字符数。 你一定是做错了什么。看看这个约束:V:[UITableViewCellContentView:0x7fbbd162ec70(44)]。你如何设置单元格的高度?你能分享一段代码吗?如果通过动态,您的意思是单元格高度是由其内部的约束计算的,这不是约束的工作方式。对超级视图没有任何影响,因此您共享的代码不会改变单元格的高度。 我将estimatedRowHeight 设置为[_tableView setEstimatedRowHeight:100.0f];,将rowHeight 设置为[_tableView setRowHeight:UITableViewAutomaticDimension]; 我不知道UITableViewAutomaticDimension,我通常做的是自己计算单元格高度,并在tableView:heightForRowAtIndexPath:委托方法中返回该值。 这是我对 tableView:heightForRowAtIndexPath: 方法的实现 - imageupload.co.uk/images/2015/03/08/…【参考方案3】:

你需要设置self.tableView.rowHeight = UITableViewAutomaticDimension;它会动态地使tableViewCell高度。

您可能还需要将高度约束设置为大于等于标签,这样如果不是内容,它的高度可以为 0。

【讨论】:

【参考方案4】:

我对动态单元高度的一些建议:

首先,不要覆盖NotificationCell 中的updateConstraints 方法。而是在将所有子视图添加到 contentView 之后,在 init 方法中添加约束。

然后,更改 heightForRow 的实现

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *) indexPath 
        NotificationCell *cell = [[NotificationCell alloc] init];

        // Do all your configuration here 
        cell.nameLabel.text = [_names objectAtIndex:indexPath.row];
        // Add all the rest too ... 

        CGFloat cellHeight = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingSizeCompressedSize].height;
        // Add a breakpoint here to see if your constraints are right and if the height is calculated correctly

        return height + 1;

如果您的标签不是多行的,即使没有设置文本,也应该正确计算高度。但是,如果它们可以跨越多行,那么您需要在 heightForRow 方法中设置文本的值。

希望这能解决您的问题。

【讨论】:

以上是关于不能满足约束 - 视觉格式语言的主要内容,如果未能解决你的问题,请参考以下文章

视觉格式化语言、按钮宽度和高度约束

使用视觉格式语言的“宽度等于高度”约束

设置视觉格式语言时 iAutoLayout 崩溃

视觉格式语言并因特征而异

在 Auto Layout 的视觉格式语言中使用可选值

如何以视觉语言格式编写具有内在大小优先级的内容?