Get reference to NSLayoutConstraint using Identifier set in storyboard

21,265

Solution 1

You may wish to extrapolate the logic provided in previous answer into an extension.

extension UIView {
    /// Returns the first constraint with the given identifier, if available.
    ///
    /// - Parameter identifier: The constraint identifier.
    func constraintWithIdentifier(_ identifier: String) -> NSLayoutConstraint? {
        return self.constraints.first { $0.identifier == identifier }
    }
}

You can then access any constraint anywhere using:

myView.constraintWithIdentifier("myConstraintIdentifier")

Edit: Just for kicks, using the above code, here's an extension that finds all the constraints with that identifier in all the child UIViews. Had to change the function to return an array instead of the first constraint found.

    func constraintsWithIdentifier(_ identifier: String) -> [NSLayoutConstraint] {
        return self.constraints.filter { $0.identifier == identifier }
    }

    func recursiveConstraintsWithIdentifier(_ identifier: String) -> [NSLayoutConstraint] {
        var constraintsArray: [NSLayoutConstraint] = []

        var subviews: [UIView] = [self]

        while !subviews.isEmpty {
            constraintsArray += subviews.flatMap { $0.constraintsWithIdentifier(identifier) }
            subviews = subviews.flatMap { $0.subviews }
        }

        return constraintsArray
    }

Solution 2

In Swift 3,

 let filteredConstraints = button.constraints.filter { $0.identifier == "identifier" }
 if let yourConstraint = filteredConstraints.first {
      // DO YOUR LOGIC HERE
 }

Solution 3

Swift 3

I wrote a quick NSView extension which handles this nicely.

extension NSView {
    func constraint(withIdentifier: String) -> NSLayoutConstraint? {
        return self.constraints.filter { $0.identifier == withIdentifier }.first
    }
}

Usage:

if let c = button.constraint(withIdentifier: "my-button-width") {
    // do stuff with c
}

Solution 4

I assume you have an outlet set up for the button so you have an available reference to it. So first, retrieve the view's constraints from your button. Then loop through the array and on each iteration compare the identifer property of each constraint with the value you entered in Interface Builder. Looks like you are coding in Objective-C, so Objective-C code sample is below. Change @"identifier" to whatever value you set in Interface Builder.

NSArray *constraints = [button constraints];
int count = [constraints count];
int index = 0;
BOOL found = NO;

while (!found && index < count) {
    NSLayoutConstraint *constraint = constraints[index];
    if ( [constraint.identifier isEqualToString:@"identifier"] ) {
        //save the reference to constraint
        found = YES;
    }

    index++;
}

Solution 5

I simplified the search and added in walking up the ancestor list of superviews:

+ (NSLayoutConstraint *) findConstraintNamed:(NSString *)identifierTarget startWith:(UIView *)aView;    
{    
    // walk upward from item through ancestors  
    UIView *currentView = aView;            

    while (currentView != nil)    
    {    
        NSArray *constraints = [currentView constraints];    
        NSInteger foundConstraintIndex = [constraints indexOfObjectPassingTest:^BOOL(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {   
            return [((NSLayoutConstraint *)obj).identifier isEqualToString:identifierTarget];    
        }];


        if (foundConstraintIndex != NSNotFound)    
        {    
            return constraints[foundConstraintIndex];    
        }                

        // not found, so walk up the ancestor chain    
        currentView = currentView.superview;    
    }    

    return nil;  // not found anywhere in item or ancestors!  :(    
}
Share:
21,265

Related videos on Youtube

Sam Shaikh
Author by

Sam Shaikh

Updated on July 16, 2020

Comments

  • Sam Shaikh
    Sam Shaikh almost 4 years

    I was setting the constraints of a button using a storyboard. I saw an option, "Identifier" in the constraint's properties.

    Screenshot of constraint's properties

    I want to make a reference to this constraint, to change its value in code, to move an object.

    How can I get a reference to this NSLayoutContraint from this Identifier.

    I read the documentation, it was written like this

    @interface NSLayoutConstraint (NSIdentifier)
    /* For ease in debugging, name a constraint by setting its identifier, which will be printed in the constraint's description.
     Identifiers starting with UI and NS are reserved by the system.
     */
    @property (nullable, copy) NSString *identifier NS_AVAILABLE_IOS(7_0);
    
    @end
    

    So I realized that it's for debugging purposes.

    What if I want to get it and use it? I saw this link, but no satisfactory answer was given: How to get NSLayoutConstraint's identifier by Its pointer?

    • Teja Nandamuri
      Teja Nandamuri over 8 years
    • Paulw11
      Paulw11 over 8 years
      It much simpler to link the constraint to an IBOutlet property in your class. Drag from "new referencing outlet" just as you would from any other element such as a label or text field.
    • Sam Shaikh
      Sam Shaikh over 8 years
      Thank you for suggestion, but this thing I am using, so wanted to write smaller piece of code, so I am searching a better way. @Paulw11
    • Paulw11
      Paulw11 over 8 years
      The outlet is a zero code solution and is Apples recommended approach. Searching the constraints array is more expensive, more code and more fragile.
    • Dima Deplov
      Dima Deplov over 7 years
      @Paulw11 what if I need to set a new constraint to an outlet? The outlet will be nil. You can't do it, but for example you need to change multiplier of the constraint and this parameter set only in init phase.
    • Paulw11
      Paulw11 over 7 years
      @flinth I don't understand your comment. Why would the outlet be nil? If you are using an IBOutlet then you link it from your storyboard and it will be non-nil by the time it is appropriate to modify the constraint (viewWillLayoutSubviews for example). If you are creating the constraint in code then use a property that isn't an IBOutlet and either make it an optional and unwrap it before you modify it or initialise it before you modify it. (E.g. Initialise it in viewDidLoad and modify it in viewWillLayoutSubviews or viewWillAppear)
    • Dima Deplov
      Dima Deplov over 7 years
      @Paulw11 I'm facing this situation right now. I have an view with vertical align to center by Y. I set it with multiplier != 1 and constant = 0. Now I need to animate it. And I need to use multiplier (for proportional animation on different device sizes). I decided to set new constraint to the IBOutlet (I forgot it's not allowed, which I hoped just replace the old constraint with the new one). Right now, I'm adding new constraint while original IBOutlet constraint active = false. Like you proposed I come to this solution too. Will test it. Hope my comment is clear now. Thx for response.
    • Dima Deplov
      Dima Deplov over 7 years
      @Paulw11 my approach failed. If you set original constraint activity as false and add new constraint (same items and attributes, just different multiplier), view just change it's position, no animation here. Sad, but I need to calculate constant part for proper animation. Hope it will help somebody.
    • Paulw11
      Paulw11 over 7 years
      You should ask your own question and post your code. You can certainly animate constraint enable/disable
    • taber
      taber about 5 years
      @Sam Shaikh you should accept one of these fine answers! :D
  • Ken Thomases
    Ken Thomases over 8 years
    The constraint won't be stored on the button itself. Constraints must be installed on a common ancestor view of all of the views involved in the constraint. It will probably be installed on the closest common ancestor (which may be one of the involved views, itself) but, in theory, could be on a more distant ancestor. For the constraint shown in the question, it will be on the button's superview. That's part of why searching by identifier is such a poor approach.
  • strwils
    strwils over 8 years
    @Ken Thomases Yes, agreed it is a poor choice. However, iOS Developer insisted that he/she would like to retrieve it from code for whatever reason. The button itself was used to illustrate logic, but yes, the constraint may be held by the view itself (width or height) or a parent view depending upon the type of constraint.
  • Sam Shaikh
    Sam Shaikh over 8 years
    @KenThomases, Thanks for answering, and comments, but I think its poor to iterate whole constraints and fine one from array. Its like I make a Group of Constraints and find them from group. Apple must allow users to find constraint from its identifier.
  • Ken Thomases
    Ken Thomases over 8 years
    @iOSDeveloper, this isn't my answer; it's strwils'. I don't follow what you mean with the "group" stuff. But regarding "Apple must allow users to find constraint from its identifier", you will find that that just isn't so. There is no mechanism to find a constraint by its identifier other than this manual search approach because that's not how you're supposed to do it. Follow the advice from Paulw11 in the comments: use an outlet.
  • NoodleOfDeath
    NoodleOfDeath over 7 years
    shouldn't you just make an @IBOutlet that is an NSLayoutConstraint in the class and link it?