22

I am trying to find the top constraint of the view in code. The top constraint is added in storyboard, and I don't want to use an IBOutlet.

Logging the value of the firstAttribute in the following code seems to always return a constraint of type NSLayoutAttributeHeight. Any idea how I could reliably find a top constraint of a view in code?

NSLayoutConstraint *topConstraint;

for (NSLayoutConstraint *constraint in self.constraints) {
    if (constraint.firstAttribute == NSLayoutAttributeTop) {
        topConstraint = constraint;
        break;
    }
}

8 Answers 8

32

Instead of iterating through self.constraints, you should iterate through self.superview.constraints.

The self.constraints only contain constraints related to just the view (e.g. height and width constraints).

Here's a code example of what this might look like:

- (void)awakeFromNib
{
  [super awakeFromNib];

  if (!self.topConstraint) {
    [self findTopConstraint];
  }
}

- (void)findTopConstraint
{
  for (NSLayoutConstraint *constraint in self.superview.constraints) {
    if ([self isTopConstraint:constraint]) {
      self.topConstraint = constraint;
      break;
    }
  }
}

- (BOOL)isTopConstraint:(NSLayoutConstraint *)constraint
{
  return  [self firstItemMatchesTopConstraint:constraint] ||
          [self secondItemMatchesTopConstraint:constraint];
}

- (BOOL)firstItemMatchesTopConstraint:(NSLayoutConstraint *)constraint
{
  return constraint.firstItem == self && constraint.firstAttribute == NSLayoutAttributeTop;
}

- (BOOL)secondItemMatchesTopConstraint:(NSLayoutConstraint *)constraint
{
  return constraint.secondItem == self && constraint.secondAttribute == NSLayoutAttributeTop;
}
Sign up to request clarification or add additional context in comments.

5 Comments

Thanks enumerating through parent's constraints did the trick
You can make this more accurate and shorter by changing findTopConstraint to use the array returned by the UIView method constraintsAffectingLayoutForAxis: This eliminates constraints in the orthogonal axis.
@uchuugaka this statement from the Apple docs gives me pause about using constraintsAffectingLayoutForAxis:, This method should only be used for debugging constraint-based layout. No application should ship with calls to this method as part of its operation.
AFAIK, there isn't any issue with enumerating through the constraints array, however.
Hadn't noticed that. Good find of the doc note.
26

I usually set an identifier of a required constraint in the IB and then find it in the code like this (Swift):

if let index = constraints.index(where: { $0.identifier == "checkmarkLeftMargin" }) {
    checkmarkImageViewLeftMargin = constraints[index]
}

OR by @Tim Vermeulen

checkmarkImageViewLeftMargin = constraints.first { $0.identifier == "checkmarkLeftMargin" }

4 Comments

if let constraint = constraints.filter{ $0.identifier == "checkmarkLeftMargin" }.first { } :)
@SeanDev the loop break once the necessary constraint fount. In the first example all constraints are being searched with a specified identifier. Of course in real conditions it won't take too much time but still.
@aryaxt There's first(where:) for that!
If you do all of that in interface builder, you can also set up an Outlet.
7

Based on @Igor answer, I changed a bit itemMatch method to consider when first item or second item is not a UIView. For example when constraint a UIView top to safe area top.

extension UIView {
    func findConstraint(layoutAttribute: NSLayoutConstraint.Attribute) -> NSLayoutConstraint? {
        if let constraints = superview?.constraints {
            for constraint in constraints where itemMatch(constraint: constraint, layoutAttribute: layoutAttribute) {
                return constraint
            }
        }
        return nil
    }

    func itemMatch(constraint: NSLayoutConstraint, layoutAttribute: NSLayoutConstraint.Attribute) -> Bool {
        let firstItemMatch = constraint.firstItem as? UIView == self && constraint.firstAttribute == layoutAttribute
        let secondItemMatch = constraint.secondItem as? UIView == self && constraint.secondAttribute == layoutAttribute
        return firstItemMatch || secondItemMatch
    }
}

Comments

5

Using swift and UIView extension

extension UIView {
    func findConstraint(layoutAttribute: NSLayoutAttribute) -> NSLayoutConstraint? {
        if let constraints = superview?.constraints {
            for constraint in constraints where itemMatch(constraint: constraint, layoutAttribute: layoutAttribute) {
                return constraint
            }
        }
        return nil
    }

    func itemMatch(constraint: NSLayoutConstraint, layoutAttribute: NSLayoutAttribute) -> Bool {
        if let firstItem = constraint.firstItem as? UIView, let secondItem = constraint.secondItem as? UIView {
            let firstItemMatch = firstItem == self && constraint.firstAttribute == layoutAttribute
            let secondItemMatch = secondItem == self && constraint.secondAttribute == layoutAttribute
            return firstItemMatch || secondItemMatch
        }
        return false
    }
}

Comments

2

Set the identifier in the inspector in Xcode. That's what it's for. You name it. If that's not enough you create the IBOutlet.

8 Comments

This isn't ideal for every situation. For example, if you're developing a library meant to be consumed/subclassed/etc by other developers, you might not want them to have to explicitly set an IBOutlet to the top constraint.
Why not ? If you're not going to do it then they have to. Otherwise you name it using the identifier. But an explicit property is clear and easy.
I don't want to use an identifier, otherwise I would use an IBOUtlet. I want this to work dynamically
Well dynamic means creating things that are identifiable easily. There's a reason UIKit and AppKit have identifier properties everywhere these days. Without this or an outlet then you are only able to guess by looking at the properties of every constraint affecting layout in a particular orientation for a given view.
Using identifier or outlet is not dynamic. Using code and going through constraints is not guess work, look at the example provided above very reliable. Using an identifier where you have to remember what the identifier name should be IS guess-work. You use an identifier when you are writing code for a specific case with a specific identifier (not dynamic)
|
0

I write a small extension in Swift:

extension UIButton {
    var topConstraints: [NSLayoutConstraint]? {
        return self.constraints.filter( { ($0.firstItem as? UIButton == self && $0.firstAttribute == .top) || ($0.secondItem as? UIButton == self && $0.secondAttribute == .top) })
    }
}

Comments

0

Here's a one-liner extension method, based on @Igor's approach:

extension UIView{

    func constraint(for layoutAttribute: NSLayoutConstraint.Attribute) -> NSLayoutConstraint? {
        return superview?.constraints.first { itemMatch(constraint: $0, layoutAttribute: layoutAttribute) }
    }

    private func itemMatch(constraint: NSLayoutConstraint, layoutAttribute: NSLayoutConstraint.Attribute) -> Bool {
        if let firstItem = constraint.firstItem as? UIView, let secondItem = constraint.secondItem as? UIView {
            let firstItemMatch = firstItem == self && constraint.firstAttribute == layoutAttribute
            let secondItemMatch = secondItem == self && constraint.secondAttribute == layoutAttribute
            return firstItemMatch || secondItemMatch
        }
        return false
    }
}

[ I also made the method signature style to match Swift 3...5 style e.g.:

constraint(for:) instead of findConstraint(layoutAttribute:). ]

Comments

0
 func topConstraint() -> NSLayoutConstraint? {
    return superview?.constraints.first {$0.firstAttribute == .top}
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.