如何在Swift中使用约束来实现UIView动画?

47

在我编造的例子中,我有以下单个视图:

enter image description here

如您所见,它由几个简单的约束组成:

  1. 水平和垂直中心对齐,
  2. 高度(设置为常量)
  3. 前导和尾随空格(设置为常量)

我希望实现的目标是使这个红色/粉色视图从顶部“进入”。传统上,在没有约束的世界中,我只需在 UIView.animateWithDuration内修改框架即可,但是我不确定在约束世界中如何做到类似的效果。

重申我的问题,如何使我的视图从场景外开始,并使其从顶部飞入动画?

我考虑过动画垂直中心约束(然后调用 layoutIfNeeded),但它没有实现期望的效果。

感谢您的帮助。


可能是如何动画化约束变化?的重复问题。 - emem
5个回答

77
您可以在viewDidAppear方法中添加以下代码。首先,创建一个视图中心Y约束的IBOutlet属性,然后更改其常量值。

您可以在viewDidAppear方法中添加以下代码。首先,创建一个视图中心Y约束的IBOutlet属性,然后更改其常量值。

self.centerYConstraint.constant = 500.0
self.view.layoutIfNeeded()

UIView.animateWithDuration(Double(0.5), animations: {
        self.centerYConstraint.constant = 0
        self.view.layoutIfNeeded()
    })

使用常量并不意味着您已经将视图居中。如果旋转设备,您将失去视图。 - Daniel Galasko
19
伙计们,self.view.layoutIfNeeded() 是关键。 - Kashif
@DanielGalasko,你可以将第一项和第二项颠倒,以从-500到0。 - wyu
@wyu 不太确定这是否符合我的意思?我已经回答了这个问题https://dev59.com/914c5IYBdhLWcg3w7994#27417189 - Daniel Galasko
好的解决方案。 - Raj Aggrawal

15
你想要的其实相当简单,我会详细说明如何完成它,而不会影响优先级或奇怪的常量。这样我们就可以得到一个在任何屏幕大小上都能工作的动画。
简短版本是,你需要配置约束,并在动画块内调用layoutIfNeeded以动画化更改。欲了解更多,请参见此SO帖子
因此,我们需要两组用于隐藏和显示视图的约束。在viewDidLoad中,视图将被隐藏,然后在viewDidAppear或任何我们想要滑入的地方。
故事板/XIB设置
首先要做的是在IB(或代码)中配置不会更改的约束。即红色视图的高度以及其与容器的前导和尾随间距。因此,您的约束可能如下所示(请注意:我没有设置宽度约束,而是让前导和尾随空间定义宽度):
现在,您将看到IB会警告您没有为视图的Y位置配置约束(因此是红色虚线)。
您现在可以添加顶部空间到容器约束,并将其设置为占位符约束(勾选框说“在构建时删除”)。我们这样做是因为我们想通过编程方式控制视图的位置。
这意味着,一旦加载视图,这个约束就不存在了,但是它可以消除所有警告,因为您告诉IB,当视图加载时,您知道如何处理这个约束。
编码时间
现在我们需要一个隐藏视图的方法,一个显示视图的方法。为此,我们将存储视图上的Y定位约束,然后对其进行动画处理。我们的viewController可能如下所示:
@IBOutlet weak var redView: UIView!
var redViewYPositionConstraint: NSLayoutConstraint?

override func viewDidLoad() {
    super.viewDidLoad()
    self.hideRedViewAnimated(false)
}

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)
    self.showRedViewAnimated(true)
}

隐藏

我们隐藏视图的方法很简单,只需删除位置约束,然后添加一个将红色视图底部与视图控制器顶部相等的约束:

func hideRedViewAnimated(animated: Bool) {
    //remove current constraint
    self.removeRedViewYPositionConstraint()
    let hideConstraint = NSLayoutConstraint(item: self.redView,
        attribute: .Bottom,
        relatedBy: .Equal,
        toItem: self.view,
        attribute: .Top,
        multiplier: 1,
        constant: 0)
    self.redViewYPositionConstraint = hideConstraint
    self.view.addConstraint(hideConstraint)
    //animate changes
    self.performConstraintLayout(animated: animated)
}

展示

类似地,我们的展示约束将把视图中心Y移动到控制器的中心Y:

func showRedViewAnimated(animated: Bool) {
    //remove current constraint
    self.removeRedViewYPositionConstraint()
    let centerYConstraint = NSLayoutConstraint(item: self.redView,
        attribute: .CenterY,
        relatedBy: .Equal,
        toItem: self.view,
        attribute: .CenterY,
        multiplier: 1,
        constant: 0)
    self.redViewYPositionConstraint = centerYConstraint
    self.view.addConstraint(centerYConstraint)
    //animate changes
    self.performConstraintLayout(animated: animated)
}

便捷方法

为了完整起见,我使用的便捷方法如下:

func performConstraintLayout(animated animated: Bool) {
    if animated == true {
          UIView.animateWithDuration(1,
          delay: 0,
          usingSpringWithDamping: 0.5,
          initialSpringVelocity: 0.6,
          options: .BeginFromCurrentState,
          animations: { () -> Void in
             self.view.layoutIfNeeded()
          }, completion: nil)
     } else {
          self.view.layoutIfNeeded()
     }
}

func removeRedViewYPositionConstraint() {
    if redViewYPositionConstraint != nil {
        self.view.removeConstraint(self.redViewYPositionConstraint!)
        self.redViewYPositionConstraint = nil
    }
}

您还可以通过进行一些CGRect数学计算来检查红色视图是否可见:

func isRedViewVisible() -> Bool {
    return CGRectContainsPoint(self.view.bounds, self.redView.frame.origin)
}

1
非常棒的答案。自从2014年回答这个问题时的iOS版本以来,布局锚点(例如UIView.anchorTop、.anchorBottom、.anchorLeading、.anchorTrailing、.anchorWidth、.anchorHeight、.centerXanchor、.centerYanchor等)在编程中更加简洁易用。 - clearlight

10

对于任何寻找简单动画的人,如所要求的:

对于上滑动画,您需要使用[...] CGAffineTransformMakeTranslation(x, y)

原因是对于上滑动画,您需要首先将视图移出屏幕,然后将其返回到原始位置。因此,在您的viewDidLoad中只需使用:

yourView.transform = CGAffineTransformMakeTranslation(0, 500)

这会将视图移出屏幕,在这个例子中是在底部。现在在 viewDidAppear方法中,您可以使用以下代码显示您的视图:

UIView.animateWithDuration(0.7, delay: 0.0, usingSpringWithDamping: 0.5,
initialSpringVelocity: 0.5, options: [], animations: {
        self.yourView.transform = CGAffineTransformMakeScale(1, 1)
    }, completion: nil)
使用这些代码,您可以在UIView上添加所需的约束,并将其放置在ViewController中需要的位置。

你能从你的链接中添加一些内容吗? - Robert
请稍等,只需要几分钟! - Massimo Polimeni
这是一个不错的动画。 - Chandan Jee

10

试一下这个:

class ViewController: UIViewController {

    // red view
    @IBOutlet weak var myView: UIView!
    //  vertical align contraint
    @IBOutlet weak var verticalConstraint: NSLayoutConstraint!

    override func viewDidLoad() {
        super.viewDidLoad()
        verticalConstraint.constant = (myView.bounds.height + self.view.bounds.height)/2
    }

    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)
        self.view.layoutIfNeeded()
        UIView.animateWithDuration(0.5) {
            self.verticalConstraint.constant = 0
            self.view.layoutIfNeeded()
        }
    }

}

在约束中要注意firstItemsecondItem的顺序。上面的代码假设Superview是firstItem:

screenshot


另一种方法是定义两个约束:

  • Center Y对齐约束 (Superview.CenterY == View.CenterY) 优先级 = 750
  • 垂直间距约束 (SuperView.Top == View.Bottom) 优先级 = 500

screenshot

并调整优先级来确定应采用哪个约束。

class ViewController: UIViewController {

    //  vertical align contraint
    @IBOutlet weak var centerYConstraint: NSLayoutConstraint!

    override func viewDidLoad() {
        super.viewDidLoad()
        centerYConstraint.priority = 250
    }

    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)
        self.view.layoutIfNeeded()
        UIView.animateWithDuration(0.5) {
            self.centerYConstraint.priority = 750
            self.view.layoutIfNeeded()
        }
    }

}

优先级从严格意义上讲并不是实现这一目的的最佳方式,因为您希望布局系统不会强制执行中心约束,因为其优先级较低。相反,您应该正确配置约束以隐藏和显示视图。 - Daniel Galasko

5

对我来说,最好的动画方式是使用Swift 3和4

self.constraint.constant = -150

UIView.animate(withDuration: 0.45) { [weak self] in
    self?.view.layoutIfNeeded()
}

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