使用程序化自动布局时容器视图的框架设置为 CGRectZero

Posted

技术标签:

【中文标题】使用程序化自动布局时容器视图的框架设置为 CGRectZero【英文标题】:Container View's Frame Set to CGRectZero when Using Programmatic Auto Layout 【发布时间】:2015-06-19 02:04:35 【问题描述】:

我有一个UIView 子类,它将是一个工具提示,可以出现在任何视图控制器的底部,以显示提示/错误消息/成功消息/等。工具提示视图有两个子视图:左侧的 UILabel 和右侧的 UIView,可以是任何东西(但通常用作 UIButton)。

TooltipView.h

@interface TooltipView : UIView

@property (nonatomic, readonly) UILabel *textLabel;

@property (nonatomic) UIView *rightView;

TooltipView.m

static CGFloat const kSubviewToSuperviewSpacing = 7.0f;
static CGFloat const kTextLabelToRightViewHorizontalSpacing = 7.0f;

@implementation TooltipView

- (instancetype)initWithFrame:(CGRect)frame 
    self = [super initWithFrame:frame];

    if (self) 
        _textLabel = [[UILabel alloc] init];
        _textLabel.numberOfLines = 0;
        [_textLabel setTranslatesAutoresizingMaskIntoConstraints:NO];

        [self addSubview:_textLabel];
    

    return self;


- (void)setRightView:(UIView *)rightView 
    if (_rightView != rightView) 
        _rightView = rightView;

        [self addSubview:_rightView];

        [self setNeedsDisplay];
    


- (BOOL)translatesAutoresizingMaskIntoConstraints 
    return NO;


- (void)updateConstraints 
    self.didUpdateConstraints = YES;

    [self addConstraint:[NSLayoutConstraint constraintWithItem:self.textLabel
                                                     attribute:NSLayoutAttributeLeading
                                                     relatedBy:NSLayoutRelationEqual
                                                        toItem:self
                                                     attribute:NSLayoutAttributeLeading
                                                    multiplier:1.0f
                                                      constant:kSubviewToSuperviewSpacing]];

    NSMutableDictionary *metricsDictionary = [@@"subviewToSuperviewSpacing": @(kSubviewToSuperviewSpacing) mutableCopy];

    NSMutableDictionary *viewsDictionary = [@@"textLabel": self.textLabel mutableCopy];

    [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-subviewToSuperviewSpacing-[textLabel]-subviewToSuperviewSpacing-|"
                                                                 options:0
                                                                 metrics:metricsDictionary
                                                                   views:viewsDictionary]];

    if (self.rightView) 
        metricsDictionary[@"textLabelToRightViewHorizontalSpacing"] = @(kTextLabelToRightViewHorizontalSpacing);
        viewsDictionary[@"rightView"] = self.rightView;

        [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-subviewToSuperviewSpacing-[rightView]-subviewToSuperviewSpacing-|"
                                                                     options:0
                                                                     metrics:metricsDictionary
                                                                       views:viewsDictionary]];

        [self addConstraint:[NSLayoutConstraint constraintWithItem:self.rightView
                                                         attribute:NSLayoutAttributeTrailing
                                                         relatedBy:NSLayoutRelationEqual
                                                            toItem:self
                                                         attribute:NSLayoutAttributeTrailing
                                                        multiplier:1.0f
                                                          constant:kSubviewToSuperviewSpacing]];
    

    [super updateConstraints];

我也有一个视图控制器来测试这个UILabel 作为rightView。它是UIViewController 的子类,它唯一的方法是覆盖loadView

- (void)loadView 
    TooltipView *tooltipView = [[TooltipView alloc] initWithFrame:CGRectZero];

    tooltipView.textLabel.text = @"Test 1";

    UILabel *testLabel = [[UILabel alloc] initWithFrame:CGRectZero];
    testLabel.text = @"Hello there";
    [testLabel setTranslatesAutoresizingMaskIntoConstraints:NO];

    tooltipView.rightView = testLabel;

    self.view = tooltipView;

最后,在我想要显示工具提示的视图控制器中,我将 TooltipViewController 添加为子视图控制器。最终,这将由某些事件触发,但出于测试目的,我将其添加到 viewWillAppear:

- (void)viewDidAppear:(BOOL)animated 
    [super viewDidAppear:animated];

    self.aViewController = [[TooltipViewController alloc] init];
    self.aViewController.view.backgroundColor = [UIColor whiteColor];

    [self addChildViewController:self.aViewController];

    [self.view addSubview:self.aViewController.view];

    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[view]|"
                                                                  options:0
                                                                  metrics:nil
                                                                    views:@@"view": self.aViewController.view]];

    [self.aViewController.view addConstraint:[NSLayoutConstraint constraintWithItem:self.aViewController.view
                                                      attribute:NSLayoutAttributeHeight
                                                      relatedBy:NSLayoutRelationEqual
                                                         toItem:nil
                                                      attribute:NSLayoutAttributeNotAnAttribute
                                                     multiplier:1.0f
                                                       constant:150.0f]];

    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.aViewController.view
                                                      attribute:NSLayoutAttributeBottom
                                                      relatedBy:NSLayoutRelationEqual
                                                         toItem:self.view
                                                      attribute:NSLayoutAttributeBottom
                                                     multiplier:1.0f
                                                       constant:0.0f]];

    [self.aViewController didMoveToParentViewController:self];

但是,在运行时,textLabelrightView 的框架似乎设置正确,但tooltipView 的框架设置为CGRectZero,三个视图都没有出现在屏幕上。

提前致谢!

编辑

我什至尝试完全删除textLabelrightView,所以只是一个带有前导、尾随、底部和高度约束的普通 UIView。没有什么是模棱两可的,但视图的框架仍然是CGRectZero

【问题讨论】:

您是否在调试器窗口中没有收到一些警告,例如“无法同时满足约束......”和“......将在打破约束后尝试渲染......”?如果有,请确认。 @Gandalf 不,我已经检查以确保布局没有歧义,并且布局没有过度约束(布局系统没有记录任何内容)。 感谢@matt 的反馈,非常感谢!这个想法是为应用程序中的任何视图控制器提供一个框架,以便在需要时显示工具提示,并且工具提示可以根据情况以各种方式进行动画处理(可以淡入、可以滑入、可以淡出、可以滑出) .使用视图控制器和 UIViewControllerAnimatedTransitioning 似乎是执行此操作的合乎逻辑的方式,但如果您不同意,请告诉我! @matt 我喜欢这里使用的模式:objc.io/issues/12-animations/…。对我来说,动画封装的好处超过了使用视图控制器的“重量级”(在这种情况下,我不认为它是重量级的,除非你能证明有显着的性能影响)。 【参考方案1】:

事实证明,解决方案只是在TooltipView 的初始化程序中调用setTranslatesAutoresizingMaskIntoConstraints,而不是覆盖该方法。这导致视图正确显示。 h/t 致 Aaron Thompson (https://twitter.com/aaptho/status/612815498661773312)。

【讨论】:

这只是......什么鬼...... grrr。最糟糕的是,我发誓我昨天已经试过了。 实际上,为什么这甚至可以解决它?如果自动调整掩码真的被转换为约束,那么应该有冲突! 哦,因为它是一个属性,覆盖getter并不会改变ivar,而在UIKit内部检查它的任何东西都是直接查看ivar。这是自动布局的一个小坑。【参考方案2】:

您似乎缺少与 textLabelrightView 相关的约束。查看您的-updateConstraints 实现,我认为最终的约束可以表示为:

V:|-kSubviewToSuperviewSpacing-[textLabel]-kSubviewToSuperviewSpacing-|
V:|-kSubviewToSuperviewSpacing-[rightView]-kSubviewToSuperviewSpacing-|
H:|-kSubviewToSuperviewSpacing-[textLabel]
H:[rightView]-kSubviewToSuperviewSpacing-|

如果您在if (self.rightView) 内部添加约束关系为:

[self addConstraint:[NSLayoutConstraint constraintWithItem:self.textLabel
                                                     attribute:NSLayoutAttributeTrailing
                                                     relatedBy:NSLayoutRelationEqual
                                                        toItem:self.rightView
                                                     attribute:NSLayoutAttributeLeading
                                                    multiplier:1.0f
                                                      constant: kTextLabelToRightViewHorizontalSpacing]];

然后加上else子句:

[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[textLabel]-subviewToSuperviewSpacing-|"
                                                             options:0
                                                             metrics:metricsDictionary
                                                               views:viewsDictionary]];

您将有足够的约束来明确定义布局。这应该会在超级视图中触发基于约束的自动调整大小。

如果这不起作用,您应该考虑覆盖-intrinsicContentSize 方法并返回textLabelrightView 的intrinsicContentSize 加上它们的填充。您需要在-setRightView: 中调用-invalidateIntrinsicContentSize 才能使其正常工作。

作为一个小问题,您没有清除-updateConstraints 中的任何约束,因此在调用此视图的约束更新过程中,您将获得双重约束。您可能希望将一些约束创建移动到-init,然后根据需要仅在-updateConstraints 中添加/删除textLabelrightView 关系。

【讨论】:

Scott,我听从了这里的所有建议,当我在运行时使用视图检查器时,tooltipView 的框架仍设置为CGRectZero。值得注意的是,在添加textLabelrightView 之间的约束之前或之后,我的视图布局并没有歧义。我还尝试完全摆脱TooltipViewController,直接将tooltipView 添加为子视图,但没有骰子 当我覆盖该方法时,内在的内容大小约束也会被覆盖。有关上下文,请参阅***.com/questions/28058590/…

以上是关于使用程序化自动布局时容器视图的框架设置为 CGRectZero的主要内容,如果未能解决你的问题,请参考以下文章

如何在具有自动布局的容器视图中将 UITableView 的大小设置为子视图

自动布局的问题

父 UIViewController 调整容器视图大小时,子视图控制器不自动布局

滚动视图和 UI 组件的通用自动布局

在视图中自动布局视图

自动布局 - 视图控制器框架不正确