Как я могу получить текущую ширину и высоту представления при использовании ограничений автоматического размещения?


108

Я не говорю о свойстве frame, потому что из него вы можете получить размер представления только в xib. Я говорю о том, когда размер представления изменяется из-за его ограничений (может быть, после поворота или в ответ на событие). Есть ли способ узнать его текущую ширину и высоту?

Я пробовал перебирать его ограничения в поисках ограничений по ширине и высоте, но это не очень чисто и терпит неудачу, когда есть внутренние ограничения (поскольку я не могу различить эти два). Кроме того, это работает только в том случае, если у них действительно есть ограничения ширины и высоты, чего нет, если они полагаются на другие ограничения для изменения размера.

Почему для меня это так сложно. ARG!


Я бы подумал, что границы представления в любой конкретный момент сохраняют текущую ширину и высоту. Это то, что корректируется в процессе верстки.
Филлип Миллс

Хм, я никогда не думал проверять границы. Я сделаю это сейчас.
yuf

Ответы:


246

Ответ есть [view layoutIfNeeded].

Вот почему:

Вы по-прежнему получаете текущую ширину и высоту представления, проверяя view.bounds.size.widthи view.bounds.size.height(или фрейм, что эквивалентно, если вы не играете с view.transform).

Если вам нужна ширина и высота, подразумеваемые вашими существующими ограничениями, ответ - не проверять ограничения вручную, так как это потребует от вас повторной реализации всей логики решения ограничений системы автоматической компоновки, чтобы интерпретировать эти ограничения. ограничения. Вместо этого вам нужно просто попросить автоматический макет обновить этот макет , чтобы он разрешил ограничения и обновил значение view.bounds с правильным решением, а затем вы проверите view.bounds.

Как вы попросите автоматический макет обновить макет? Вызовите, [view setNeedsLayout]если хотите, чтобы автоматическая компоновка обновляла компоновку при следующем повороте цикла выполнения.

Однако, если вы хотите, чтобы макет обновлялся немедленно, чтобы вы могли сразу же получить доступ к новому значению границ позже в вашей текущей функции или в другой момент до поворота цикла выполнения, вам необходимо вызвать [view setNeedsLayout]и [view layoutIfNeeded].

Вы задали второй вопрос: «как я могу изменить ограничение высоты / ширины, если у меня нет ссылки на него напрямую?».

Если вы создаете ограничение в IB, лучшим решением будет создание IBOutlet в вашем контроллере представления или в вашем представлении, чтобы у вас была прямая ссылка на него. Если вы создали ограничение в коде, вам следует удерживать ссылку во внутреннем слабом свойстве в то время, когда вы его создавали. Если кто-то другой создал ограничение, вам необходимо найти его, изучив свойство view.constraints в представлении и, возможно, всю иерархию представления, и реализовав логику, которая находит критически важный NSLayoutConstraint. Это, вероятно, неправильный путь, поскольку он также фактически требует от вас определения того, какое конкретное ограничение определяет размер границ, когда нет гарантии, что на этот вопрос будет простой ответ. Окончательное значение границ может быть решением очень сложной системы множественных ограничений,


16
Отличный ответ и хорошо объяснили, тоже. Спас меня через час или два выдергивания волос.
Бен Кригер

2
layoutIfNeededотлично, и сделает раму доступной сразу. Тем не менее, он также принудительно отрисовывает ограничения для всех представлений в поддеревьях представления, для которого оно вызвано. Если вы, например, программно добавляете представления и вызываете их layoutIfNeededвсе в своей рекурсивной процедуре, вы можете обнаружить, что ваша иерархия представлений отображается очень медленно. (Я усвоил это на собственном горьком опыте) Как упоминалось в отличном ответе, setNeedsLayout более эффективен и сделает кадр доступным на следующем проходе макета, что в идеале происходит примерно за 1/60 секунды.
shmim

1
Я знаю, что это старый, но где вы называете этот метод? В initWithCoder?
MayNotBe

4
Зачем заставлять макет просто получить границы? Это кажется неэффективным, а при сложном интерфейсе это может быть дорого. Вместо этого обращайтесь к последним границам из viewDidLayoutSubviews или даже viewWillLayoutSubviews и позвольте какао обрабатывать собственное время макета. Задайте свойство, если вам нужно получить доступ к значению или флагу, чтобы избежать множественных вызовов, если это проблема.
smileBot

1
Да, вам нужно только принудительно установить макет, если вам нужен доступ к автоматически вычисленному значению макета до поворота цикла выполнения - например, в пределах текущего вызова функции.
algal

8

У меня была аналогичная проблема, когда мне нужно было добавить верхнюю и нижнюю границу к UITableViewобъекту, размер которого изменяется в зависимости от настроек ограничений в файле UIStoryboard. Я смог получить доступ к обновленным ограничениям с помощью - (void)viewDidLayoutSubviews. Это полезно для того, чтобы не создавать подклассы для представления и переопределять его метод компоновки.

/*** SET TOP AND BOTTOM BORDERS ON TABLE VIEW ***/
- (void)addBorders
{
    CALayer *topBorder           = [CALayer layer];
    topBorder.frame              = CGRectMake(0.0f, self.tableView.frame.origin.y, 320.0f, 0.5f);
    topBorder.backgroundColor    = [UIColor redColor].CGColor;

    CALayer *bottomBorder        = [CALayer layer];
    bottomBorder.frame           = CGRectMake(0.0f, (self.tableView.frame.origin.y + self.tableView.frame.size.height), 320.0f, 0.5f);
    bottomBorder.backgroundColor = [UIColor redColor].CGColor;

    [self.view.layer addSublayer:topBorder];
    [self.view.layer addSublayer:bottomBorder];
}

/*** GET AUTORESIZED FRAME DIMENSIONS ***/
- (void)viewDidLayoutSubviews{
    [self addBorders];
}

Без вызова метода из viewDidLayoutSubviewметода правильно отображается только верхняя граница, так как нижняя граница находится где-то за пределами экрана.


3
viewDidLayoutSubviews вызывается несколько раз, вы создаете тонны слоев ... Вам нужно добавить несколько проверок существования перед добавлением другого слоя.
Kalzem

Комментарий с опозданием на несколько лет ... вы также должны вызвать этот метод в суперклассе при переопределении, прежде чем что-либо еще:[super viewDidLayoutSubviews];
Алехандро Иван,

5

Для тех, кто все еще может сталкиваться с такими проблемами, особенно с TableviewCell.

Просто переопределите метод:

-(void)layoutSubviews
{
//your code here like drawing a shadow
}

В случае UITableViewCell или UICollectionViewCell создайте подкласс ячейки и переопределите тот же метод:

-(void)layoutSubviews
{
//your code here like drawing a shadow
}

это единственный способ получить реальный окончательный размер моего обзора
Бенуа Жадинон

Это был единственный способ получить окончательный размер для любого из моих ограниченных представлений, потому что до этого я пытался получить их на awakeFromNib, который не давал подпредставлениям время для фактического изменения размера в первую очередь. Спасибо!
Азин Мехрнош,

3

Пользуйтесь -(void)viewWillAppear:(BOOL)animatedи звоните [self.view layoutIfNeeded];- работает Я пробовал.

потому что, если вы -(void)viewDidLayoutSubviewsего используете, он определенно будет работать, но этот метод вызывается каждый раз, когда ваш пользовательский интерфейс требует обновлений / изменений. С которым будет трудно справиться. Программная клавиша - вы используете переменную типа bool, чтобы избежать такого цикла вызовов. лучше использовать viewWillAppear. Помните, viewWillAppearчто также будет вызываться, если представление снова загружается (без перераспределения).


2

Рамка еще действительна. В конце концов, представление использует свойство frame, чтобы расположить себя. Он вычисляет этот кадр на основе всех ограничений. Ограничения используются только для начального макета (и каждый раз, когда layoutSubviews вызывается в представлении, например, после поворота). После этого информация о позиции находится в свойстве frame. Или вы видите иначе?


1
Я считаю иначе. Значения кадров не меняются даже после непосредственного изменения ограничений ширины / высоты (путем изменения их констант. У меня есть IBOutlet для них в xib)
yuf

Моя проблема уникальна, потому что я меняю ограничение, а затем мне нужно использовать фрейм в той же функции. Вызов setNeedsLayout не обновляет его немедленно. Я думаю, мне нужно сначала дождаться макета, а затем использовать рамку. Мой второй вопрос: как я могу изменить ограничение высоты / ширины, если у меня нет ссылки на него напрямую?
yuf

1
Тогда вы должны dispatch_async, и это позволит вам отложить ваш код до следующего кадра. Что касается второй части ... я не знаю ... вам придется перебирать все ограничения и искать, какое из них применимо ... IBOutlets намного проще.
borrrden

Верно для IBOutlets, но я хочу написать функцию в категории для UIView, с которой у меня не будет IB для работы.
yuf
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.