Swift Update Constraint

i0S Swift Issue

Question or problem in the Swift programming language:

I added the constraint to the buttons created in my UIView

func CreateButtonWithIndex(index:Int) {

    newButton.setTranslatesAutoresizingMaskIntoConstraints(false)

    self.view.addSubview(newButton)

    let newButtonConstraintL = NSLayoutConstraint(item: newButton, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 50)
    let newButtonConstraintH = NSLayoutConstraint(item: newButton, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 50)
    var newButtonConstraintX = NSLayoutConstraint(item: newButton, attribute: .CenterX, relatedBy: .Equal, toItem: view, attribute: .CenterX, multiplier: 1, constant: CGFloat(riga))
    var newButtonConstraintY = NSLayoutConstraint(item: newButton, attribute: .CenterY, relatedBy: .Equal, toItem: view, attribute: .CenterY, multiplier: 1, constant: CGFloat(colonna))

    view.addConstraints([newButtonConstraintX,newButtonConstraintY,newButtonConstraintH,newButtonConstraintL])

    var pan = UIPanGestureRecognizer(target:self, action:"pan:")
    pan.maximumNumberOfTouches = 2
    pan.minimumNumberOfTouches = 1
    newButton.addGestureRecognizer(pan)
}

func pan(rec:UIPanGestureRecognizer) {
  case .Ended: 
        if let subview = selectedView {
            if let button = rec.view as? UIButton {
                if let title = button.titleForState(.Normal){
                    button.setTranslatesAutoresizingMaskIntoConstraints(false)
                    let line = Float(p.x % snapX)
                    let column = Float(p.x % snapY)
                    let constraintL = NSLayoutConstraint(item: button, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 50)
                    let constraintH = NSLayoutConstraint(item: button, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 50)
                    var constraint2 = NSLayoutConstraint(item: button, attribute: .CenterX, relatedBy: .Equal, toItem: view, attribute: .CenterX, multiplier: 1, constant: CGFloat(line))
                    var constraint3 = NSLayoutConstraint(item: button, attribute: .CenterY, relatedBy: .Equal, toItem: view, attribute: .CenterY, multiplier: 1, constant: CGFloat(column))

                    view.addConstraints([constraint2,constraint3,constraintL.constraintH])
                }
            }
        }
}

In My Project my users can move these buttons and then I tried to add more constraints to the buttons recognized but I just get an endless error constraint

  "",
  "",
  "",
  ""

Will attempt to recover by breaking constraint

""

"",
"",
"",
""

Will attempt to recover by breaking constraint


… should I update newButtonConstraintX / Y but I’m not understanding how I can do this ?

How to solve the problem:

Solution 1:

The issue is that you’re adding a new constraint that conflicts with the existing constraint.

You have a few options:

  1. Effective in iOS 8, you can set the active property to false for a constraint before you add a new constraint.

  2. In iOS versions prior to 8, you would want to remove the old constraints before adding new constraints.

  3. Ideally, it’s best to not have to activate/deactivate (or, worse, add and remove) constraints, but rather just modify the constant property of a single constraint. For example in Swift 3/4:

    class ViewController: UIViewController {
    
        private var xConstraint: NSLayoutConstraint!
        private var yConstraint: NSLayoutConstraint!
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            let label = UILabel()
            label.text = "x"
            label.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(label)
    
            // I don't really need to save references to these, so these are local variables
    
            let widthConstraint = label.widthAnchor.constraint(equalToConstant: 50)
            let heightConstraint = label.heightAnchor.constraint(equalToConstant: 50)
    
            // but since I'll be modifying these later, these are class properties
    
            xConstraint = label.centerXAnchor.constraint(equalTo: view.centerXAnchor)
            yConstraint = label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
    
            NSLayoutConstraint.activate([widthConstraint, heightConstraint, xConstraint, yConstraint])
    
            let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
            view.addGestureRecognizer(pan)
        }
    
        private var originalCenter: CGPoint!
    
        @objc func handlePan(_ gesture: UIPanGestureRecognizer) {
            if gesture.state == .began {
                originalCenter = CGPoint(x: xConstraint.constant, y: yConstraint.constant)
            }
    
            let translation = gesture.translation(in: gesture.view!)
    
            xConstraint.constant = originalCenter.x + translation.x
            yConstraint.constant = originalCenter.y + translation.y
        }
    
    }
    

    When the desired effect can be achieved by modifying the constant of the constraint, that’s generally best.

For Swift 2 syntax, see previous revision of this answer.

Solution 2:

Looks like you are just adding more and more constraint. You can’t do that because they obviously conflict with each other. You are basically saying “put the view at position x = 1, x = 2, x = 3, x = 4 and x = 5”.

You have to remove the old constraints. You have two options to do that.

  1. Save the constraints in an array, and remove these constraints from the view before adding new ones.

  2. Or you keep a reference to the constraints that change and adjust their properties.

Since your constraints just differ in the constant value you should go for option 2.

Make newButtonConstraintX and newButtonConstraintY a variable of the viewController.

e.g.

class ViewController: UIViewController {
    var newButtonConstraintX: NSLayoutConstraint!
    var newButtonConstraintY: NSLayoutConstraint!


    func CreateButtonWithIndex(index:Int) {

        newButtonConstraintX = NSLayoutConstraint(item: newButton, attribute: .CenterX, relatedBy: .Equal, toItem: view, attribute: .CenterX, multiplier: 1, constant: CGFloat(riga))

        newButtonConstraintY = NSLayoutConstraint(item: newButton, attribute: .CenterY, relatedBy: .Equal, toItem: view, attribute: .CenterY, multiplier: 1, constant: CGFloat(colonna))

        /* ... */
    }

    func pan(rec:UIPanGestureRecognizer) {  
        /* ... */
        newButtonConstraintX.constant = line
        newButtonConstraintY.constant = column
        button.layoutIfNeeded()
    }

Solution 3:

Update Rob’s Answer to Swift 3:

class ViewController: UIViewController {

    private var xConstraint: NSLayoutConstraint!
    private var yConstraint: NSLayoutConstraint!

    override func viewDidLoad() {
    super.viewDidLoad()

    let label = UILabel()
    label.text = "x"
    label.setTranslatesAutoresizingMaskIntoConstraints(false)
    view.addSubview(label)

    // I don't really need to save references to these, so these are local variables


    let widthConstraint = NSLayoutConstraint(item: drugToDrugView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 50)
    let heightConstraint = NSLayoutConstraint(item: drugToDrugView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 50)

    // but since I'll be modifying these later, these are class properties

    xConstraint = NSLayoutConstraint(item: drugToDrugView, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1.0, constant: 0)
    yConstraint = NSLayoutConstraint(item: drugToDrugView, attribute: .centerY, relatedBy: .equal, toItem: view, attribute: .centerY, multiplier: 1.0, constant: 0)

    drugToDrugView.addConstraints([widthConstraint, heightConstraint, xConstraint, yConstraint])

    let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
    view.addGestureRecognizer(pan)

 }

 private var originalCenter: CGPoint!

 func handlePan(gesture: UIPanGestureRecognizer) {
    if gesture.state == .began {
        originalCenter = CGPoint(x: xConstraint.constant, y: yConstraint.constant)
    }

    let translation = gesture.translation(in: gesture.view!)

    xConstraint.constant = originalCenter.x + translation.x
    yConstraint.constant = originalCenter.y + translation.y
    view.setNeedsLayout()
}

Hope this helps!