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

时间:2013-06-13 23:30:50

标签: ios objective-c uiview uilabel autolayout

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

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

enter image description here

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

Here's a direct link to a short video showing what happens.

我如何最好地设置它以便UILabel保持原位?

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

3 个答案:

答案 0 :(得分: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];
    }];
}

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

结果如下:

navigation bar animation

您还可以使用此动画块通过更改其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__之上。这告诉您,您处于事件处理阶段。 (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发送到内容视图来强制布局阶段立即
  3. layoutIfNeeded documentation说:

      

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

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

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

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

答案 1 :(得分:1)

从底部,引线和轨迹设置约束,将一个约束设置为固定高度。

答案 2 :(得分:1)

(从我发布的标记为重复的问题中复制我的答案:I have a UILabel positioned on the screen with autolayout, but when I hide the navigation bar it causes the label to "twitch" for a second

您可以尝试从标签(常量中为22)定义顶视图的顶部空间约束,将其作为IBOutlet连接到视图属性,并在导航栏中为其设置动画,而不是底部空间约束。隐藏或显示。

例如,我将顶部空间属性声明为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;
}