Search code examples
iosswiftuiviewextension-methodsnslayoutconstraint

NSLayoutConstraints not added when activated in extension function


I have convenience extension functions that allow me to add constraints to UIViews:

enum Constraint : String {
    
    case top = "topAnchor"
    case bottom = "bottomAnchor"
    case right = "rightAnchor"
    case left = "leftAnchor"
    case centerX = "centerXAnchor"
    case centerY = "centerYAnchor"
}
extension UIView {
    
    func constraintLeft(toLeft of: UIView, margin: CGFloat = 0) {
        self.deleteConstraints(.left)
        print(self.constraints) // prints [] 
        let left = leftAnchor.constraint(equalTo: of.leftAnchor, constant: margin)
        left.identifier = Constraint.left.rawValue
        NSLayoutConstraint.activate([left])
        setNeedsUpdateConstraints()
        print(self.constraints) // prints []
    }
    /* Other functions left out */
    
    func deleteConstraints(_ constraintsToRemove: Constraint...) {
        self.removeConstraints(self.constraints.filter({ c in
            guard c.identifier != nil else {
                return false
            }
            return constraintsToRemove.contains { constraint in
                constraint.rawValue.elementsEqual(c.identifier!)
            }
        }))
    }
    
}

However, when I am using these extension functions, the constraints do not fully work. When I add the constraints separately without calling the extension functions, it does work !

Here is my current usage of these functions:

func createButton(icon: String, label: String) -> UIView {
    let button = TransparentCardView()
    button.translatesAutoresizingMaskIntoConstraints = false
    
    let uiImageView = UIImageView(image: UIImage(named: icon))
    button.addSubview(uiImageView)
    uiImageView.translatesAutoresizingMaskIntoConstraints = false
    uiImageView.constraintCenterVertical(to: button) // works
    //uiImageView.constraintLeft(toLeft: button,margin: StyleConstants.contentPadding) // this does not work
    uiImageView.leftAnchor.constraint(equalTo: button.leftAnchor,constant: StyleConstants.contentPadding).isActive = true // this does
    
    let textView = UILabel()
    button.addSubview(textView)
    textView.translatesAutoresizingMaskIntoConstraints = false
    textView.constraintCenterVertical(to: button) // works
    //textView.constraintLeft(toRight: uiImageView,margin: 0) // This does not work!
    textView.leftAnchor.constraint(equalTo: uiImageView.rightAnchor,constant: StyleConstants.contentPadding).isActive = true // this does work!
    button.heightAnchor.constraint(equalToConstant: StyleConstants.More.CardViewSize).isActive = true
    return button
}

Edit: I have added additional print calls, after removing previous constraint and after activating the new constraint.

Constraints are printed as [] if I use my extension functions. but not if i constraint them normally.


Solution

  • I now know why Constraints disappear:

    The identifier needs to be unique in the whole View Hierarchy it seems.

    After procedurally generating identifier names, constraints do not disappear anymore.

    func createConstraintName(constraint:Constraint, from: UIView, to: UIView) -> String {
            var symbol = ""
            switch (constraint) {
            case .bottom: symbol = "___"
            case .centerX: symbol = "-X-"
            case .centerY: symbol = "-Y-"
            case .left: symbol = "|__"
            case .right: symbol = "__|"
            case .top: symbol = "‾‾‾"
            }
            
            return String(describing: from) + symbol + String(describing: to)
        }