【问题标题】:How to animate addSubView: using Autolayout如何为 addSubView 设置动画:使用自动布局
【发布时间】:2015-01-13 16:46:59
【问题描述】:

设置:在屏幕上的UIViewController 内有一个containerView(为简单起见,假设containerView 占据整个屏幕)。

问题:创建overlayView 并将其添加为containerView 的子视图,并对外观进行动画处理,使其从右侧动画到某个位置,作为对用户操作的响应。

UIView *overlayView = [[UIView alloc] initWithFrame:containerView.frame];
overlayView.translatesAutoresizingMaskIntoConstraints = NO;
[containerView addSubview:overlayView]; // Can't add constraints before inserting into view hierarchy

方法:将overlayView 的前缘与containerView 的前缘联系起来,我称之为overlayLeadingConstraint 的约束。将overlayLeadingConstraint.constant 设置为containerView 的宽度,以便它最初位于屏幕右侧之外。

NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|[overlayView(width)]" options:0 metrics:metrics views:views];
[containerView addConstraints:constraints];
NSLayoutConstraint *overlayViewLeadingConstraint = [constraints objectAtIndex:0];
overlayViewLeadingConstraint.constant = containerView.frame.size.width;
// Height constraint not shown for simplicity

动画:现在进入真正的问题;从右侧将overlayView 设置为动画。第一种方法是这样的:

overlayViewLeadingConstraint.constant = 0.0;
[UIView animateWithDuration:1.0 animations:^{
    [containerView layoutIfNeeded];
}];

但这不起作用,因为上面的所有代码都会在同一个运行循环上执行,因此只会显示最终结果。

第二种方法:尝试将动画推迟到未来的运行循环,直到addSubview: 之后的初始布局已经发生。

dispatch_async(dispatch_get_main_queue(), ^{
    overlayViewLeadingConstraint.constant = 0.0;
    [UIView animateWithDuration:1.0 animations:^{
        [containerView layoutIfNeeded];
    }];
});

这也不起作用,它暗示addSubview: 和约束的设置可能会占用多个运行循环。

第三种方法:尝试进一步延迟动画:未来的几个运行循环。

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    overlayViewLeadingConstraint.constant = 0.0;
    [UIView animateWithDuration:1.0 animations:^{
        [containerView layoutIfNeeded];
    }];
}];

时间延迟很小,但它允许在动画之前完成几个运行循环,这似乎达到了预期的效果。

问题: 上述方法似乎是一种解决方法,而不是 真正的 解决方案。所以我想知道是否有更好的方法来解决这个问题。我曾考虑使用托管viewControllerviewDidLayoutSubviews 方法来了解overlayView 何时到位并且可以启动动画,但文档明确建议不要这样做:

但是,调用此方法并不表示各个布局 视图的子视图已调整。每个子视图负责 调整自己的布局。

我开始认为 Apple 的想法是在初始化时添加所有子视图,并立即隐藏那些您不需要的。因此,当需要为 subView 设置动画时,它已经是与约束正确绑定的视图层次结构的成员。

你会怎么做?非常感谢任何意见。

【问题讨论】:

    标签: ios objective-c autolayout


    【解决方案1】:

    我不确定你对一些你没有显示的东西做了什么,但这段代码对我来说很好用。我在 IB (containerView) 中做了一个 200 x 200 的视图,并使用了这段代码,

     - (void)viewDidAppear:(BOOL)animated {
        [super viewDidAppear:animated];
        UIView *overlayView = [UIView new];
        overlayView.backgroundColor = [UIColor redColor];
        overlayView.translatesAutoresizingMaskIntoConstraints = NO;
        [containerView addSubview:overlayView];
        NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|[overlayView]|" options:0 metrics:nil views:@{@"overlayView":overlayView}];
        NSArray *constraints2 = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[overlayView]|" options:0 metrics:nil views:@{@"overlayView":overlayView}];
        [containerView addConstraints:constraints];
        [containerView addConstraints:constraints2];
        NSLayoutConstraint *overlayViewLeadingConstraint = [constraints objectAtIndex:0];
        overlayViewLeadingConstraint.constant = containerView.frame.size.width;
        [containerView layoutIfNeeded];
    
        overlayViewLeadingConstraint.constant = 0.0;
        [UIView animateWithDuration:1.0 animations:^{
            [containerView layoutIfNeeded];
        }];
    }
    

    这动画正确。请注意,我在动画块之前调用了 layoutIfNeeded。在您的情况下,这可能是必要的,也可能不是必要的,具体取决于您未显示的代码的某些方面。

    不知道用[constraints objectAtIndex:0]获取要修改的约束是否安全;它在这种情况下有效,但我不知道是否保证了使用视觉格式设置的约束的顺序。

    【讨论】:

    • 感谢您的意见。对layoutIfNeeded 的第一次调用是解决我问题的关键。有趣的是,既然你已经展示了它,它就非常有意义,但我从来没有想过要这样做。再次感谢!
    • 你说得对,objectAtIndex: 方法不安全。然而,视觉格式更好地传达了这个想法,所以为了这个例子,它更好地满足了我的目的。不过,我将替换发货版本中的视觉格式。
    【解决方案2】:

    您尚未向animations 块添加任何动画。 animations 块通常被认为是“我希望我的 UI 最终处于这种状态;动画东西来实现它”块。

    [UIView animateWithDuration:1.0 animations:^{
        overlayViewLeadingConstraint.constant = 0.0;
    }];
    

    【讨论】:

    • 感谢您的输入,尽管我认为推荐的方法是先修改约束,然后在动画块中简单地调用layoutIfNeeded。将不断更新放在动画块中会产生相同的效果,但对 layoutIfNeeded 的调用是触发动画的原因,因此您的示例中缺少。
    • 你错了。正如我所说,如果您不在 animations 块内执行值更改,它们将不会被动画化。
    • 不,伊恩,你错了。我已经完成了许多动画,将约束更改放在动画块之外(但在具有 animateWithDuration 调用的同一方法内),并且它工作正常(并且您确实需要在动画块内调用 layoutIfNeeded,正如 kadam 指出的那样)。
    猜你喜欢
    • 1970-01-01
    • 2015-01-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多