如何使用自动布局来实现addSubView的动画效果

3

安装: 在一个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并启动动画,但文档明确建议不要这样做:

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

我开始认为苹果的想法是在初始化时添加所有子视图,并隐藏那些您不需要立即使用的子视图。因此,当到达动画子视图的时间时,它将已经成为具有约束的视图层次结构的成员。

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

2个回答

6

我不确定你对于一些未显示的内容所做的操作,但是这段代码对我来说运行良好。我在IB中创建了一个大小为200 x 200的视图(containerView),并使用了以下代码:

 - (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 是解决我的问题的关键。有趣的是,现在你已经展示了它,它变得非常合理,但我从未想过这样做。再次感谢! - kadam
关于objectAtIndex:方法不安全的问题,你说得没错。但是视觉格式更能传达想法,所以为了例子的完整性,它更符合我的目的。不过在最终版本中我会替换掉视觉格式。 - kadam

1
你还没有在要进行动画的 animations 块中添加任何内容。通常认为 animations 块是“我希望我的用户界面最终呈现这种状态;通过动画来实现”的块。
[UIView animateWithDuration:1.0 animations:^{    overlayViewLeadingConstraint.constant = 0.0;}];

1
感谢您的输入,尽管我认为推荐的方法是先修改约束,然后在动画块中简单调用layoutIfNeeded。将常量更新放在动画块内会产生相同的效果,但调用layoutIfNeeded是触发动画的关键,而这在您的示例中缺失了。 - kadam
你是错误的。正如我所说,如果你不在“animations”块内执行值的更改,它们将不会被动画化。 - Ian MacDonald
1
不,Ian,你错了。我做过很多动画,将约束的改变放在动画块之外(但在具有animateWithDuration调用的同一个方法内),它可以正常工作(正如kadam指出的那样,你确实需要在动画块内调用layoutIfNeeded)。 - rdelmar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接