Swift Update Constraint

23,992

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.

Share:
23,992
stack
Author by

stack

Updated on June 20, 2020

Comments

  • stack
    stack almost 4 years

    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

      "<NSLayoutConstraint:0x7f8e5a4e9450 UIButton:0x7f8e5a644f60'Button6'.centerX == UIView:0x7f8e5a648bc0.centerX - 120>",
      "<NSLayoutConstraint:0x7f8e5a5be040 'UIView-Encapsulated-Layout-Width' H:[UIView:0x7f8e5a648bc0(736)]>",
      "<NSAutoresizingMaskLayoutConstraint:0x7f8e5a5be1f0 h=-&- v=-&- 'UIView-Encapsulated-Layout-Left' H:|-(0)-[UIView:0x7f8e5a648bc0]   (Names: '|':UITransitionView:0x7f8e5a5a8190 )>",
      "<NSAutoresizingMaskLayoutConstraint:0x7f8e5a5b53b0 h=--& v=--& UIButton:0x7f8e5a644f60'Button6'.midX == + 200>"
    

    Will attempt to recover by breaking constraint

    "<NSLayoutConstraint:0x7f8e5a4e9450 UIButton:0x7f8e5a644f60'Button6'.centerX == UIView:0x7f8e5a648bc0.centerX - 120>"
    
    "<NSLayoutConstraint:0x7f8e5a4e9510 UIButton:0x7f8e5a644f60'Button6'.centerY == UIView:0x7f8e5a648bc0.centerY - 40>",
    "<NSLayoutConstraint:0x7f8e5a5be1a0 'UIView-Encapsulated-Layout-Height' V:[UIView:0x7f8e5a648bc0(414)]>",
    "<NSAutoresizingMaskLayoutConstraint:0x7f8e5a5be240 h=-&- v=-&- 'UIView-Encapsulated-Layout-Top' V:|-(0)-[UIView:0x7f8e5a648bc0]   (Names: '|':UITransitionView:0x7f8e5a5a8190 )>",
    "<NSAutoresizingMaskLayoutConstraint:0x7f8e5a5b82d0 h=--& v=--& UIButton:0x7f8e5a644f60'Button6'.midY == + 189>"
    

    Will attempt to recover by breaking constraint

    <NSLayoutConstraint:0x7f8e5a4e9510 UIButton:0x7f8e5a644f60'Button6'.centerY == UIView:0x7f8e5a648bc0.centerY - 40>
    

    ... should I update newButtonConstraintX / Y but I'm not understanding how I can do this ?

  • Chanchal Warde
    Chanchal Warde almost 7 years
    Hi @Rob, I am using the same approach. I have added constraints programmatically and now updating constants, but it's not updating view at.
  • Chanchal Warde
    Chanchal Warde almost 7 years
    Not updating frame of view .
  • Chanchal Warde
    Chanchal Warde almost 7 years
    I tried it with connecting constraints from storyboard and changing its constants from IBOutlet is working fine.