Внедрение intrinsicContentSize правильно для размеров, зависящих от разрешенной внутренней компоновки

У меня есть решение, которое, кажется, работает, но я хочу убедиться, что он безопасен и использует инструмент по назначению. Я могу решить эту проблему до этого сценария - у меня есть UIView, который содержит несколько UILabels, уложенных вертикально. Каждая метка может быть заполнена произвольным текстом, который обертывает и расширяет этикетку по вертикали. Ограничения автоматической компоновки для складывания меток в представлении тривиальны. Я бы хотел, чтобы UIView сообщал свою предпочтительную высоту (высота представления должен соответствовать всем ярлыкам) своему владельцу через intrinsicContentSize. Однако внутренняя компоновка должна быть решена, прежде чем я смогу вернуть соответствующую высоту. Я понимаю, что после возвращения из [super layoutSubviews] решатель будет завершен, это то, что я делаю:

- (void)layoutSubviews
{
    [super layoutSubviews];

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

My intrinsicContentSize реализация относится к последнему кадру ярлыка, чтобы определить высоту. Это все, кажется, работает для моего нынешнего дела, но это заставляет меня нервничать. Похоже, что решатель запрашивает внутреннее значение ContanceSize текущего представления, в то время как он создает свои внутренние элементы. Я не буду вдаваться в подробности, но я столкнулся с ситуацией, в которой этот шаблон превратился в бесконечный цикл. Я смог исправить эту проблему, изменив некоторые из моего кода макета - это не внушало доверия.

Есть ли лучший способ сделать это? Это похоже на то, что хочется делать, и когда я начал работать над проблемой, я думал, что это то, для чего был intrinsicContentSize. Реализации intrinsicContentSize, которые я видел, всегда были тривиальными - жестко запрограммированными размерами для измерения или измеряли единое представление, которое не зависит от разрешенной внутренней компоновки.

Примечание. Я не могу просто использовать метки меток с помощью методов NSString sizeWith..., потому что внутренний макет слишком сложный.

Примечание. Я изучил с помощью systemLayoutSizeFittingSize:, но вызов этого из intrinsicContentSize приводит к бесконечному циклу.

Любой ответ будет оценен! Спасибо заранее!

Ответ 1

Я хочу обновить эту тему и закрыть этот вопрос - я думаю, что здесь все еще значение для других людей, только начиная с AutoLayout, которые могут иметь подобное недоразумение. Важно понять реальную цель intrinsicContentSize - это не то, как типичные представления сообщают свой предпочтительный размер их родительским представлениям. Если в представлении используется AutoLayout для компоновки его дочерних элементов, тогда он будет использовать ограничения, определяющие его внутренний макет, для информирования своего родителя о его размере - в частности, это будут ограничения, которые связывают дочерние представления с их родителями, которые помогут определить размер родителей. Это фактически часть AutoLayout, которая является волшебной и разочаровывающей - это сумма всех этих ограничений (как внутренних, так и внешних по отношению к вашему представлению), которые приводят к компоновке. Внутренний размер действительно необходим только для случаев, когда ваше представление не использует AutoLayout для определения его размера (например, классический случай UILabel или UIImageView).

Я думаю, что ранние ошибки/несоответствия iOS, связанные с многострочным текстом, привели меня к пути к intrinsicContentSize. Многострочный текст по-прежнему не идеален, но, надеюсь, лучше в iOS10.

Ответ 2

Вот решение, которое я придумал для этой проблемы. Если вы хотите предоставить intrinsicContentSize для NSView, который инкапсулирует другие экземпляры NSView, тогда есть две возможности. Либо все subviews имеют значения 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];
}