Search code examples
swiftanchornslayoutconstraint

Unsatisfiable Constraint for NSLayoutConstraint changed programmatically - swift


I'm setting up a constraint in viewDidLoad and then changing its constant when the keyboard shows up. This is the initial set Up

bottomConstraint = NSLayoutConstraint(item: bottomBar, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0)
    view.addConstraint(bottomConstraint)

Then I change the constant when I receive a keyboard notification:

@objc func handleKeyboardNotification(notification: NSNotification){
    
    if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
        let keyboardRectangle = keyboardFrame.cgRectValue
        let keyboardHeight = keyboardRectangle.height
        
        let isKeyboardShowing = notification.name == UIResponder.keyboardWillShowNotification
        
        bottomConstraint?.constant = isKeyboardShowing ? -keyboardHeight : 0
        
        UIView.animate(withDuration:0.1, delay: 0 , options: .curveEaseOut , animations: {
            self.view.layoutIfNeeded()
        } , completion: {(completed) in
        })
    }
}

It's interesting because I'm only changing the constant and not adding another constraint. Nevertheless I receive this warning in console:

Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want. 
Try this: 
    (1) look at each constraint and try to figure out which you don't expect; 
    (2) find the code that added the unwanted constraint or constraints and fix it. 
(
"<NSLayoutConstraint:0x282ae4ff0 UIView:0x109f11110.bottom == UIView:0x109f13240.bottom   (active)>",
"<NSLayoutConstraint:0x282ae8050 UIView:0x109f11110.bottom == UIView:0x109f13240.bottom - 291   (active)>"
 )

Which basically states that the constraints I have don't work well togheter. I don't know what I'm doing wrong.

EDIT: This is the code I use to add the bottom bar programmatically:

let bottomBar:UIView = {
    let v = UIView()
    v.translatesAutoresizingMaskIntoConstraints = false
    return v
}()

In ViewdidLoad()

view.addSubview(bottomBar)
bottomBar.addSubview(fontView)
bottomBar.addSubview(colorPicker)
fontView.pin(to: bottomBar)
colorPicker.pin(to: bottomBar)

 func setUpConstraints(){
 NSLayoutConstraint.activate([
        bottomBar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
        bottomBar.trailingAnchor.constraint(equalTo: view.trailingAnchor),
        bottomBar.heightAnchor.constraint(equalToConstant: 70),
 ])
}

Solution

  • Attempting to make it a bit easier for you...

    Start simple.

    This code creates a text field near the top, and a red "bottomBar" view at the bottom. When the keyboard shows or hides, the bottomBar's bottom constraint constant is updated (tap anywhere on the view to dismiss the keyboard):

    class ConstraintTestViewController: UIViewController {
        
        let bottomBar = UIView()
        
        var bottomConstraint: NSLayoutConstraint!
        
        override func viewDidLoad() {
            super.viewDidLoad()
        
            // add a text field
            let tf = UITextField()
            tf.borderStyle = .roundedRect
            tf.translatesAutoresizingMaskIntoConstraints = false
            
            bottomBar.translatesAutoresizingMaskIntoConstraints = false
            bottomBar.backgroundColor = .red
            
            view.addSubview(tf)
            view.addSubview(bottomBar)
    
            let g = view.safeAreaLayoutGuide
            NSLayoutConstraint.activate([
                
                // constrain text field 80-pts from Top, Width: 240, centerX
                tf.topAnchor.constraint(equalTo: g.topAnchor, constant: 80.0),
                tf.widthAnchor.constraint(equalToConstant: 240.0),
                tf.centerXAnchor.constraint(equalTo: g.centerXAnchor),
                
                // constrain bottomBar Leading and Trailing, Height: 70-pts
                bottomBar.leadingAnchor.constraint(equalTo: g.leadingAnchor),
                bottomBar.trailingAnchor.constraint(equalTo: g.trailingAnchor),
                bottomBar.heightAnchor.constraint(equalToConstant: 70.0),
                
            ])
            
            // create and add the bottom constraint
            bottomConstraint = NSLayoutConstraint(item: bottomBar, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0)
            view.addConstraint(bottomConstraint)
    
            // or, use more modern syntax...
            //bottomConstraint = bottomBar.bottomAnchor.constraint(equalTo: g.bottomAnchor)
            //bottomConstraint.isActive = true
            
            // keyboard show/hide notifications
            NotificationCenter.default.addObserver(self,
                                                   selector: #selector(self.handleKeyboardNotification(notification:)),
                                                   name: UIResponder.keyboardWillShowNotification,
                                                   object: nil)
            
            NotificationCenter.default.addObserver(self,
                                                   selector: #selector(self.handleKeyboardNotification(notification:)),
                                                   name: UIResponder.keyboardWillHideNotification,
                                                   object: nil)
    
            // add a "tap on the view to dismiss the keyboard" gesture
            let t = UITapGestureRecognizer(target: self, action: #selector(self.didTap(_:)))
            view.addGestureRecognizer(t)
            
        }
        
        @objc func handleKeyboardNotification(notification: NSNotification){
            
            if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
                let keyboardRectangle = keyboardFrame.cgRectValue
                let keyboardHeight = keyboardRectangle.height
                
                let isKeyboardShowing = notification.name == UIResponder.keyboardWillShowNotification
                
                bottomConstraint.constant = isKeyboardShowing ? -keyboardHeight : 0
                
                UIView.animate(withDuration:0.1, delay: 0 , options: .curveEaseOut , animations: {
                    self.view.layoutIfNeeded()
                } , completion: {(completed) in
                })
            }
        }
        
        @objc func didTap(_ g: UITapGestureRecognizer) -> Void {
            view.endEditing(true)
        }
        
    }
    

    If this runs without auto-layout errors / warnings (which it will), then start adding in your other UI elements (and supporting code) one at a time. If / when you get the constraint conflict again, you'll know exactly where you went wrong.