Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to "find" your own constraint?

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?

It's perhaps worth noting this question


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.)

If you are new to constraints, note that .constraints just gives you the ends stored "there".

like image 890
Fattie Avatar asked Nov 01 '17 11:11

Fattie


People also ask

How do you set constraints?

To create a constraint between two views, Control-click one of the views and drag to the other. When you release the mouse, Interface Builder displays a HUD menu with a list of possible constraints.

What is constraint to margin?

By unchecking "constraints to margin", you are adding constraints, meaning your interface will react correctly to changes in size or orientation.

How do constraints work in IOS?

To create constraints select the button and click the Align icon in the auto layout menu. A popover menu will appear, check both “Horizontal in container” and “Vertically in container” options to center the button on the screen. Then click the “Add 2 Constraints” button. Run the application.


1 Answers

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

To repeat. For constraints which are between two views. iOS does, in fact, always store them in the lowest common ancestor. Thus, a constraint of a view can always be found by searching all ancestors of the view.

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

like image 148
stakri Avatar answered Sep 20 '22 14:09

stakri