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;
    }
}
aryaxt
  • 76,198
  • 92
  • 293
  • 442

8 Answers8

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;
}
JRG-Developer
  • 12,454
  • 8
  • 55
  • 81
  • Thanks enumerating through parent's constraints did the trick – aryaxt Nov 09 '14 at 22:59
  • 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 Nov 10 '14 at 00:27
  • 1
    @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.` – JRG-Developer Nov 11 '14 at 01:47
  • AFAIK, there isn't any issue with enumerating through the `constraints` array, however. – JRG-Developer Nov 11 '14 at 01:48
  • 1
    Hadn't noticed that. Good find of the doc note. – uchuugaka Nov 11 '14 at 03:05
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" }
SoftDesigner
  • 5,640
  • 3
  • 58
  • 47
  • 3
    `if let constraint = constraints.filter{ $0.identifier == "checkmarkLeftMargin" }.first { }` :) – aryaxt Mar 02 '16 at 13:46
  • 1
    @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. – SoftDesigner Apr 26 '16 at 15:23
  • 3
    @aryaxt There's `first(where:)` for that! – Tim Vermeulen Jan 31 '17 at 11:26
  • 2
    If you do all of that in interface builder, you can also set up an Outlet. – paxos Nov 11 '18 at 20:41
6

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
    }
}
Mohammad Eslami
  • 536
  • 1
  • 6
  • 17
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
    }
}
Igor
  • 4,235
  • 3
  • 34
  • 32
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.

uchuugaka
  • 12,679
  • 6
  • 37
  • 55
  • 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. – JRG-Developer Nov 09 '14 at 22:11
  • 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. – uchuugaka Nov 09 '14 at 22:13
  • I don't want to use an identifier, otherwise I would use an IBOUtlet. I want this to work dynamically – aryaxt Nov 09 '14 at 22:57
  • 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. – uchuugaka Nov 09 '14 at 23:20
  • 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) – aryaxt Nov 09 '14 at 23:32
  • It is guess work as number of constraints with same objects is non-deterministic and the number of objects to which your object could be constrained directly is non-deterministic. You're assuming there will always be one and that it will always be to the direct parent superview. – uchuugaka Nov 10 '14 at 00:23
  • There will always be one top constraint from a view to its superview – aryaxt Nov 10 '14 at 17:13
  • Then it's not dynamic? That is, the API doesn't enforce that. There can be multiple constraints by design. I may be missing something, but it seems to me most judicious to simply declare it and be done with it. It's verbose but much simpler to to introspect – uchuugaka Nov 10 '14 at 18:37
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) })
    }
}
MrHim
  • 401
  • 4
  • 16
Serhii Londar
  • 231
  • 1
  • 18
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:). ]

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