[Résolu] Problème de contrainte dans UITableViewCell dynamique

Bonjour,
J’ai un problème de contrainte dans un UITableViewCell dynamique.

[LayoutConstraints] 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:0x283f610e0 UIView:0x10473cab0.height == 20   (active)>",
    "<NSLayoutConstraint:0x283f58d20 'UISV-fill-proportionally' UIView:0x10473cab0.height == 0   (active)>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x283f58d20 'UISV-fill-proportionally' UIView:0x10473cab0.height == 0   (active)>

Je m’explique ma UITableViewCell est dynamique car une partie est construite via l’IB et je la modifie ajoutant des UIView dans une UIStackView en Swift.

Pour faire simple ma UITableViewCell se compose de 3 Parties : un HEADER, un CONTENT et un FOOTER.

En gros je modifie simplement le CONTENT qui est ma UIStackView qui a pour contrainte :

  • un Space Top (4) -> HEADER
  • un Space Bottom (4) -> FOOTER
  • un Leading (0) -> parentView
  • un Trailing (0) -> parentView
  • un Height (0) -> je pense que c’est ce qui pose problème

Et voilà le code qui me permet de modifier ma UITableViewCell (Simplifier) :

class MyTableViewCell: UITableViewCell {
    
    
    //MARK: - ATTRIBUTES
    var lecons : [Lecon]?
    
    
    //MARK: - IBOUTLETS
    
    @IBOutlet weak var ui_numSeanceLabel: UILabel! //HEADER
    @IBOutlet weak var ui_typeSeanceLabel: UILabel! //HEADER
    @IBOutlet weak var ui_dateSeanceLabel: UILabel! //HEADER
    @IBOutlet weak var ui_formateurSeanceLabel: UILabel! //HEADER
    @IBOutlet weak var ui_contentStackView: UIStackView! //CONTENT
    @IBOutlet weak var ui_commentLabel: UILabel! //FOOTER
    
    @IBOutlet weak var ui_contentStackViewHeightConstraint: NSLayoutConstraint! //Contrainte Height de la UIStackView
    
    //MARK: - METHODS

    //Fonction appeler dans la tableview
    func updateCell(){
        //reinitialise la UIStacKView
        ui_contentStackView.subviews.forEach { view in
            view.removeFromSuperview()
        }
        ui_contentStackViewHeightConstraint.constant = 0
        
        ui_numSeanceLabel.text =  " 3 "
        ui_typeSeanceLabel.text = " "
        
        ui_dateSeanceLabel.text = " 14 dec. 2018 "
        ui_formateurSeanceLabel.text = "par Paul"
        ui_commentLabel.text = "∅ - Aucune observation"

        setupContentViewWithLecon()
        
    }
    
    private func setupContentViewWithLecon(){
        
        guard let objectifs = lecon?.objectif else { return }
        guard !objectifs.isEmpty else { return }
        var heightContent : CGFloat = 0.0
        var numComp : Int = 0
        var numCompBefore : Int = -1
        objectifs.forEach{ competence in
            if let obj = competence.objectif {
                numComp = competence.numCompetence ?? 0
                let colorsOfSubviews = setColorObjectif(competenceId: numComp)
                if(numComp != numCompBefore) {
                    heightContent += 20.0
                    ui_contentStackViewHeightConstraint.constant = heightContent
                    let view = setCompetenceTilteItem(pText: "Competence \(numComp)", pBgColor: colorsOfSubviews.0, pTextColor: UIColor.white)
                    ui_contentStackView.addArrangedSubview(view)
                    view.translatesAutoresizingMaskIntoConstraints = false
                    ui_contentStackView.addConstraint(NSLayoutConstraint(item: view, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 20))
                    numCompBefore = numComp
                }
                heightContent += 80.0
                ui_contentStackViewHeightConstraint.constant = heightContent
                let compView = setCompetenceItem(pNumValid: competence.niveau ?? 0, pText: obj.description ?? "" ,pLetter: obj.titre ?? "0", pHasSeparator: true, pBgColor: colorsOfSubviews.1 , fgColor: colorsOfSubviews.0)
                ui_contentStackView.addArrangedSubview(compView)
                compView.translatesAutoresizingMaskIntoConstraints = false
                ui_contentStackView.addConstraint(NSLayoutConstraint(item: compView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 80))
                numCompBefore = numComp
            }
            
        }
    }
    
    private func setColorObjectif(competenceId : Int) -> (UIColor,UIColor) {
        let colors : (UIColor, UIColor)
        switch competenceId {
        case 2 :
            colors = (UIColor.C2Dark,UIColor.C2Light)
        case 3 :
            colors = (UIColor.C3Dark,UIColor.C3Light)
        case 4 :
            colors = (UIColor.C4Dark,UIColor.C4Light)
        default:
            colors = (UIColor.C1Dark,UIColor.C1Light)
        }
        return colors
    }
    
    private func setCompetenceTilteItem(pText: String, pBgColor: UIColor, pTextColor: UIColor) -> UIView{
        let view =  UIView()
        view.backgroundColor = pBgColor
        let label = UILabel()
        view.addSubview(label)
        label.translatesAutoresizingMaskIntoConstraints = false
        label.numberOfLines = 0
        label.text = pText
        label.textColor = pTextColor
        view.addConstraint(NSLayoutConstraint(item: label, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 8))
        view.addConstraint(NSLayoutConstraint(item: label, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: -8))
        view.addConstraint(NSLayoutConstraint(item: label, attribute: .centerY, relatedBy: .equal, toItem: view, attribute: .centerY, multiplier: 1, constant: 0))
        
        return view
    }
    
    private func setCompetenceItem(pNumValid: Int, pText: String, pLetter: String = "", pHasSeparator: Bool, pBgColor: UIColor, fgColor: UIColor) -> UIView {
        let view =  UIView()
        view.backgroundColor = pBgColor
        let bgImageCheckBox = UIImageView(image: #imageLiteral(resourceName: "bg_check_box"))
        let fgImageCheckBox = UIImageView(image: #imageLiteral(resourceName: "fg_check_box"))
        let imageCheckBox = MyCompetenceCheckBoxImageView()
        imageCheckBox.setState(niveau: pNumValid)
        imageCheckBox.setStateBegin(niveau: pNumValid)
        imageCheckBox.updateImage()
        let letterObjectif = UILabel()
        let textObjectif = UILabel()
        letterObjectif.numberOfLines = 0
        letterObjectif.text = pLetter
        textObjectif.numberOfLines = 0
        textObjectif.textAlignment = .left
        textObjectif.text = pText
        let hSeparator = UIView()
        hSeparator.backgroundColor = UIColor.gray
        
        view.addSubview(bgImageCheckBox)
        view.addSubview(fgImageCheckBox)
        view.addSubview(imageCheckBox)
        view.addSubview(letterObjectif)
        view.addSubview(textObjectif)
        view.addSubview(hSeparator)
        
        bgImageCheckBox.translatesAutoresizingMaskIntoConstraints = false
        fgImageCheckBox.translatesAutoresizingMaskIntoConstraints = false
        imageCheckBox.translatesAutoresizingMaskIntoConstraints = false
        letterObjectif.translatesAutoresizingMaskIntoConstraints = false
        textObjectif.translatesAutoresizingMaskIntoConstraints = false
        hSeparator.translatesAutoresizingMaskIntoConstraints = false
        
        //bgImageCheckBox Constraint
        view.addConstraint(NSLayoutConstraint(item: bgImageCheckBox, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 8))
        view.addConstraint(NSLayoutConstraint(item: bgImageCheckBox, attribute: .centerY, relatedBy: .equal, toItem: view, attribute: .centerY, multiplier: 1, constant: 0))
        view.addConstraint(NSLayoutConstraint(item: bgImageCheckBox, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 36))
        view.addConstraint(NSLayoutConstraint(item: bgImageCheckBox, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 36))
        //fgImageCheckBox Constraint
        view.addConstraint(NSLayoutConstraint(item: fgImageCheckBox, attribute: .leading, relatedBy: .equal, toItem: bgImageCheckBox, attribute: .leading, multiplier: 1, constant: 0))
        view.addConstraint(NSLayoutConstraint(item: fgImageCheckBox, attribute: .trailing, relatedBy: .equal, toItem: bgImageCheckBox, attribute: .trailing , multiplier: 1, constant: 0))
        view.addConstraint(NSLayoutConstraint(item: fgImageCheckBox, attribute: .top, relatedBy: .equal, toItem: bgImageCheckBox, attribute: .top, multiplier: 1, constant: 0))
        view.addConstraint(NSLayoutConstraint(item: fgImageCheckBox, attribute: .bottom, relatedBy: .equal, toItem: bgImageCheckBox, attribute: .bottom, multiplier: 1, constant: 0))
        //imageCheckBox Constraint
        view.addConstraint(NSLayoutConstraint(item: imageCheckBox, attribute: .leading, relatedBy: .equal, toItem: bgImageCheckBox, attribute: .leading, multiplier: 1, constant: 0))
        view.addConstraint(NSLayoutConstraint(item: imageCheckBox, attribute: .trailing, relatedBy: .equal, toItem: bgImageCheckBox, attribute: .trailing , multiplier: 1, constant: 0))
        view.addConstraint(NSLayoutConstraint(item: imageCheckBox, attribute: .top, relatedBy: .equal, toItem: bgImageCheckBox, attribute: .top, multiplier: 1, constant: 0))
        view.addConstraint(NSLayoutConstraint(item: imageCheckBox, attribute: .bottom, relatedBy: .equal, toItem: bgImageCheckBox, attribute: .bottom, multiplier: 1, constant: 0))
        
        //hSeparator Constraint
        view.addConstraint(NSLayoutConstraint(item: hSeparator, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 1))
        view.addConstraint(NSLayoutConstraint(item: hSeparator, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing , multiplier: 1, constant: -8))
        view.addConstraint(NSLayoutConstraint(item: hSeparator, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 8))
        view.addConstraint(NSLayoutConstraint(item: hSeparator, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0))
        
        //letterObjectif Constraint
        view.addConstraint(NSLayoutConstraint(item: letterObjectif, attribute: .leading, relatedBy: .equal, toItem: bgImageCheckBox, attribute: .trailing, multiplier: 1, constant: 4))
        //view.addConstraint(NSLayoutConstraint(item: letterObjectif, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute , multiplier: 1, constant: 8))
        view.addConstraint(NSLayoutConstraint(item: letterObjectif, attribute: .centerY, relatedBy: .equal, toItem: view, attribute: .centerY, multiplier: 1, constant: 0))
        letterObjectif.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 1000), for: UILayoutConstraintAxis.horizontal)
        //textObjectif Constraint
        view.addConstraint(NSLayoutConstraint(item: textObjectif, attribute: .leading, relatedBy: .equal, toItem: letterObjectif, attribute: .trailing, multiplier: 1, constant: 4))
        view.addConstraint(NSLayoutConstraint(item: textObjectif, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing , multiplier: 1, constant: -8))
        view.addConstraint(NSLayoutConstraint(item: textObjectif, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: 0))
        view.addConstraint(NSLayoutConstraint(item: textObjectif, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0))
        textObjectif.setContentHuggingPriority(UILayoutPriority(rawValue: 249), for: UILayoutConstraintAxis.horizontal)
        return view
    }
}

Je n’arrive pas a réglé ce warning, et je ne sais pas comment tracé correctement et savoir que quelle vue XCode parle :

        "<NSLayoutConstraint:0x283f610e0 UIView:0x10473cab0.height == 20   (active)>",
        "<NSLayoutConstraint:0x283f58d20 'UISV-fill-proportionally' UIView:0x10473cab0.height == 0   (active)>"

Si quelqu’un a une idée je suis preneur.

Merci d’avance pour vos réponses

Hello,

Pour debugger cela, il faut que tu donnes un identifiant à chaque contrainte que tu as dans la cellule.

25

L’identifiant va permettre dans ta console de ne plus avoir des choses comme 0x283f610e0 mais (1) ou (2) ou tout autre identifiant que tu auras mis.
Comme ça, c’est plus facile de voir ce qui est incriminé dans le warning.

Merci je vais tester ça

(
    "<NSLayoutConstraint:0x282c4c960 'titleCompetence_2__2018-12-03 17:00:00' tilteItem_C_2018-12-03 17....height == 20   (active, names: tilteItem_C_2018-12-03 17...:0x10e41a400 )>",
    "<NSLayoutConstraint:0x282c80690 'UISV-fill-proportionally' tilteItem_C_2018-12-03 17....height == 0   (active, names: tilteItem_C_2018-12-03 17...:0x10e41a400 )>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x282c80690 'UISV-fill-proportionally' tilteItem_C_2018-12-03 17....height == 0   (active, names: tilteItem_C_2018-12-03 17...:0x10e41a400 )>

je ne comprend pas comment changer 'UISV-fill-proportionally' puisque pour moi c’est elle qui pose problème.

Si tu commentes la ligne

ui_contentStackViewHeightConstraint.constant = 0

le warning disparaît ?

Cette ligne me permet de remettre ma cellule dans la config de base.
Si je la commente je garde la taille quand le UITableView réutilise la cellule.

du coup le conflit de contrainte viens de

ui_contentStackView.addConstraint(NSLayoutConstraint(item: view, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 20))

Du coup j’ai contourné le problème en gérant différemment les choses.

Merci pour ta réponse dans tout les cas