Dou*_*ith 2 objective-c uiview uilabel ios autolayout
我有一个带有UILabel的视图控制器,当按下按钮时会打印一些单词.点击按钮时,导航栏设置为隐藏.
所以我尝试使用UILabel并在Interface Builder中给出这些约束:

但是有了这些,当我按下按钮时,UILabel跳下来,导航栏消失,然后再次备份,纠正自己,看起来很糟糕.无论导航栏发生什么,它都应永久保留在原位.
我怎样才能最好地设置它以便UILabel保持原位?
当您告诉导航控制器隐藏导航栏时,它会将其内容视图(您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属性来淡入和淡出标签.
回答您评论中的问题:
首先,您需要了解运行循环的各个阶段.您的应用程序始终在其主线程上运行循环.非常简化的循环看起来像这样:
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_needed或CA::Layer::display_if_needed取决于你在哪个阶段.
这就是运行循环及其阶段.现在,视图何时被标记为需要布局?它在收到时被标记为需要布局setNeedsLayout.例如,如果您更改了视图应显示的内容并且需要相应地移动或调整其大小,则可以发送此内容.但是视图将setNeedsLayout在两种情况下自动发送:当其bounds更改的大小(或其大小frame)以及其subviews数组发生更改时.
请注意,更改视图的大小或其子视图不会使视图立即显示其子视图!它只是安排在运行循环的布局阶段之后布置其子视图.
那么......这一切与你有什么关系?
在你的hideControls:方法中,你做到了[self.navigationController setNavigationBarHidden:visible animated:YES].假设visible是NO.以下是导航控制器的响应:
对内容视图框架的更改会导致内容视图自行发送setNeedsLayout.
请注意,导航栏框架和内容视图框架的更改是动画的.但内容视图的子视图的框架尚未更改.在布局阶段,这些更改将在稍后发生.
因此,导航控制器会将更改设置为您的顶级内容视图的动画,但不会对内容视图的子视图所做的更改设置动画.您必须强制将这些更改设置为动画.
您可以通过以下两个步骤强制对这些更改进行动画处理:
layoutIfNeeded.该layoutIfNeeded文件说,这:
使用此方法在绘制之前强制子视图的布局.从接收者开始,只要superviews需要布局,此方法就会向上遍历视图层次结构.然后它展示了祖先下面的整棵树.
它通过layoutSubviews从树根到叶子的顺序向树中的视图发送消息来布局整个树.如果您没有使用自动布局,它还会在发送layoutSubviews到视图之前应用每个视图的子视图的自动调整遮罩.
因此,通过发送layoutIfNeeded到您的内容视图,您将强制自动布局在layoutIfNeeded返回之前立即更新内容视图的子视图的帧.这意味着这些更改发生在动画块内,因此它们使用与导航栏和内容视图的更改相同的参数(持续时间和曲线)进行动画处理.
在动画块中布置子视图非常重要,Apple定义了一个动画选项UIViewAnimationOptionLayoutSubviews.如果指定此选项,则在动画块结束时,它将自动发送layoutIfNeeded.但是使用该选项需要使用消息的长版本animateWithDuration:delay:options:animations:completion:,因此通常[self.view layoutIfNeeded]在块结束时更容易做到.