iOS AutoLayout - получить ширину размера кадра

Я разрабатываю с использованием автоматического макета iOS 6

Я хотел бы зарегистрировать сообщение, отображающее ширину кадра представления.

Я вижу textView на экране.

Но я получаю ширину и высоту равными нулю, я что-то упустил?

NSLog(@"textView    = %p", self.textView);
NSLog(@"height      = %f", self.textView.frame.size.height);
NSLog(@"width       = %f", self.textView.frame.size.width);

textView    = 0x882de00
height      = 0.000000
width       = 0.000000

person user1046037    schedule 21.09.2012    source источник


Ответы (9)


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

Изменить: этот ответ указывает, почему сценарий в вопросе не работает. ответ @dreamzor указывает, где разместить код, чтобы решить эту проблему.

person Jesper    schedule 21.09.2012
comment
Я попытался зарегистрировать сообщение в viewDidLoad. Какой метод вызывается после завершения автоматической компоновки. Причина, по которой я спрашиваю, заключается в том, что я хочу рассчитать ширину до того, как пользователь сможет взаимодействовать с textView. Так что регистрация описания объекта может не помочь, потому что я хочу использовать ширину для каких-то других вычислений. - person user1046037; 21.09.2012
comment
Нет, viewDidLoad происходит до автоматической компоновки. Автоматическая компоновка происходит лениво после того, как представление было добавлено в другое представление (что происходит вскоре после вызова viewWillAppear, но, вероятно, ненадежно до viewDidAppear). Единственный надежный способ узнать это — узнать, когда layoutSubviews завершится в представлении контроллера представления. - person Jesper; 21.09.2012
comment
Почему вы пытаетесь получить кадр в любом случае? Если вы хотите выполнять расчеты на его основе и делать макет самостоятельно, вы, вероятно, в конечном итоге столкнетесь с автоматической компоновкой. - person Jesper; 21.09.2012
comment
Я пытался подсчитать количество букв, которые могут поместиться в UITextView, чтобы обрезать его и придать ему... вид. - person user1046037; 21.09.2012
comment
Поскольку вы не можете сделать это, пока кто-то вводит текст, используйте UILabel с lineBreakMode для одного из значений NSLineBreakByTruncating... и установите свои ограничения, чтобы метка всегда имела ту же ширину, высоту и положение, что и текстовое представление. - person Jesper; 21.09.2012
comment
Спасибо, Джаспер, но единственная проблема с использованием UILabel заключается в том, что когда в UILabel есть только одна строка текста, текст отображается в вертикальном центре (высота / 2), а не в верхней части метки. - person user1046037; 21.09.2012
comment
Очень ценю вашу помощь в этом. Для полноты, пожалуйста, можете ли вы обновить свой ответ, указав причину, по которой ширина кадра равна нулю, потому что viewDidLoad происходит до автоматического макета. Так что я могу отметить его как ответ. - person user1046037; 21.09.2012
comment
Отредактировано, чтобы соответствовать тому, в чем была проблема, и удачи в решении вашей проблемы. - person Jesper; 25.09.2012
comment
Йеспер, большое спасибо за подсказки, viewDidAppear кажется, происходит после автоматической компоновки. Если есть лучший подход, дайте мне знать. - person user1046037; 28.09.2012

На самом деле, приведенные выше ответы не совсем правильные. Я следовал за ними и снова и снова получал нули.

Хитрость заключается в том, чтобы поместить ваш код, зависящий от фрейма, в метод viewDidLayoutSubviews, который

Уведомляет контроллер представления о том, что его представление только что выложило свои подпредставления.

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

Надеюсь, это будет полезно для кого-то.

Просто хотел добавить, что для моей программы без ландшафтного режима, НЕ использующего Auto Layout, намного проще... Я пытался, хотя =D

person dreamzor    schedule 03.01.2013
comment
По опыту, это самый ранний обратный вызов после того, как autolayout выполнит свою работу. Я рекомендую использовать, например, BOOL, чтобы поймать первый раз, когда это вызывается, и выполнить логику, зависящую от кадра. Это не очень элегантно, но выполняет свою работу до тех пор, пока не появятся правильные обратные вызовы для конкретных ограничений автомакета. - person ArkReversed; 16.07.2013
comment
@dreamzor, как узнать, последний ли это viewDidLayoutSubviews? Я обнаружил, что в iOS 7 перед viewDidAppear будет вызываться только один viewDidLayoutSubviews, но в iOS 8 я вижу несколько viewDidLayoutSubviews перед viewDidAppear. Это раздражает, потому что первый viewDidLayoutSubviews даст неправильный размер кадра на iOS 8, поэтому я должен игнорировать его в первый раз. - person Wingzero; 02.04.2015
comment
@Wingzero В моем проекте пятый раз правильный, так как я могу игнорировать другие? - person Elijah_Lam; 07.06.2015
comment
@Elijah_Lam Я думаю, ты не можешь. это полностью системное поведение, и вы не можете его контролировать. Пока iOS считает, что представление нуждается в макете, оно активирует его. - person Wingzero; 08.06.2015
comment
@Wingzero Какое у тебя наконец решение? - person Elijah_Lam; 08.06.2015
comment
это работа вокруг, я думаю. Мне нужно поместить некоторый код, которому нужна информация о кадре, в viewDidAppear или в viewDidLayoutSubViews, я просто принимаю любой кадр, хотя это пустая трата времени. - person Wingzero; 08.06.2015
comment
Вероятно, следует отметить, что если вы используете [viewDidLayoutSubviews] для (синхронного) обновления представления, эффекты будут видны в последующей анимации перехода от-представления-контроллера-Foo-к-представлению-контроллера-Bar, если таковые имеются. Если вы подождете до [viewDidAppear:] с обновлением, будет неожиданно визуализирована уже анимация перехода контроллера представления. - person Gary; 26.07.2016
comment
Добавьте if yourView.bounds != .zero, если границы вашего представления все еще nil при использовании viewDidLayoutSubviews. Ваше мнение может быть не первым, что выложено. - person Anro Swart; 03.07.2019

Включите в свой viewDidLoad()

self.view.setNeedsLayout()
self.view.layoutIfNeeded()

Перед доступом к yourview.frame.size.width

person diegosantiviago    schedule 04.03.2015
comment
Я думаю лучший ответ! - person Murat Yasar; 07.02.2019

Решение

  • Поместите этот код в viewDidAppear

Причина

  • viewDidLoad происходит до завершения автомакета. Таким образом, позиция еще не установлена ​​​​автоматической разметкой, указанной в xib.
  • viewDidAppear происходит после завершения автомакета.
person user1046037    schedule 28.09.2012
comment
Я поместил код в viewDidAppear и, тем не менее, NSLog кадра моего представления показывает нули. - person dreamzor; 03.01.2013
comment
На самом деле viewDidAppear вызывается перед viewDidLayoutSubviews, как и для iOS 6.0, поэтому dreamzor прав. - person vedrano; 21.02.2013
comment
В iOS 6, основываясь на моем тестировании, я использовал контроллер навигации во втором контроллере, viewDidLayoutSubviews вызывался перед viewDidAppear, но в viewDidLayoutSubviews ширина кадра подпредставления остается равной нулю. Только в viewDidAppear ширина кадра подпредставления имеет правильное значение. - person user1046037; 21.02.2013

На самом деле мне удалось принудительно обновить макет перед моим кодом в viewDidLoad:

override func viewDidLoad() {
        super.viewDidLoad()

        println("bounds before \(self.previewContainer.bounds)");
        //on iPhone 6 plus -> prints bounds before (0.0,0.0,320.0,320.0)

        self.view.setNeedsLayout()
        self.view.layoutIfNeeded()

        println("bounds after \(self.previewContainer.bounds)");
        //on iPhone 6 plus -> prints bounds after (0.0,0.0,414.0,414.0)

        //Size dependent code works here
        self.create()
    }

ОБНОВЛЕНИЕ: это больше не работает

person Francescu    schedule 18.12.2014

Это действительно странная особенность. Но я нашел:

Если вы хотите получить кадр без использования метода layoutsubviews, используйте этот:

dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"View frame: %@", NSStringFromCGRect(view.frame));
    });

Это действительно странно!!!

person SamSol    schedule 29.12.2015

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

viewDidLayoutSubviews должен быть правильным местом для запуска кода, который зависит от завершения автомакета, но, как указывали другие, он вызывается несколько раз в последних версиях iOS, и вы не можете знать, какой вызов является последним. .

Поэтому я решил это с помощью небольшого взлома. В моей раскадровке mySubview должно быть меньше, чем self.view. Но когда viewDidLayoutSubviews вызывается впервые, mySubview по-прежнему имеет ширину 600, в то время как self.view кажется установленным правильно (это проект для iPhone). Так что все, что мне нужно сделать, это отслеживать последующие вызовы и проверять относительную ширину. Когда mySubview меньше, чем self.view, я могу быть уверен, что он расположен правильно.

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    if self.mySubview.bounds.size.width < self.view.bounds.size.width {

        // mySubview's width became less than the view's width, so it is
        // safe to assume it is now laid out correctly...

    }
}

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

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

person Echelon    schedule 04.11.2015

Узнать размер можно только после первого прохода макета. Или просто вызовите метод ниже, после того как вы получите фактическую ширину вашего просмотра.

[yourView layoutIfNeeded];
person Bhavesh Patel    schedule 08.07.2017

Я согласен с решением пользователя 1046037

Если вы одновременно используете руководства atuoLayout и код, вам лучше поместить программирование фреймов (или NSlayoutConstraints) в ViewDidAppear, особенно при разработке пользовательского интерфейса для нескольких устройств.

Основываясь на моих тестах, iOS14 будет брать автомакеты с экрана (по умолчанию iPhone 11 Pro Max) до ViewDidLoad и обновлять их в ViewDidAppear.

Для людей, которые не знакомы с циклом просмотра, его загрузка из раскадровки -> ViewDidLoad -> ViewWillAppear -> ViewDidAppear.

person Johnhappily    schedule 09.11.2020