Используя AutoLayout, как мне сохранить UILabel в том же месте, когда исчезает панель навигации?

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

Поэтому я попытался взять UILabel и задать ему следующие ограничения в Интерфейсном Разработчике:

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

Вот'Прямая ссылка на короткое видео, показывающее, что происходит.

Как мне лучше настроить его так, чтобы UILabel оставалась на месте?

Проект:http://cl.ly/1T2K0V3w1P21

 Doug Smith14 июн. 2013 г., 13:21
Разве это не то, что является третьим ограничением на картинке?
 CodaFi14 июн. 2013 г., 01:52
Вы можете установить жесткое ограничение от нижней части метки до нижней части экрана.

Ответы на вопрос(3)

(Копирую мой ответ с вопроса, который вы разместили и который помечен как дубликат:У меня есть UILabel, расположенная на экране с авторазметкой, но когда я скрываю панель навигации, это заставляет ярлык "дергаться» на секунду)

Вместо ограничения нижнего пространства вы можете попытаться определить ограничение верхнего пространства для суперпредставления по метке (которая равна 22 в константе), подключить его как IBOutlet к свойству представления и анимировать его, когда панель навигации скрыта или показано на рисунке.

Например, я объявляю свойство top space как topSpaceConstraint:

@property (weak, nonatomic) IBOutlet NSLayoutConstraint *topSpaceConstraint;

Затем внутри метода hideControls я могу анимировать ограничение:

- (void)hideControls:(BOOL)visible {
    if (visible) {
        [UIView animateWithDuration:UINavigationControllerHideShowBarDuration animations:^{
            self.topSpaceConstraint.constant = 66; //44 is the navigation bar height, you need to find a way not to hardcode this
            [self.view layoutIfNeeded];
        }];     
    }
    else {
        [UIView animateWithDuration:UINavigationControllerHideShowBarDuration animations:^{
            self.topSpaceConstraint.constant = 22;
            [self.view layoutIfNeeded];
        }];
    }
    [self.navigationController setNavigationBarHidden:visible animated:YES];
    self.backFiftyWordsButton.hidden = visible;
    self.forwardFiftyWordsButton.hidden = visible;
    self.WPMLabel.hidden = visible;
    self.timeRemainingLabel.hidden = visible;
}

Установите ограничение от дна, поводка и следа и одно на фиксированную высоту.

 matehat26 июн. 2013 г., 22:56
Это'часть сNSAutoresizingMaskLayoutConstraint, вам нужно установить UILabel'stranslatesAutoresizingMaskIntoConstraint собственность на.false
 Andrea14 июн. 2013 г., 16:38
Это означает, что где-то есть ненужное ограничение, оно включает метку. количество констант должно быть равно 4
 Andrea14 июн. 2013 г., 15:01
opss .. поэтому установите верхнее ограничение вместо фиксированной высоты, и контролируйте, чтобы метка была установлена для рисования текста в середине
 Doug Smith14 июн. 2013 г., 13:21
Разве это не то, что является третьим ограничением на картинке?
 Doug Smith14 июн. 2013 г., 15:59
Не могли бы вы быть более конкретным? Я добавил ограничение сверху и удалил высоту (теперь это выглядит так:i.imgur.com/TDjkw1u.png) но я получаю эту ошибку каждый раз, когда контроллер представления загружается: "<NSLayoutConstraint: 0x865dbe0 V: [UILabel: 0x865b450 (76)]> "," <NSLayoutConstraint: 0x8669dc0 V: | - (22) - [UILabel: 0x865b450] (имена: '|': UIView: 0x8666220)> "," <NSLayoutConstraint: 0x8669d80 V: [UILabel: 0x865b450] - (318) - | (Имена: '|': UIView: 0x8666220)> "," <NSAutoresizingMaskLayoutConstraint: 0x866c280 h = - & v = - & В: [UIView: 0x8666220 (460)]>»
Решение Вопроса

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

Чтобы исправить это, вам нужно заставить представление выполнять макет внутри блока анимации. К счастью, SDK содержит константу продолжительности анимации, которая скрывает панель навигации, а анимация использует линейную кривую. Измени свойhideControls: метод к этому:

- (void)hideControls:(BOOL)visible {
    [UIView animateWithDuration:UINavigationControllerHideShowBarDuration animations:^{
        [self.navigationController setNavigationBarHidden:visible animated:YES];
        self.backFiftyWordsButton.hidden = visible;
        self.forwardFiftyWordsButton.hidden = visible;
        self.WPMLabel.hidden = visible;
        self.timeRemainingLabel.hidden = visible;
        [self.view layoutIfNeeded];
    }];
}

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

Вот's результат:

Вы также можете использовать этот блок анимации для постепенного исчезновения надписей, изменяя ихalpha свойства вместо ихhidden свойства.

ОБНОВИТЬ

В ответ на вопросы в вашем комментарии:

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

while (1) {

    wait for an event (touch, timer, local or push notification, etc.)

    Event phase: dispatch the event as appropriate (this often ends up
        calling into your code, for example calling your tap recognizer's action)

    Layout phase: send `layoutSubviews` to every view in the on-screen
        view hierarchy that has been marked as needing layout

    Draw phase: send `drawRect:` to any view that has been marked as needing
        display (because it's a new view or it received `setNeedsDisplay` or
        it has `UIViewContentModeRedraw`)

}

Например, если вы установите точку останова вhideControls:коснитесь экрана, а затем посмотрите на трассировку стека в отладчике, выя увижуPurpleEventCallback вниз в след (прямо выше__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__). Это говорит вам, что вына этапе обработки событий. (Фиолетовым было кодовое название проекта iPhone в Apple.)

Если ты видишьCA::Transaction::observer_callback, вы'находится либо в фазе макета, либо в фазе рисования. Дальше вверх по стеку выпосмотрим либоCA::Layer::layout_if_needed или жеCA::Layer::display_if_needed в зависимости от того, на каком этапе выре в.

Чтобы'S цикл выполнения и его фазы. Теперь, когда представление помечается как нужное расположение? Он получает помеченный как нуждающийся макет при полученииsetNeedsLayout, Вы можете отправить это, если, например, выВы изменили контент, который должны отображаться в ваших представлениях, и они должны быть соответственно перемещены или изменены. Но вид отправит самsetNeedsLayout автоматически в двух случаях: когда размер егоbounds изменения (или размер егоframe), и когда егоsubviews изменения массива.

Обратите внимание, что изменение видаРазмер s или его подпредставления не заставляют представление немедленно выложить его подпредставления! Это'Просто запланировано выложить свои подпредставления позже, на этапе макета цикла выполнения.

Итак ... какое это имеет отношение к тебе?

В вашемhideControls: метод, вы делаете[self.navigationController setNavigationBarHidden:visible animated:YES], предполагатьvisible являетсяNO, Вот's что контроллер навигации делает в ответ:

Начинается анимационный блок.Он устанавливает положение панели навигации выше верхней части экрана.Увеличивает высоту просмотра содержимого на 44 пункта (высота панели навигации).Уменьшает координату Y представления содержимого на 44 пункта.Заканчивается блок анимации.

Изменения в представлении содержимого »s кадр заставляет представление контента отправлять себя.setNeedsLayout

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

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

Вы заставляете эти изменения быть анимированными, делая два шага:

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

layoutIfNeeded документация говорит это:

Используйте этот метод для принудительной компоновки подпредставлений перед рисованием. Начиная с получателя, этот метод перемещается вверх по иерархии представлений, пока суперпредставления требуют компоновки. Затем он раскладывает все дерево под этим предком.

Выкладывает все дерево, отправивlayoutSubviews сообщения в представлениях в дереве, в порядке от корня до листьев. Если ты'не использует автоматическое расположение, оно также применяет маску автоматического изменения размера каждого видас подпредставлениями перед отправкойlayoutSubviews на вид.

Итак, отправивlayoutIfNeeded что касается просмотра содержимого, вы заставляете автоматическую компоновку обновлять кадры представления содержимого. 'с подвидом немедленно, доlayoutIfNeeded возвращается. Это означает, что эти изменения происходят внутри вашего блока анимации, поэтому они анимируются с теми же параметрами (длительностью и кривой), что и изменения на панели навигации и представлении вашего контента.

Размещение подпредставлений в блоке анимации настолько важно, что Apple определила параметр анимации,UIViewAnimationOptionLayoutSubviews, Если вы укажете эту опцию, то в конце блока анимации она автоматически отправитlayoutIfNeeded, Но использование этой опции требует использования длинной версии сообщения,animateWithDuration:delay:options:animations:completion:, так что'обычно проще сделать[self.view layoutIfNeeded] себя в конце квартала.

 rob mayoff27 июн. 2013 г., 06:58
Я обновил свой ответ.
 Doug Smith28 июн. 2013 г., 00:56
Вау, это было невероятно ясно, спасибо. Мой единственный вопрос, который остается: что было причиной скачка в первую очередь? Движение просмотра контента? И является ли это причиной того, что все это происходит в том блоке, который требует изменения макета в конце?
 rob mayoff28 июн. 2013 г., 01:21
Скачок был вызван, когда представление контента выложило свои подпредставления. Помните, что этикеткаПроисхождение s Y пришлось увеличить на 44 балла, потому что просмотр содержимого стал на 44 балла выше. Это изменение на этикеткекадр был за пределами любого анимационного блока, поэтомут анимированные. Между тем изменения в представлении контента »кадр был анимирован. Таким образом, ярлык упал на 44 пункта сразу. Представление контента двигалось вверх на 44 пункта, что уравновешивает представление ярлыка.движение, но просмотр контентаДвижение было оживленным.
 Doug Smith27 июн. 2013 г., 02:48
Вау, этоэто фантастика, большое спасибо. Несколько вопросов, хотя, если вы неt возражаю: я запустил симулятор в замедленном режиме, и, похоже, моя проблема заключалась в том, что метка сразу переместилась в позицию, а затем медленно (рядом со скрытой панелью навигации) переместилась в новую позицию. Как это входит в игру, когда вы говорите это?вне блока анимации? (И как это можно исправить, добавив его в блок анимации?) И не могли бы вы объяснить layoutIfNeeded, I 'У меня действительно есть проблемы с пониманием того, что он делает. Еще раз большое спасибо за удивительный ответ.

Ваш ответ на вопрос