使用AutoLayout,当导航栏消失时,如何将UILabel放在同一个地方?

我有一个UILabel的视图控制器,它打印一些button时,打印一些单词。 点击该button时,导航栏被设置为隐藏。

所以我尝试了UILabel并在Interface Builder中给它提供这些约束:

在这里输入图像说明

但是对于这些,当我按下button时,UILabel随着导航栏的消失而跳下去,然后再次备份,自我纠正,看起来很糟糕。 无论使用什么导航栏,它都应该永久保留。

这是一个直接链接到一个简短的video,显示发生了什么。

我如何最好的去设置它,让UILabel保持原状?

项目: http : //cl.ly/1T2K0V3w1P21

当您告诉导航控制器隐藏导航栏时,会将其内容视图(您的ReadingViewController的视图)调整为全屏,并且内容视图会为新的全屏大小ReadingViewController其子视图。 默认情况下,它会任何animation块之外进行布局,所以新的布局立即生效。

为了解决这个问题,你需要使视图在一个animation块内执行布局。 幸运的是,SDK包含一个隐藏导航栏的animation持续时间的常量,而animation使用线性曲线。 把你的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常量将方法体包装在一个animation块中,所以animation具有正确的持续时间。 另一个变化是我把layoutIfNeeded发送到animation块内部的视图,这样视图就会animation到它们的新帧。

结果如下:

导航栏动画

您也可以使用此animation块通过更改其alpha属性而不是其hidden属性来淡入标签。

UPDATE

针对您评论中的问题:

首先,你需要了解运行循环的各个阶段。 你的应用程序总是在其主线程上运行一个循环。 循环,非常简化,看起来像这样:

 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:放置了一个断点hideControls:点击屏幕,然后在debugging器中查看堆栈跟踪,您会在跟踪中看到PurpleEventCallback方式(正上方__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ )。 这告诉你你正处在事件处理阶段。 (Purple是Apple内部iPhone项目的代码名称。)

如果您看到CA::Transaction::observer_callback ,那么您处于布局阶段或绘制阶段。 进一步的堆栈,你会看到CA::Layer::layout_if_neededCA::Layer::display_if_needed取决于你在哪个阶段。

这就是运行循环及其阶段。 现在,什么时候视图被标记为需要布局? 它在接收到setNeedsLayout时被标记为需要布局。 例如,如果您更改了视图应该显示的内容,并且需要相应地移动或resize,则可以发送此内容。 但是视图会在两种情况下自动发送setNeedsLayoutbounds的大小发生变化(或frame的大小),以及subviews数组发生变化时。

请注意,更改视图的大小或其子视图不会使视图立即布置其子视图! 只是计划在运行循环的布局阶段之后再布置其子视图。

那么…这一切和你有什么关系?

在你的hideControls:方法中,你可以做[self.navigationController setNavigationBarHidden:visible animated:YES] 。 假设visibleNO 。 以下是导航控制器做出的回应:

  • 它开始一个animation块。
  • 它将导航栏的位置设置在屏幕顶部之上。
  • 它将内容视图的高度增加了44个点(导航栏的高度)。
  • 它将内容视图的Y坐标减less了44个点。
  • 它结束了animation块。

对内容视图框架setNeedsLayout的更改会导致内容视图向自己发送setNeedsLayout

请注意,对导航栏框架和内容视图框架的更改是dynamic的。 但内容视图的子视图的框架还没有改变。 这些变化发生在布局阶段之后。

因此,导航控制器将对您的顶级内容视图的更改进行animation处理,但不会为您的内容视图的子视图制作animation更改。 你必须强制这些变化是animation。

您强制这些更改通过两个步骤进行animation:

  1. 您可以创build一个animation块,其参数与导航控制器使用的参数相匹配。
  2. 在该animation块内部,您可以通过将layoutIfNeeded发送到内容视图来强制立即执行布局阶段。

layoutIfNeeded文档说:

使用此方法在绘制之前强制子视图的布局。 从接收器开始,只要superview需要布局,这个方法就会遍历视图层次结构。 然后把那棵树下的整棵树放在那个祖先的下面。

它通过发送layoutSubviews消息到树中的视图,按照从根到叶的顺序来layoutSubviews整个树。 如果您没有使用自动布局,则在将layoutSubviews发送到视图之前,还会应用每个视图的子视图的自动识别遮罩。

因此,通过将layoutIfNeeded发送到您的内容视图,您可以在layoutIfNeeded返回之前立即强制自动布局更新内容视图子视图的框架。 这意味着这些更改发生在您的animation块内部,因此它们的animation使用与导航栏和内容视图更改相同的参数(持续时间和曲线)。

在animation块中放置子视图非常重要,以至于Apple定义了一个animation选项UIViewAnimationOptionLayoutSubviews 。 如果你指定了这个选项,那么在animation块的最后,它会自动发送layoutIfNeeded 。 但是使用这个选项需要使用长版本的消息, animateWithDuration:delay:options:animations:completion:所以通常在块的末尾自己做一个[self.view layoutIfNeeded]就比较容易。

设置一个约束forms,引导和跟踪,一个固定的高度。

(复制我的答案从你张贴的问题被标记为重复: 我有一个UILabel定位在屏幕上的自动布局,但是当我隐藏导航栏会导致标签“抽搐”一秒钟 )

您可以尝试从标签(在常量中为22)中定义超级视图的顶部空间约束,将其作为IBOutlet连接到视图属性,并在隐藏导航栏时为其添加animation或所示。

例如,我声明顶部空间属性为topSpaceConstraint:

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

然后在hideControls方法里面,我可以animation约束:

 - (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; }