对于依赖于已解决的内部布局的大小,正确实现intrinsicContentSize

时间:2013-11-06 18:58:44

标签: ios layout uiview uilabel autolayout

我有一个似乎有效的解决方案,但我想确保它是安全的,并按预期使用该工具。我可以将我的问题提炼到这种情况 - 我有一个包含多个垂直堆叠的UILabel的UIView。每个标签都可以填充任意文本,垂直包装和展开标签。在视图中堆叠标签的自动布局约束是微不足道的。我想做的是让UIView通过intrinsicContentSize向其所有者传达其首选高度(视图的高度应该适合所有标签)。但是,在我返回适当的高度之前,需要先解决内部布局问题。我的理解是,从[super layoutSubviews]返回后,求解器保证完整,这就是我正在做的事情:

- (void)layoutSubviews
{
    [super layoutSubviews];

    // solver's complete, now we can measure?
    [self invalidateIntrinsicContentSize];
}

我的intrinsicContentSize实现是指最后一个标签的框架来计算高度。这一切似乎都适用于我目前的情况,但它让我感到紧张。似乎解算器在布局其内部时要求当前视图的intrinsicContentSize。我不会详细介绍,但我遇到了一个案例,其中这种模式转变为无限循环。通过调整我的一些布局代码,我能够解决这个问题 - 这并没有激发人们的信心。

有更好的方法吗?这似乎是一个常见的事情,当我开始研究这个问题时,我认为这就是intrinsicContentSize的用途。我所见过的intrinsicContentSize的实现总是微不足道的 - 维度的硬编码大小,或者测量不依赖于已解决的内部布局的单个视图。

注意,我不能仅通过NSString sizeWith...方法测量标签,因为内部布局太复杂了。

注意,我探讨了使用systemLayoutSizeFittingSize:,但是从intrinsicContentSize调用它会导致无限循环。

任何回复都将不胜感激!提前谢谢!

2 个答案:

答案 0 :(得分:4)

我想更新这个帖子并关闭这个问题 - 我认为对于刚开始使用可能有类似误解的AutoLayout的其他人来说仍然有价值。了解intrinsicContentSize的真正目的非常重要 - 这不是典型视图如何将其首选大小传达给父视图。如果视图使用AutoLayout来布局其子项,那么它将使用定义其内部布局的约束来通知其父项的大小 - 具体而言,它将成为将子视图与其父视图相关联的约束。帮助定义父母的大小。这实际上是AutoLayout的一部分,它既神奇又令人沮丧 - 它是导致布局的所有这些约束(视图的内部和外部)的总和。内部大小实际上仅在视图不使用AutoLayout定义其大小的情况下(例如UILabel或UIImageView的经典案例)。

我认为与多行文本相关的早期iOS错误/不一致是导致我走向intrinsicContentSize的道路。多行文本仍然不完美,但在iOS10中希望更好。

答案 1 :(得分:3)

这是我为此问题提出的解决方案。 如果要为封装其他intrinsicContentSize实例的NSView提供NSView,则有两种可能性。所有子视图都有intrinsicContentSize个值,您可以使用这些值,否则在某个地方至少有一个NSViewNoInstrinsicMetric值,您需要依赖这些子视图的布局结果来计算intrinsicContentSize

在第二种情况下,您的intrinsicContentSize取决于其子视图的帧值,您需要对这些帧的任何更改做出反应,就像标签对其包含的文本的任何更改做出反应一样。您可以使用NSViewFrameDidChangeNotification执行此操作。以下是我在自定义NSView子类中执行此操作的方法:

- (void)didAddSubview:(NSView *)subview
{
    [super didAddSubview:subview];
    subview.postsFrameChangedNotifications = YES;
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(subviewFrameDidChange:) name:NSViewFrameDidChangeNotification object:subview];
    [self setNeedsUpdateConstraints:YES];
    [self invalidateIntrinsicContentSize];
}

- (void)willRemoveSubview:(NSView *)subview
{
    [super willRemoveSubview:subview];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:NSViewFrameDidChangeNotification object:subview];
    [self setNeedsUpdateConstraints:YES];
    [self invalidateIntrinsicContentSize];
}

- (void)subviewFrameDidChange:(NSNotification*)notif
{
    [self invalidateIntrinsicContentSize];
}