UIStackView 中的 CABasicAnimation 不可靠?

Posted

技术标签:

【中文标题】UIStackView 中的 CABasicAnimation 不可靠?【英文标题】:CABasicAnimation not reliable in UIStackView? 【发布时间】:2017-01-25 19:58:25 【问题描述】:

我有一个轴垂直的 UIStackView,它显示了一些类似于 UITableView 的项目。但是,我正在为它们设置动画。使用 DispatchQueue.main.asyncAfter 进行延迟。

这是我用来创建 CABasicAnimation 的代码:

let anim = CABasicAnimation(keyPath: "transform.translation.x")
anim.duration = 0.2
anim.fromValue = UIScreen.main.bounds.size.width
anim.toValue = 0
anim.isRemovedOnCompletion = true
anim.fillMode = kCAFillModeRemoved

出于某种原因,至少在模拟器上(我认为这无关紧要,因为这个应用程序应该可以在旧设备或 iPhone 7+ 等功能强大的设备上运行),动画并不总是出现在同一个地方点。

有时,单元格会飞入并距左侧约 8 个像素,有时约为 12,有时约为 20。这似乎不一致。但我认为我的 toValue 为 0,如果我没有向图层添加动画,它应该出现在正常出现的位置。

我尝试将 isRemovedOnCompletion 设置为 true,并将 fillMode 设置为已移除,但这似乎无法修复动画。

附带说明一下,随着动画的继续,有一个单元格是由水平 UIStackView 组成的,其中排列的子视图隐藏在其中。我使用 UIView.animateWithDuration 方法将它们的隐藏值设置为 false,从而为子视图设置动画。

我之所以注意到这一点,是因为当这种情况发生时,上方的单元格会弹回到它们应该在的位置,而不是偏离几个像素。

我在单元格动画后尝试了 setNeedsDisplay,也尝试了verticalStackView,但也没有用。我不明白发生了什么以及为什么这些图层不会动画到它们的正确位置。有什么想法吗?

附:垂直stackView全部在ViewController的init方法中初始化。动画发生在 viewDidAppear 方法中。我尝试通过添加 DispatchQueue.main.asyncAfter 来抵消动画,并且在 super.viewDidAppear(animated) 之后也尝试了第一件事,但两者的结果相同。

【问题讨论】:

您可能会更成功地为约束设置动画而不是尝试转换 X 位置。堆栈视图广泛使用自动布局,您可以在此系统之外有效地工作。 我尝试在 UIView 的前导值的情节提要中编辑常量,其中 UIView 位于 UIStackView 中。它不喜欢那样。当stackview不允许时,如何为位置约束设置动画?也许我需要创建一个占位符单元格。然后为内容制作动画? 是的,添加了一个占位符,并且修复了它。 是的,我打算这样做;堆栈视图非常聪明,但您需要使用它们;他们尝试计算其内容的内在大小,因此当内容为空时它们无法正常工作。一个占位符解决了这个问题。 【参考方案1】:

动画项目进出堆栈视图实际上非常容易。

您所要做的就是在将视图添加到堆栈视图之前将视图的框架设置为您希望它开始的位置,然后在添加新视图后立即在动画块内调用 layoutIfNeeded() 。以下是从工作应用程序中获取的一些示例代码:

let newView = createNewView()
self.stackView.addArrangedSubview(newView)
if sender != nil 
  UIView.animate(withDuration: 0.2) 
    self.stackView.layoutIfNeeded()
  

【讨论】:

因此,如果我希望所有单元格从左侧飞入,我需要将它们的框架设置为添加为排列子视图后的位置,但减去 xPosition 的屏幕宽度?这似乎有点复杂,因为现在我必须计算它。如果是这样的话,如果不是为了简化所有计算,我为什么要使用 stackview 开始呢? 在我为每个用户交互添加新的排列子视图的情况下,这将是一个非常好的解决方案,我希望它们源自我计算的位置,而不是让 UIStackView 计算它。就像用户点击一个加号按钮,金币从一袋黄金或其他东西中一次激活一个。 如果你总是希望它们从左边出现,将它们的 x 位置设置为 0,甚至 -tileView.bounds.width,并将它们的 y 值设置为 0。正如保罗所说,你在战斗使用自动布局使您的堆栈视图工作。【参考方案2】:

解决了这个问题。问题来了……

1) 您只想为表示层设置动画,但要保持元素的实际位置,因为 UIStackView 可以很好地为您定位。所以 CoreAnimation 似乎是个不错的选择。

2) 因为您正在处理 UIStackView,所以乍一看,您无法为它的arrangedSubviews 设置位置约束的动画。因此,这是您不想为约束设置动画并使用 CoreAnimation 的另一个原因。

所以你选择 CoreAnimation。因为只有表示层很重要,所以不需要动画约束对吗?仅当您希望视图的用户交互部分遵守约束时才真正需要这样做吗?嗯,是的,也不是。

但无论出于何种原因,CoreAnimation 都没有做它应该做的事情。也许苹果会在未来解决这个问题。但也许不是。哦,那没关系。这是解决方案:

为堆栈视图中的所有排列子视图使用占位符。对我来说,这只是一个 UIView,它会根据里面的内容调整自己的大小。

现在,让这个占位符没有背景颜色。只允许它包含您不想制作动画的可见内容。由于我正在为整个单元格设置动画,因此我只有一个没有背景颜色的空白 UIView。

为此占位符视图的子视图设置约束。现在,您可以使用常用的 AutoLayout 动画工具为这些约束设置动画。例如:

constraint.constant = 0
UIView.animate(withDuration: 0.2, 
    delay: 0.1, 
    options: UIViewAnimationOptions.curveEaseOut, 
    animations: 
        cell.layoutIfNeeded()
    , completion: nil)

嘘!动画很流畅,实际上最终会出现在您想要它们相对于排列子视图的位置。哇哦!

【讨论】:

以上是关于UIStackView 中的 CABasicAnimation 不可靠?的主要内容,如果未能解决你的问题,请参考以下文章

UISlider 在水平 UIStackView 中的高度

UIStackView 在 Interface Builder 中的显示方式不同

替换 UIStackView 中的视图

UIScrollView 中的 UIStackView 不滚动

UIStackView 中的 UILabel 中的 CAGradientLayer

检测 UIStackView 间距中的点击(项目之间)