UIView动画在开始时会跳跃

33

我正在运行这段代码...

[UIView animateWithDuration:0.2
                      delay:0.0
                    options:UIViewAnimationOptionCurveEaseOut
                 animations:^{

                     CGAffineTransform settingsTransform = CGAffineTransformMakeTranslation(self.settingsView.frame.size.width, 0);
                     CGAffineTransform speedTransform = CGAffineTransformMakeTranslation(-self.speedView.frame.size.width, 0);

                     self.settingsView.transform = settingsTransform;
                     self.speedView.transform = speedTransform;

                 } completion:nil];

但是当它运行时,视图会在滑向正确方向的一半位置之前,向相反的方向跳动半个变换。

我已经将动画持续时间减慢到5秒,但最初的跳跃是瞬间的,并且在错误的方向上进行了一半的变换。

当我使用以下代码向后动画时...

[UIView animateWithDuration:0.2
                      delay:0.0
                    options:UIViewAnimationOptionCurveEaseOut
                 animations:^{
                     self.settingsView.transform = CGAffineTransformIdentity;
                     self.speedView.transform = CGAffineTransformIdentity;
                 } completion:nil];

它的作用完全相同。

结果是,最终移动的距离只有期望变换的一半,因为它首先向错误的方向跳了一半的变换。

我真的无法弄清楚为什么会发生这种情况?

有任何想法吗?

::编辑::

澄清可能存在的歧义。

我不想让这些视图“弹回”到它们所在的位置。我正在对类似于屏幕边缘控制面板的视图进行动画处理。当用户按下“go”时,该视图将滑出屏幕。当用户按下“stop”时,面板将滑回视图中。

至少曾经如此。自从启用自动布局(我需要应用程序的其他部分)以来,我就不能只更改视图的框架,因此我采用了变换方法。

您可以通过将视图放入视图控制器并添加一个按钮来运行动画来查看此效果。

谢谢


我刚刚建立了一个全新的项目,其中只有一个VC(在故事板中),在VC的视图中只有一个按钮和一个视图(红色背景)。该按钮使用与上述相同的代码对红色视图进行从左到右的变换动画。但是它以完全相同的方式跳跃。 - Fogmeister
动画何时何地开始? - NeverBe
你想要一个视图自左向右重复地动画吗?如果是的话,有一种更简单的方法可以实现这个效果。 - tiguero
嗨,不,这些动画完全独立于彼此。请参见OP中的编辑。谢谢。 - Fogmeister
你有没有考虑在第二个动画中使用UIViewAnimationOptionBeginFromCurrentState选项?我已经尝试了你的第一个代码,它会将你的视图向右动画并按预期停止。如果你想让第二个动画从第一个动画的任何时间开始,请使用我提到的标志。 - tiguero
显示剩余4条评论
9个回答

71

我遇到了完全相同的问题,所以这是我想出的解决方案。

    CGAffineTransform transform = CGAffineTransformMake(1, 0, 0, 1, translation.x, translation.y);
    [UIView animateWithDuration:0.25 animations:^{
        _assetImageView.transform = transform;
        [self.view layoutIfNeeded];
    } completion:^(BOOL finished) {
    }];

所以我在动画块内调用 [self.view layoutIfNeeded];。如果没有这个,它会像你一样出现同样的问题,并且首先跳转到负平移的距离,然后从那里动画到正确的位置。我是从视图控制器中调用这个的,所以 self 是 UIViewController 的子类。在您的情况下,“self.view”可能不存在,但我希望您能理解。


1
谢谢,这个方法可行。有人能解释一下为什么它可行吗? - Jeff Ames
1
这应该被标记为正确答案,因为由于动画约束的性质,这是必需的。这对我很有效! - SmokersCough
2
哇,那是一个完美运作的随机解决方案!你有任何想法为什么它能够工作吗? - Aviel Gross
天啊,@davidnesbitt 是我的英雄!我已经寻找解决方案至少一年了,期间一直在苦苦挣扎。你太棒了! - MobileVet
1
我仍然有同样的问题。如果在动画之前进行仿射旋转和缩放,它会非常扭曲。如果有延迟,它将保持在这个扭曲的位置,并从扭曲的位置动画到最终所需的变换。我可以从缩放到缩放+变换进行动画,没有问题。只是从缩放+变换到另一个缩放+变换的动画。在旧版iOS中可以正常工作。 - samuel Schweighart
显示剩余3条评论

16

这里的所有解决方案都不适用于我...我的动画总是会跳过。(除非它恰好在另一个动画的结尾,提示)。

一些细节:

导致这种情况的视图已经有一系列的比例和平移操作。

解决方案:

使用关键帧动画,第一个关键帧非常短,基本上重新应用了上一个动画应该设置的变换。

    UIView.animateKeyframesWithDuration(0.4, delay: 0.0, options: [.CalculationModeCubic], animations: { () -> Void in
        //reset start point
        UIView.addKeyframeWithRelativeStartTime(0.0, relativeDuration: 0.01, animations: { () -> Void in
            self.element.transform = initialTransform
        })

        UIView.addKeyframeWithRelativeStartTime(0.01, relativeDuration: 0.99, animations: { () -> Void in
            self.element.transform = finalTransform
        })
    }, completion: nil)

1
错误在iOS 9.3仍然存在。这是唯一可行的解决方案。 - Donnit
1
Andres,我很敬畏你。你是我的英雄。太棒了!!!(有一件小事...我认为你打错字了。最后一个relativeDuration应该是0.99而不是0.39,对吧?) - Charlie Hitchcock
@CharlieHitchcock,谢谢!你测试过了吗?有什么不同吗?从我的经验来看,由于持续时间是相对的,所以这里的总持续时间为0.4,因此它大约与键值为0.025和0.975的相同。我不确定是否测试了不同的范围并且这个持续时间有效,或者在此之前是否有另一个关键帧。 - Andres Canella
1
@AndresCanella 我已经使用“relativeDuration: 0.99”进行了测试,以这种方式它按预期工作(非常出色!)。而且0.99是正确的,因为它是相对持续时间。从addKeyFrame文档中可以看到:“开始时间和持续时间是介于0.0和1.0之间的值,指定相对于关键帧动画的总时间的时间和持续时间”。 - Charlie Hitchcock
没错!还在游乐场测试过了。将答案更新为0.99。 - Andres Canella
显示剩余2条评论

8
我向苹果技术支持咨询了这个问题,并得到了如下回复,所以这是一个bug。
iOS 8目前在UIKit动画中存在一个已知的bug,其中变换动画获取错误的fromValue(主要影响动画对象的位置,在动画开始时使它们出现意外的“跳动”)。
直至可能修复此问题时的解决方法是使用Core Animation API来编写您的动画。

这个问题是3年前写的。当时它不是一个bug,而是自动布局和我没有正确地进行动画处理。 - Fogmeister
1
我很感激这个答案,因为它恰好就是我遇到的 bug。 - phatmann

8

好的,重新观看WWDC视频后发现,在使用AutoLayout时,首先必须要做的是删除任何对setFrame的调用。

由于屏幕的复杂性,我已完全删除了Auto-Layout,并使用frame位置来移动视图在屏幕上的位置。

谢谢。


2
禁用自动布局解决了我的问题。谢谢。 - YongJiang Zhang

4
这是Andreas回答的改进版 关键区别在于您可以指定一个关键帧并获得更少的代码
UIView.animateKeyframes(withDuration: animationDuration, delay: 0.0, options: [], animations: {
  UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 1.0, animations: {
    animatedView.transform = newTransform
    })
  }, completion: nil)

这个可以运行,但我真不知道为什么。 - mattsven
(为了增加一些背景,我在iOS 12中遇到了这个错误) - mattsven

1

由于您希望第二个动画从第一个动画的当前状态开始(无论它是否完成),因此建议在设置第二个动画时使用UIViewAnimationOptionLayoutSubviews选项。

[UIView animateWithDuration:0.2
                      delay:0.0
                    options:UIViewAnimationOptionCurveEaseOut|UIViewAnimationOptionLayoutSubviews
                 animations:^{
                     self.settingsView.transform = CGAffineTransformIdentity;
                     self.speedView.transform = CGAffineTransformIdentity;
                 } completion:nil];

这是我使用简单视图控制器模板进行测试时使用的代码示例:

myviewcontroller.m:

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.animatedView = [[UIView alloc] initWithFrame:CGRectMake(20, 20, 160, 80)];
    self.animatedView.backgroundColor = [UIColor yellowColor];

    [UIView animateWithDuration:0.2
                          delay:0.0
                        options:UIViewAnimationOptionCurveEaseOut|UIViewAnimationOptionLayoutSubviews
                     animations:^{
                         CGAffineTransform settingsTransform = CGAffineTransformMakeTranslation(self.animatedView.frame.size.width, 0);

                         self.animatedView.transform = settingsTransform;

                     }
                     completion:nil];
    self.buttonToTest = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    self.buttonToTest.frame = CGRectMake(90, 20, 80, 40);
    [self.buttonToTest setTitle:@"Click Me!" forState:UIControlStateNormal];
    [self.buttonToTest addTarget:self action:@selector(buttonClicked) forControlEvents:UIControlEventTouchUpInside];

    // set-up view hierarchy
    [self.view addSubview:self.buttonToTest];
    [self.view addSubview: self.animatedView];

}

- (void) buttonClicked
{
    [UIView animateWithDuration:.2
                          delay:0.0
                        options:UIViewAnimationOptionCurveEaseOut|UIViewAnimationOptionBeginFromCurrentState|UIViewAnimationOptionLayoutSubviews
                     animations:^{
                         self.animatedView.transform = CGAffineTransformIdentity;

                     }
                     completion:nil];
}

嗨,这两个动画完全独立于彼此。我不想将它们链接在一起。请参见 OP 的编辑。谢谢。 - Fogmeister
用户第一次按下按钮时,它向右移动。当用户再次按下它时,视图返回到原来的位置。每个动画只有一个单向滑动的移动。如果您使用 OP 中显示的视图设置项目,则可以看到这种情况发生。 - Fogmeister

1

您的settingsView或speedView在此动画发生之前是否已经应用了变换?

如果这些视图的变换不是恒等变换(即CGAffineTransformIdentity,又称无变换),则无法访问它们的.frame属性。

当视图应用变换时,UIViews的frame属性无效。请改用“bounds”。


感谢您的回答。在尝试动画之前,变换没有被触摸,当第二个动画(返回CGAffineTransformIdentity)时,它会回到起始位置。谢谢。 - Fogmeister
如果你组建一个项目来尝试这个,你就可以看到它发生了。只需要一个按钮和一个视图来执行动画。当你执行动画时,它会先向后跳动视图,然后再向前移动。 - Fogmeister

0

我尝试了上面关于layoutIfNeeded的答案,但是没有起作用。 我将layoutIfNeeded添加到动画块之外(之前),这解决了我的问题。

view.layoutIfNeeded()
    
UIView.animate(withDuration: duration, delay: 0, options: .beginFromCurrentState, animations: {
    // Animation here
})

0

我曾经遇到过这个问题,并通过以下方式解决:

  1. 隐藏原始视图
  2. 添加一个临时视图,复制您想要动画的视图
  3. 执行动画,现在应该是预期的效果
  4. 删除临时视图并显示最终状态的原始视图

希望这可以帮到您。


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