Search code examples
iosswiftxcodenslayoutconstraint

How do I have to modify my UIView extension if I wanted to animate the auto layout constraints afterward?


I have made an UIView extension to set my NSLayout anchors. Everything works just fine. But how can I modify my extension if I wanted to add an NSLayoutConstraint so I could animate the constraints afterward?

Here is my current extension:

extension UIView {
    func anchor(top: NSLayoutYAxisAnchor?, leading: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, trailing: NSLayoutXAxisAnchor?, padding: UIEdgeInsets = .zero, size: CGSize = .zero) {
        //translate the view's autoresizing mask into Auto Layout constraints
        translatesAutoresizingMaskIntoConstraints = false

        if let top = top {
            topAnchor.constraint(equalTo: top, constant: padding.top).isActive = true
        }

        if let leading = leading {
            leadingAnchor.constraint(equalTo: leading, constant: padding.left).isActive = true
        }

        if let bottom = bottom {
            bottomAnchor.constraint(equalTo: bottom, constant: -padding.bottom).isActive = true
        }

        if let trailing = trailing {
            trailingAnchor.constraint(equalTo: trailing, constant: -padding.right).isActive = true
        }

        if size.width != 0 {
            widthAnchor.constraint(equalToConstant: size.width).isActive = true
        }

        if size.height != 0 {
            heightAnchor.constraint(equalToConstant: size.height).isActive = true
        }
    }
}

And here is how the extension is called:

//feedViewButton constraints
feedViewButton.anchor(top: nil, leading: nil, bottom: view.safeAreaLayoutGuide.bottomAnchor, trailing: view.trailingAnchor, padding: .init(top: 0, left: 0, bottom: 0, right: 25), size: .init(width: 50, height: 50))

UPDATE

I would like to have something like this

var topAnchor: NSLayoutConstraint?

topAnchor = topAnchor.constraint(equaltTo: top, constant: padding.top)
topAnchor.isActive = true

And then animate it like this:

let animator = UIViewPropertyAnimator(duration: 1, curve: .easeOut) {
    topAnchor.constant = 20
}
animator.startAnimation()

Solution

  • Just try to animate it using autolayout, since the constraints haven't been applied yet, it should be animatable:

    feedViewButton.anchor(top: nil, leading: nil, bottom: view.safeAreaLayoutGuide.bottomAnchor, trailing: view.trailingAnchor, padding: .init(top: 0, left: 0, bottom: 0, right: 25), size: .init(width: 50, height: 50))
    feedViewButton.superview?.setNeedsLayout()
    UIView.animate(withDuration: 0.2, delay: 0, options: [.allowUserInteraction], animations: {
        feedViewButton.superview?.layoutIfNeeded()
    }, completion: nil)
    

    UPDATE

    If you want to later change the constants on the anchors to animate some other change, you have to keep references to the constraints to be able to manipulate them later:

    enum ConstraintType {
        case top, leading, trailing, bottom, width, height
    }
    
    extension UIView {
    
        func anchor(top: NSLayoutYAxisAnchor?, leading: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, trailing: NSLayoutXAxisAnchor?, padding: UIEdgeInsets = .zero, size: CGSize = .zero) -> [ConstraintType : NSLayoutConstraint] {
            //translate the view's autoresizing mask into Auto Layout constraints
            translatesAutoresizingMaskIntoConstraints = false
    
            var constraints: [ConstraintType : NSLayoutConstraint] = [:]
    
            if let top = top {
                constraints[.top] = topAnchor.constraint(equalTo: top, constant: padding.top)
            }
    
            if let leading = leading {
                constraints[.leading] = leadingAnchor.constraint(equalTo: leading, constant: padding.left)
            }
    
            if let bottom = bottom {
                constraints[.bottom] = bottomAnchor.constraint(equalTo: bottom, constant: -padding.bottom)
            }
    
            if let trailing = trailing {
                constraints[.trailing] = trailingAnchor.constraint(equalTo: trailing, constant: -padding.right)
            }
    
            if size.width != 0 {
                constraints[.width] = widthAnchor.constraint(equalToConstant: size.width)
            }
    
            if size.height != 0 {
                constraints[.height] = heightAnchor.constraint(equalToConstant: size.height)
            }
            let constraintsArray = Array<NSLayoutConstraint>(constraints.values)
            NSLayoutConstraint.activate(constraintsArray)
            return constraints
        }
    }
    

    This extension returns a dictionary of constraints that you can later change and animate those changes. E.g.:

    let constraints = feedViewButton.anchor(top: nil, leading: nil, bottom: view.safeAreaLayoutGuide.bottomAnchor, trailing: view.trailingAnchor, padding: .init(top: 0, left: 0, bottom: 0, right: 25), size: .init(width: 50, height: 50))
    // applies the constraints    
    self.view.layoutIfNeeded()
    
    // now to animate bottom to -50:
    if let bottomConstraint = constraints[.bottom] {
        bottomConstraint.constant = -50
        self.view.setNeedsLayout()
        let animator = UIViewPropertyAnimator(duration: 1, curve: .easeInOut, animations: {
            self.view.layoutIfNeeded()
        })
        animator.startAnimation()
    }