Saturday, November 11, 2017

How to “find” your own constraint?

Leave a Comment

Say I have a UIView,

 class CleverView: UIView 

In the custom class, I want to do this:

func changeWidth() {    let c = ... find my own layout constraint, for "width"   c.constant = 70 * Gameinfo.ImportanceOfEnemyFactor } 

Similarly I wanna be able to "find" like that, the constraint (or I guess, all constraints, there could be more than one) attached to one of the four edges.

So, to look through all the constraints attached to me, and find any width/height ones, or indeed any relevant to a given (say, "left") edge.

Any ideas?


Please, note that (obviously) I am asking how to do this dynamically/programmatically.

(Yes, you can say "link to the constraint" or "use an ID" - the whole point of the QA is how to find them on the fly and work dynamically.)

3 Answers

Answers 1

There are really two cases:

  1. Constraints regarding a view's size or relations to descendant views are saved in itself
  2. Constraints between two views are saved in the views' lowest common ancestor

Thus, we need to check the view itself and all its superviews for constraints. One approach could be:

extension UIView {      // retrieves all constraints that mention the view     func getAllConstraints() -> [NSLayoutConstraint] {          // array will contain self and all superviews         var views = [self]          // get all superviews         var view = self         while let superview = view.superview {             views.append(superview)             view = superview         }          // transform views to constraints and filter only those         // constraints that include the view itself         return views.flatMap({ $0.constraints }).filter { constraint in             return constraint.firstItem as? UIView == self ||                 constraint.secondItem as? UIView == self         }     } } 

You can apply all kinds of filters after getting all constraints about a view, and I guess that's the most difficult part. Some examples:

extension UIView {      // Example 1: Get all width constraints involving this view     // We could have multiple constraints involving width, e.g.:     // - two different width constraints with the exact same value     // - this view's width equal to another view's width     // - another view's height equal to this view's width (this view mentioned 2nd)     func getWidthConstraints() -> [NSLayoutConstraint] {         return getAllConstraints().filter( {             ($0.firstAttribute == .width && $0.firstItem as? UIView == self) ||             ($0.secondAttribute == .width && $0.secondItem as? UIView == self)         } )     }      // Example 2: Change width constraint(s) of this view to a specific value     // Make sure that we are looking at an equality constraint (not inequality)     // and that the constraint is not against another view     func changeWidth(to value: CGFloat) {          getAllConstraints().filter( {             $0.firstAttribute == .width &&                 $0.relation == .equal &&                 $0.secondAttribute == .notAnAttribute         } ).forEach( {$0.constant = value })     }      // Example 3: Change leading constraints only where this view is     // mentioned first. We could also filter leadingMargin, left, or leftMargin     func changeLeading(to value: CGFloat) {         getAllConstraints().filter( {             $0.firstAttribute == .leading &&                 $0.firstItem as? UIView == self         }).forEach({$0.constant = value})     } } 

// edit: Enhanced examples and clarified their explanations in comments

Answers 2

I guess you can work with constraints property of UIView. constraints basically returns an array of constraint directly assigned to UIView. It will not be able to get you the constraints held by superview such as leading, trailing, top or bottom but width and height constraints are held by View itself. For superview's constraints, you can loop through superview's constraints. Lets say the clever view has these constraints:

enter image description here

class CleverView: UIView {      func printSuperViewConstriantsCount() {         var c = 0         self.superview?.constraints.forEach({ (constraint) in             guard constraint.secondItem is CleverView || constraint.firstItem is CleverView else {                 return             }             c += 1             print(constraint.firstAttribute.toString())         })         print("superview constraints:\(c)")     }      func printSelfConstriantsCount() {         self.constraints.forEach { (constraint) in             return print(constraint.firstAttribute.toString())         }         print("self constraints:\(self.constraints.count)")     } } 

Output:

top
leading
trailing
superview constraints:3
height
self constraints:1

Basically, you can look at NSLayoutConstraint class to get the info out about a particular constraint.

To print the name of constraints, we can use this extension

extension NSLayoutAttribute {     func toString() -> String {         switch self {         case .left:             return "left"         case .right:             return "right"         case .top:             return "top"         case .bottom:             return "bottom"         case .leading:             return "leading"         case .trailing:             return "trailing"         case .width:             return "width"         case .height:             return "height"         case .centerX:             return "centerX"         case .centerY:             return "centerY"         case .lastBaseline:             return "lastBaseline"         case .firstBaseline:             return "firstBaseline"         case .leftMargin:             return "leftMargin"         case .rightMargin:             return "rightMargin"         case .topMargin:             return "topMargin"         case .bottomMargin:             return "bottomMargin"         case .leadingMargin:             return "leadingMargin"         case .trailingMargin:             return "trailingMargin"         case .centerXWithinMargins:             return "centerXWithinMargins"         case .centerYWithinMargins:             return "centerYWithinMargins"         case .notAnAttribute:             return "notAnAttribute"         }     } } 

Answers 3

I use the following extension to examine all constraints:

extension NSLayoutConstraint {     class func listConstraints (_ v:UIView?) {         var v = v         if v == nil {             v = UIApplication.shared.keyWindow         }         for vv in v!.subviews {             let arr1 = vv.constraintsAffectingLayout(for:.horizontal)             let arr2 = vv.constraintsAffectingLayout(for:.vertical)             NSLog("\n\n%@\nH: %@\nV:%@", vv, arr1, arr2);             if vv.subviews.count > 0 {                 self.listConstraints(vv)             }         }     } } 

Just call that with NSLayoutConstraint.listConstraints(nil) or NSLayoutConstraint.listConstraints(myView). That lists all constraints, each one telling all details about itself, so it's pretty easy to figure out which one is causing any particular phenomenon. Instead of the NSLog, you can easily add some sort of code to do something else.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment