【问题标题】:How to use UIViewPropertyAnimator with auto layout?如何将 UIViewPropertyAnimator 与自动布局一起使用?
【发布时间】:2017-11-04 14:15:51
【问题描述】:

我在网上找到的UIViewPropertyAnimator 的所有示例都使用通过设置框架而不是使用自动布局来布局的视图,这是视图通常的布局方式。使用自动布局时,视图动画到我想要的位置,但之后我不确定如何将其设置回其“模型”位置。

// Move a view that is constrained to the center of its superview
UIViewPropertyAnimator(duration: 1, curve: .linear) {
    testView.center = CGPoint(x: 10, y: 10)
}.startAnimation()

它显然增加了某种约束?我在执行动画之前和之后记录了容器视图的约束:

CONSTRAINTS BEFORE:
view: [<NSLayoutConstraint:0x618000094f00 UIView:0x7fd764004050.centerX == UIView:0x7fd75fd00000.centerX   (active)>, <NSLayoutConstraint:0x618000092de0 UIView:0x7fd764004050.centerY == UIView:0x7fd75fd00000.centerY   (active)>]

CONSTRAINTS AFTER:
view: [<NSLayoutConstraint:0x618000094f00 UIView:0x7fd764004050.centerX == UIView:0x7fd75fd00000.centerX   (active)>, <NSLayoutConstraint:0x618000092de0 UIView:0x7fd764004050.centerY == UIView:0x7fd75fd00000.centerY   (active)>, <NSLayoutConstraint:0x6100000922a0 'UIView-Encapsulated-Layout-Height' UIView:0x7fd75fd00000.height == 667   (active)>, <NSAutoresizingMaskLayoutConstraint:0x610000092570 h=-&- v=-&- 'UIView-Encapsulated-Layout-Left' UIView:0x7fd75fd00000.minX == 0   (active, names: '|':UIWindow:0x7fd75fc07110 )>, <NSAutoresizingMaskLayoutConstraint:0x610000092890 h=-&- v=-&- 'UIView-Encapsulated-Layout-Top' UIView:0x7fd75fd00000.minY == 0   (active, names: '|':UIWindow:0x7fd75fc07110 )>, <NSLayoutConstraint:0x610000091670 'UIView-Encapsulated-Layout-Width' UIView:0x7fd75fd00000.width == 375   (active)>]

这里发生了什么?我应该如何处理使用自动布局定位的视图的动画? Apple 是否鼓励用户重新使用基于框架的布局?

【问题讨论】:

    标签: ios swift animation uiview autolayout


    【解决方案1】:

    您可以将UIViewPropertyAnimator自动布局一起使用,但您需要修改约束而不是视图的框架并在闭包内调用layoutIfNeeded()

    第一个例子:通过修改约束constant制作动画

    在情节提要中创建一个视图。创建一个约束以将视图的centerX 定位为等于父视图的前沿。为该约束创建一个@IBOutlet 并将其命名为centerXConstraint。同样,创建一个centerYConstraint,将您的视图的centerY 定位为其父视图的顶部。这两个约束都应该有constant = 0

    为约束创建@IBOutlets:

    @IBOutlet weak var centerXConstraint: NSLayoutConstraint!
    @IBOutlet weak var centerYConstraint: NSLayoutConstraint!
    

    将约束的constant 值设置为新位置,然后在UIViewPropertyAnimator 内部调用view.layoutIfNeeded()

    // Make sure view has been laid out
    view.layoutIfNeeded()
    
    // Set new X and Y locations for the view
    centerXConstraint.constant = 50
    centerYConstraint.constant = 80
    
    UIViewPropertyAnimator(duration: 1, curve: .easeInOut) {            
        self.view.layoutIfNeeded()
    }.startAnimation()
    

    第二个示例:通过激活新约束来制作动画

    在您的 Storyboard 中创建一个视图(例如 redView)并在您的代码中为其创建一个 @IBOutlet

    @IBOutlet weak var redView: UIView!
    

    为控制视图位置的约束创建@IBOutlets

    // Outlets to the constraints set in the Storyboard
    @IBOutlet weak var topConstraint: NSLayoutConstraint!
    @IBOutlet weak var leadingConstraint: NSLayoutConstraint!
    

    然后是时候制作动画了:

    // Make sure view has been laid out
    view.layoutIfNeeded()
    
    // Deactivate the old constraints
    topConstraint.isActive = false
    leadingConstraint.isActive = false
    
    // Active new constraints that move view to the bottom right        
    redView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
    redView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
    
    // Animate the change        
    UIViewPropertyAnimator(duration: 1, curve: .easeInOut) {
        self.view.layoutIfNeeded()
    }.startAnimation()
    

    那么这比旧的UIView 动画块有什么好处呢?

    UIViewPropertyAnimator 是一个对象,您可以配置它以各种方式控制动画。下面是一个完整的例子来演示:

    1. 开始动画
    2. 暂停动画
    3. 找出已完成的动画部分并将幻灯片值设置为该部分
    4. 使用滑块浏览动画

    class ViewController: UIViewController {
    
        @IBOutlet weak var redView: UIView!
        @IBOutlet weak var centerXConstraint: NSLayoutConstraint!
        @IBOutlet weak var centerYConstraint: NSLayoutConstraint!
        @IBOutlet weak var slider: UISlider!
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            slider.isHidden = true
        }
    
        var myAnimator: UIViewPropertyAnimator?
    
        @IBAction func startAnimation(_ sender: UIButton) {
            view.layoutIfNeeded()
    
            centerXConstraint.isActive = false
            centerYConstraint.isActive = false
    
            redView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
    
            redView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
    
            myAnimator = UIViewPropertyAnimator(duration: 10, curve: .easeInOut) {
    
                self.view.layoutIfNeeded()
            }
    
            myAnimator?.startAnimation()
        }
    
        @IBAction func pauseAnimation(_ sender: UIButton) {
            guard let myAnimator = myAnimator else { return }
    
            myAnimator.pauseAnimation()
    
            print("The animation is \(String(format: "%.1f", myAnimator.fractionComplete * 100))% complete")
    
            slider.value = Float(myAnimator.fractionComplete)
            slider.isHidden = false
        }
    
        @IBAction func scrub(_ sender: UISlider) {
            myAnimator?.fractionComplete = CGFloat(sender.value)
        }
    }
    

    【讨论】:

    • 这只是 UIView 动画块的老方法。没有改善吗?还有那些生成的约束是什么?
    • UIViewPropertyAnimator 是一个可以配置为允许动画暂停、反转等的对象。您可以创建自己的曲线来控制速度等等。
    • 我不明白你在问什么约束。我只是在演示如何将新约束应用于视图并为其设置动画。
    • 在我的问题结束时,我表明视图中添加了系统生成的约束。允许您在帧上制作动画并生成这些约束似乎是一种有意的行为。我想了解一下。
    • 我不认为这些约束是您期望与之交互的任何东西。我更新了我的问题以回答您的“UIView 动画块是否有任何改进”的问题。看看吧。
    【解决方案2】:

    我为swift 4.1做了一个教程?

    来自 github 的示例应用和完整教程:
    http://eon.codes/blog/2018/05/15/Animating-with-constraints/

    class AnimVC:UIViewController{
    
    
     lazy var square:Square = createSquare()
        override func viewDidLoad() {
            super.viewDidLoad()
            view.backgroundColor = .green
            _ = square
        }
    }
    extension AnimVC{
        func createSquare() -> Square{
            let square = Square()
            square.backgroundColor = .orange
            self.view.addSubview(square)
            _ = {//Define constraints for Square
                square.translatesAutoresizingMaskIntoConstraints = false
                let anchor = Constraint.anchor(square, to: view, align: .centerCenter, alignTo: .centerCenter)
                let size = Constraint.size(square, size: CGSize(width:100,height:100))
                square.anchor = anchor
                square.size = size
                NSLayoutConstraint.activate([anchor.x,anchor.y,size.w,size.h])
            }()
    
            let tap = UITapGestureRecognizer(target: self, action:  #selector(handleTap))
            square.addGestureRecognizer(tap)
            return square
        }
        @objc func handleTap(_ sender:UITapGestureRecognizer){
            let newConstraint = {
                guard let oldAnchorConstraint = self.square.anchor else {fatalError("err posConstraint not available")}
                NSLayoutConstraint.deactivate([oldAnchorConstraint.x])
                let newAnchorConstraint = Constraint.anchor(self.square, to: self.view, align: .topLeft, alignTo: .topLeft, offset: CGPoint(x:0,y:0))
                NSLayoutConstraint.activate([newAnchorConstraint.x])
                self.square.anchor?.x = newAnchorConstraint.x
            }
            let anim = UIViewPropertyAnimator(duration: 0.3, curve: .easeOut, animations: {
                newConstraint()// ⚠️️ Set the new constraint goal
                self.view.layoutIfNeeded()//⚠️️  Ask the parent view to update its layout
            })
            anim.startAnimation()
        }
    }
    class Square:UIView{
        var anchor:(x:NSLayoutConstraint,y:NSLayoutConstraint)?
        var size:(w:NSLayoutConstraint,h:NSLayoutConstraint)?
    }
    

    【讨论】:

      猜你喜欢
      • 2018-03-17
      • 2019-03-22
      • 1970-01-01
      • 2020-09-07
      • 2010-12-17
      • 2021-05-17
      • 1970-01-01
      • 1970-01-01
      • 2015-04-30
      相关资源
      最近更新 更多