To help understand why we cannot mix constraints with .center (frame) changes...
Here are two almost identical view controllers.
Each adds a button, and sets the button's .centerXAnchor and .centerYAnchor to the view's .centerXAnchor and .centerYAnchor.
We add a pan gesture to the button so we can drag it around.
We also implement touchesBegan(...) where all we do is change the button title. Doing so will trigger an auto-layout pass.
In the pan func in the first example, we use a typical "get the pan location and update the .center property:
@objc func pan(_ sender: UIPanGestureRecognizer) {
    guard let v = sender.view, let sv = v.superview else { return }
    if sender.state == .changed {
        let pt: CGPoint = sender.location(in: sv)
        // update the button's .center property (changes the frame)
        v.center = pt
    }
}
This works fine, until we tap anywhere off the button. At that point, we change the button title and auto-layout moves the button back to its center X and Y constraints.
In the second example, we add X and Y constraints as var / properties to the controller:
// btn center constraints
//  we will modify the .constants when panning
var xConstraint: NSLayoutConstraint!
var yConstraint: NSLayoutConstraint!
set them up in viewDidLoad(), and then move the button in the pan gesture like this:
@objc func pan(_ sender: UIPanGestureRecognizer) {
    guard let v = sender.view, let sv = v.superview else { return }
    if sender.state == .changed {
        let pt: CGPoint = sender.location(in: sv)
        let xOff = pt.x - sv.center.x
        let yOff = pt.y - sv.center.y
        // update the .constant values for the btn center x/y
        xConstraint.constant = xOff
        yConstraint.constant = yOff
    }
}
Now, tapping anywhere else will change the button title, but it will stay where it is because we've changed the .constant values of our X and Y constraints.
Set .center -- problems
class DemoVC: UIViewController {
    
    let btnToMove = UIButton()
    
    var tapCounter: Int = 0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        btnToMove.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(btnToMove)
        
        btnToMove.setTitle("Test", for: [])
        btnToMove.backgroundColor = .red
        
        NSLayoutConstraint.activate([
            btnToMove.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            btnToMove.centerYAnchor.constraint(equalTo: view.centerYAnchor),
        ])
        
        let p = UIPanGestureRecognizer(target: self, action: #selector(pan(_:)))
        btnToMove.addGestureRecognizer(p)
    }
    
    @objc func pan(_ sender: UIPanGestureRecognizer) {
        guard let v = sender.view, let sv = v.superview else { return }
        if sender.state == .changed {
            let pt: CGPoint = sender.location(in: sv)
            // update the button's .center property (changes the frame)
            v.center = pt
        }
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        // update the button title - which will trigger an auto-layout pass
        tapCounter += 1
        btnToMove.setTitle("Test \(tapCounter)", for: [])
    }
    
}
Update constraint .constant values -- no problems
class DemoVC: UIViewController {
    
    let btnToMove = UIButton()
    
    var tapCounter: Int = 0
    
    // btn center constraints
    //  we will modify the .constants when panning
    var xConstraint: NSLayoutConstraint!
    var yConstraint: NSLayoutConstraint!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        btnToMove.setTitle("Test", for: [])
        btnToMove.backgroundColor = .red
        btnToMove.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(btnToMove)
        xConstraint = btnToMove.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0.0)
        yConstraint = btnToMove.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0.0)
        NSLayoutConstraint.activate([
            xConstraint, yConstraint,
        ])
        
        let p = UIPanGestureRecognizer(target: self, action: #selector(pan(_:)))
        btnToMove.addGestureRecognizer(p)
    }
    
    @objc func pan(_ sender: UIPanGestureRecognizer) {
        guard let v = sender.view, let sv = v.superview else { return }
        if sender.state == .changed {
            let pt: CGPoint = sender.location(in: sv)
            let xOff = pt.x - sv.center.x
            let yOff = pt.y - sv.center.y
            // update the .constant values for the btn center x/y
            xConstraint.constant = xOff
            yConstraint.constant = yOff
        }
    }
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        // update the button title - which will trigger an auto-layout pass
        tapCounter += 1
        btnToMove.setTitle("Test \(tapCounter)", for: [])
    }
}