Подтвердить что ты не робот

Получение правильных границ представления UIViewController

У меня есть iPad-приложение. В альбомной ориентации UIViewController вид фактической ширины = 1024px и height = 768-20 (statusBar) - 44 (navigationBar) = 704px.

Итак, я хочу получить этот размер [1024 x 704], и я использую self.view.bounds для него. Он возвращает [748 x 1024], что неверно! Но когда я поворачиваю экран дважды (текущий → портрет → текущий), границы обзора верны - [1024 x 704].

Представление было инициализировано следующим образом:

- (void)loadView {
    self.view = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
    self.view.backgroundColor = [UIColor lightGrayColor];
}

И границы были такими:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"bounds = %@", NSStringFromCGRect(self.view.bounds));
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
    NSLog(@"bounds = %@", NSStringFromCGRect(self.view.bounds));
}

Итак, вопрос в том, как.. Как я могу получить правильную оценку в самом начале?

4b9b3361

Ответ 1

В соответствии с некоторыми другими ответами проблема, которую вы видите, заключается в том, что viewDidLoad называется до, когда происходит ротация. Поскольку iPad всегда инициализируется в портретном режиме, если вы получаете значения размера в viewDidLoad, они всегда будут размерами портрета - это независимо от настроек, которые вы настроили.

Чтобы получить размер после, ориентация/поворот произойдет, получите значения размера в viewDidAppear.


Я не особо понимаю, почему iOS не справляется с этим лучше - особенно учитывая, что вы определяете ориентацию в настройках проекта и в Xcode Interface Builder. Но, я уверен, что есть веская причина: -).

Ответ 2

Как сделать это правильно

Ваш подкласс UIViewController должен переопределить метод viewWillLayoutSubviews, см. Также здесь.

Когда этот метод вызываются, ViewController view имеет правильный размер, и вы можете внести необходимые коррективы в подобозрение до начала прохода макета над подвидами.

стриж

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()
    NSLog("bounds = \(self.view.bounds)")
}

Obj-C,

- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];
    NSLog(@"bounds = %@", NSStringFromCGRect(self.view.bounds));
}

Документация

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

Как видно из выделенной части, этот метод вызывается каждый раз, когда вид контроллера представления изменяет размер, а также когда вид появляется впервые. Это позволяет вам правильно реагировать на поворот и другие изменения границ

Несколько способов, которые не работают

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

  • viewDidLoad и viewWillAppear - во время этих вызовов view еще не имеет своего окончательного размера. Размер, который вы получите, будет правильным только по чистой случайности, чтобы ввести вас в заблуждение.
  • viewDidAppear - это слишком поздно. Ваш вид уже на экране и виден пользователю. Внесение изменений здесь вызовет видимые изменения/резкие глюки и будет выглядеть любительским. Еще раз, пожалуйста - ради вас, ради меня, ради всех: не делайте этого! Вы лучше этого, и ваши пользователи тоже.
  • UIScreen.mainScreen.bounds.size - это крайне низкий уровень. Вы реализуете UIViewController и размер его представления зависит от того, в какие контроллеры он вложен (навигация, вкладка, пейджинг, любые пользовательские контроллеры и т.д.), Как устройство поворачивается и, возможно, как разделен экран. для многозадачности. Таким образом, хотя вы сможете компенсировать все это и рассчитать окончательный размер вашего представления, вы получите сложный и хрупкий код, который может легко сломаться, если Apple решит изменить какой-либо из этих показателей. UIViewController сделает все это за вас, если вы просто переопределите viewWillLayoutSubviews.

Помимо предоставления неверной информации, эти проблемные подходы не помогут вам с автоповоротом или другими событиями, которые приводят к изменению размера представления контроллера, например жестами многозадачности. Это то, с чем вы действительно хотите справиться.

Поэтому, пожалуйста: будь чемпионом. Делай это правильно. Используйте viewWillLayoutSubviews. Ваша реализация будет требоваться для каждого изменения размера, и ваши пользователи, будущие "я", члены команды и я будем отмечать вас за это. Браво!

Дальнейшие советы

Когда viewWillLayoutSubviews, единственным представлением в вашей иерархии, размер которого будет изменен до его окончательного размера, является viewController.view. Отдать за это в названии метода. Это говорит вам о view… что вы view… (ваше корневое представление контроллера view) …WillLayout… (очень скоро, но это еще не произошло) …Subviews (все остальное в его иерархии под корневым представлением).

Так что макета подпредставления еще не было. У каждого ребенка под корнем еще нет действительного конечного размера. Любая информация о размере, которую вы запрашиваете из дочерних представлений, в лучшем случае будет полностью неверной.

Скорее и, что еще хуже, это будет неверно правильно.

Это то, что вы ожидаете и что вам нужно из-за значения по умолчанию в этом размере и ориентации, или из-за настроек раскадровки. Но это только случайно и не является чем-то, на что вы можете положиться при разных размерах устройства или ориентации.

Если вам нужно layoutSubviews когда конкретное подпредставление меняет размер, и знаете его точный конечный размер, вы должны переопределить метод layoutSubviews этого конкретного подкласса UIView.

Ответ 3

Я всегда использовал:

CGSize sizeOfScreen = [[UIScreen mainScreen] bounds].size;

чтобы получить размер экрана, и:

CGSize sizeOfView = self.view.bounds.size;

чтобы получить размер view. Я только что протестировал его на viewDidLoad и вернулся:

2012-07-17 10:25:46.562 Project[3904:15203] bounds = {768, 1004}

Это правильно, поскольку CGSize определяется как {Ширина, Высота}.

Ответ 4

Это то, что я нашел в моем последнем проекте: рамка self.view будет скорректирована после viewDidLoad в соответствии с тем, если на этом экране есть панель навигации и т.д.

Итак, возможно, вы захотите использовать это значение после viewDidLoad (возможно, в viewWillAppear или viewDidAppear) или отрегулировать его вручную, вычитая высоту баров.

Ответ 5

Я столкнулся с этой проблемой и обнаружил, что получение границ в viewDidAppear работает для моих нужд.

Ответ 6

Вот альтернативное решение (Swift 4):

override func viewDidLoad() {
    super.viewDidLoad()
    self.view.setNeedsLayout()
    self.view.layoutIfNeeded()
}

По сути, это заставляет контроллер представления правильно расположить его, и оттуда вы можете получить правильные кадры для всех ваших подпредставлений. Это особенно помогает при анимации переходов, а ваши контроллеры представления используют autolayout и построитель интерфейса.

Из того, что я заметил, похоже, что начальные кадры установлены в соответствии с тем, какой класс размера по умолчанию установлен в вашем конструкторе интерфейсов. Я обычно редактирую, используя класс размера iPhone XS, поэтому в viewDidLoad кажется, что ширина просмотра всегда равна 375 независимо от того, используете ли вы iPhone XR или нет. Это исправляет себя перед viewWillAppear хотя.

Приведенный выше код исправит эту проблему и позволит вам получить правильные кадры для вашего представления/подпредставлений до того, как контроллер представления будет отображен на экране.