我正在更新一个带有AdBannerView的旧应用程序,当没有广告时,它就会滑出屏幕。当有广告时,它就会在屏幕上滑动。基本的东西。

旧的风格,我设置帧在一个动画块。 新样式,我有一个IBOutlet到自动布局约束,它决定了Y的位置,在这种情况下,它是从父视图底部的距离,并修改常数:

- (void)moveBannerOffScreen {
    [UIView animateWithDuration:5 animations:^{
        _addBannerDistanceFromBottomConstraint.constant = -32;
    }];
    bannerIsVisible = FALSE;
}

- (void)moveBannerOnScreen {
    [UIView animateWithDuration:5 animations:^{
        _addBannerDistanceFromBottomConstraint.constant = 0;
    }];
    bannerIsVisible = TRUE;
}

横幅的移动和预期的完全一样,但是没有动画。


更新:我重新看了WWDC 12演讲《掌握自动布局的最佳实践》,其中包括动画。它讨论了如何使用CoreAnimation更新约束:

我尝试了下面的代码,但得到完全相同的结果:

- (void)moveBannerOffScreen {
    _addBannerDistanceFromBottomConstraint.constant = -32;
    [UIView animateWithDuration:2 animations:^{
        [self.view setNeedsLayout];
    }];
    bannerIsVisible = FALSE;
}

- (void)moveBannerOnScreen {
    _addBannerDistanceFromBottomConstraint.constant = 0;
    [UIView animateWithDuration:2 animations:^{
        [self.view setNeedsLayout];
    }];
    bannerIsVisible = TRUE;
}

另一方面,我已经检查了很多次,这是在主线程上执行的。


当前回答

两个重要注意事项:

You need to call layoutIfNeeded within the animation block. Apple actually recommends you call it once before the animation block to ensure that all pending layout operations have been completed You need to call it specifically on the parent view (e.g. self.view), not the child view that has the constraints attached to it. Doing so will update all constrained views, including animating other views that might be constrained to the view that you changed the constraint of (e.g. View B is attached to the bottom of View A and you just changed View A's top offset and you want View B to animate with it)

试试这个:

objective - c

- (void)moveBannerOffScreen {
    [self.view layoutIfNeeded];

    [UIView animateWithDuration:5
        animations:^{
            self._addBannerDistanceFromBottomConstraint.constant = -32;
            [self.view layoutIfNeeded]; // Called on parent view
        }];
    bannerIsVisible = FALSE;
}

- (void)moveBannerOnScreen { 
    [self.view layoutIfNeeded];

    [UIView animateWithDuration:5
        animations:^{
            self._addBannerDistanceFromBottomConstraint.constant = 0;
            [self.view layoutIfNeeded]; // Called on parent view
        }];
    bannerIsVisible = TRUE;
}

斯威夫特3

UIView.animate(withDuration: 5) {
    self._addBannerDistanceFromBottomConstraint.constant = 0
    self.view.layoutIfNeeded()
}

其他回答

我很感激你提供的答案,但我认为最好能进一步讨论这个问题。

文档中的基本块动画

[containerView layoutIfNeeded]; // Ensures that all pending layout operations have been completed
[UIView animateWithDuration:1.0 animations:^{
     // Make all constraint changes here
     [containerView layoutIfNeeded]; // Forces the layout of the subtree animation block and then captures all of the frame changes
}];

但实际上,这是一个非常简单的场景。如果我想通过updateConstraints方法动画子视图约束?

调用子视图updateConstraints方法的动画块

[self.view layoutIfNeeded];
[self.subView setNeedsUpdateConstraints];
[self.subView updateConstraintsIfNeeded];
[UIView animateWithDuration:1.0f delay:0.0f options:UIViewAnimationOptionLayoutSubviews animations:^{
    [self.view layoutIfNeeded];
} completion:nil];

updateConstraints方法在UIView子类中被重写,必须在方法的末尾调用super。

- (void)updateConstraints
{
    // Update some constraints

    [super updateConstraints];
}

自动布局指南有很多不足之处,但值得一读。我自己使用它作为UISwitch的一部分,它使用一个简单而微妙的折叠动画(0.2秒长)来切换带有一对UITextFields的子视图。子视图的约束被处理在UIView子类updateConstraints方法如上所述。

两个重要注意事项:

You need to call layoutIfNeeded within the animation block. Apple actually recommends you call it once before the animation block to ensure that all pending layout operations have been completed You need to call it specifically on the parent view (e.g. self.view), not the child view that has the constraints attached to it. Doing so will update all constrained views, including animating other views that might be constrained to the view that you changed the constraint of (e.g. View B is attached to the bottom of View A and you just changed View A's top offset and you want View B to animate with it)

试试这个:

objective - c

- (void)moveBannerOffScreen {
    [self.view layoutIfNeeded];

    [UIView animateWithDuration:5
        animations:^{
            self._addBannerDistanceFromBottomConstraint.constant = -32;
            [self.view layoutIfNeeded]; // Called on parent view
        }];
    bannerIsVisible = FALSE;
}

- (void)moveBannerOnScreen { 
    [self.view layoutIfNeeded];

    [UIView animateWithDuration:5
        animations:^{
            self._addBannerDistanceFromBottomConstraint.constant = 0;
            [self.view layoutIfNeeded]; // Called on parent view
        }];
    bannerIsVisible = TRUE;
}

斯威夫特3

UIView.animate(withDuration: 5) {
    self._addBannerDistanceFromBottomConstraint.constant = 0
    self.view.layoutIfNeeded()
}

在约束动画的上下文中,我想提一下在keyboard_opened通知中立即对约束进行动画的特定情况。

约束定义了一个从文本框到容器顶部的顶部空间。打开键盘时,我只需将常数除以2。

我无法直接在键盘通知中实现一致的平滑约束动画。大约一半的时间视图会跳转到它的新位置——没有动画。

在我看来,可能会有一些额外的布局发生的结果,键盘打开。 添加一个简单的dispatch_after块和10ms的延迟使得动画每次都运行-没有跳跃。

Swift 4解决方案

UIView.animate

三个简单的步骤:

改变约束条件,例如: heightAnchor。常数= 50 告诉包含视图它的布局是脏的,并且自动布局应该重新计算布局: self.view.setNeedsLayout () 在动画块中告诉布局重新计算布局,这相当于直接设置帧(在这种情况下,自动布局将设置帧): UIView。animate(withDuration: 0.5) { self.view.layoutIfNeeded () }

最简单的例子:

heightAnchor.constant = 50
self.view.setNeedsLayout()
UIView.animate(withDuration: 0.5) {
    self.view.layoutIfNeeded()
}

旁注

有一个可选的第0步-在改变约束之前,你可能想要调用self.view.layoutIfNeeded()来确保动画的起点来自应用了旧约束的状态(以防有一些其他的约束变化不应该包含在动画中):

otherConstraint.constant = 30
// this will make sure that otherConstraint won't be animated but will take effect immediately
self.view.layoutIfNeeded()

heightAnchor.constant = 50
self.view.setNeedsLayout()
UIView.animate(withDuration: 0.5) {
    self.view.layoutIfNeeded()
}

UIViewPropertyAnimator

因为在iOS 10中我们获得了一个新的动画机制——UIViewPropertyAnimator,我们应该知道基本上相同的机制适用于它。步骤基本相同:

heightAnchor.constant = 50
self.view.setNeedsLayout()
let animator = UIViewPropertyAnimator(duration: 0.5, timingParameters: UICubicTimingParameters(animationCurve: .linear))
animator.addAnimations {
    self.view.layoutIfNeeded()
}
animator.startAnimation()

由于animator是动画的封装,我们可以保持对它的引用,并在以后调用它。然而,由于在动画块中我们只是告诉自动布局重新计算帧,我们必须在调用startAnimation之前改变约束。因此,这样的事情是可能的:

// prepare the animator first and keep a reference to it
let animator = UIViewPropertyAnimator(duration: 0.5, timingParameters: UICubicTimingParameters(animationCurve: .linear))
animator.addAnimations {
    self.view.layoutIfNeeded()
}

// at some other point in time we change the constraints and call the animator
heightAnchor.constant = 50
self.view.setNeedsLayout()
animator.startAnimation()

改变约束和启动动画器的顺序很重要——如果我们只是改变约束并将动画器留到后面的某个点,下一个重绘周期可以调用自动布局重新计算,而更改将不会被动画化。

另外,请记住单个动画器是不可重用的——一旦运行它,就不能“重新运行”它。所以我想没有什么好的理由继续使用动画器,除非我们用它来控制交互式动画。

通常,你只需要更新约束并在动画块中调用layoutIfNeeded。这可以是改变NSLayoutConstraint的.constant属性,添加remove约束(iOS 7),或者改变约束的.active属性(iOS 8和9)。

示例代码:

[UIView animateWithDuration:0.3 animations:^{
    // Move to right
    self.leadingConstraint.active = false;
    self.trailingConstraint.active = true;

    // Move to bottom
    self.topConstraint.active = false;
    self.bottomConstraint.active = true;

    // Make the animation happen
    [self.view setNeedsLayout];
    [self.view layoutIfNeeded];
}];

示例设置:

争议

有一些关于约束是否应该在动画块之前或里面改变的问题(见以前的答案)。

以下是教授iOS的马丁·皮尔金顿(Martin Pilkington)和自动布局(Auto Layout)作者肯·费里(Ken Ferry)在Twitter上的对话。Ken解释说,虽然在动画块之外改变常量目前可能工作,但这是不安全的,它们应该在动画块内部改变。 https://twitter.com/kongtomorrow/status/440627401018466305

动画:

示例项目

下面是一个简单的项目,展示了视图如何被动画化。它使用Objective C并通过改变几个约束的.active属性来动画视图。 https://github.com/shepting/SampleAutoLayoutAnimation