使用AutoLayout,当导航栏消失时,如何将UILabel保留在同一位置?

Dou*_*ith 2 objective-c uiview uilabel ios autolayout

我有一个带有UILabel的视图控制器,当按下按钮时会打印一些单词.点击按钮时,导航栏设置为隐藏.

所以我尝试使用UILabel并在Interface Builder中给出这些约束:

在此输入图像描述

但是有了这些,当我按下按钮时,UILabel跳下来,导航栏消失,然后再次备份,纠正自己,看起来很糟糕.无论导航栏发生什么,它都应永久保留在原位.

这是一个直接链接到一个简短的视频,显示发生了什么.

我怎样才能最好地设置它以便UILabel保持原位?

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

rob*_*off 8

当您告诉导航控制器隐藏导航栏时,它会将其内容视图(您ReadingViewController的视图)的大小调整为全屏,内容视图会为新的全屏大小显示其子视图.默认情况下,它任何动画块之外执行此布局,因此新布局会立即生效.

要修复它,您需要使视图在动画块内执行布局.幸运的是,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];
    }];
}
Run Code Online (Sandbox Code Playgroud)

这里有两个变化.一个是我使用UINavigationControllerHideShowBarDuration常量将方法体包装在动画块中,因此动画具有正确的持续时间.另一个变化是我发送layoutIfNeeded到动画块内部的视图,因此视图将动画显示其新帧.

这是结果:

导航栏动画

您还可以使用此动画块通过更改其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`)

}
Run Code Online (Sandbox Code Playgroud)

例如,如果你输入一个断点hideControls:,点击屏幕,然后查看调试器中的堆栈跟踪,你会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.例如,如果您更改了视图应显示的内容并且需要相应地移动或调整其大小,则可以发送此内容.但是视图将setNeedsLayout在两种情况下自动发送:当其bounds更改的大小(或其大小frame)以及其subviews数组发生更改时.

请注意,更改视图的大小或其子视图不会使视图立即显示其子视图!它只是安排在运行循环的布局阶段之后布置其子视图.

那么......这一切与你有什么关系?

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

  • 它开始一个动画块.
  • 它将导航栏的位置设置为屏幕顶部上方.
  • 它将内容视图的高度增加44点(导航栏的高度).
  • 它将内容视图的Y坐标减少44个点.
  • 它结束了动画块.

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

请注意,导航栏框架和内容视图框架的更改是动画的.但内容视图的子视图的框架尚未更改.在布局阶段,这些更改将在稍后发生.

因此,导航控制器会将更改设置为您的顶级内容视图的动画,但不会对内容视图的子视图所做的更改设置动画.您必须强制将这些更改设置为动画.

您可以通过以下两个步骤强制对这些更改进行动画处理:

  1. 您可以创建一个动画块,其参数与导航控制器使用的参数相匹配.
  2. 在该动画块中,您可以通过发送到内容视图来强制立即发生布局阶段layoutIfNeeded.

layoutIfNeeded文件说,这:

使用此方法在绘制之前强制子视图的布局.从接收者开始,只要superviews需要布局,此方法就会向上遍历视图层次结构.然后它展示了祖先下面的整棵树.

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

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

在动画块中布置子视图非常重要,Apple定义了一个动画选项UIViewAnimationOptionLayoutSubviews.如果指定此选项,则在动画块结束时,它将自动发送layoutIfNeeded.但是使用该选项需要使用消息的长版本animateWithDuration:delay:options:animations:completion:,因此通常[self.view layoutIfNeeded]在块结束时更容易做到.