Sam Shaikh
Sam Shaikh

Reputation: 1646

Get reference to NSLayoutConstraint using Identifier set in storyboard

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?

Upvotes: 22

Views: 22501

Answers (6)

Isaiah Turner
Isaiah Turner

Reputation: 2662

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
    }

Upvotes: 14

software evolved
software evolved

Reputation: 4352

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!  :(    
}

Upvotes: 1

Venk
Venk

Reputation: 5955

In Swift 3,

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

Upvotes: 26

Yarm
Yarm

Reputation: 1328

For resizing a set of buttons within a common view container this works. Each subview/button must use a common identifier (e.g. "height").

@IBAction func btnPressed(_ sender: UIButton) {
    for button in self.btnView.subviews{
        for constraint in button.constraints{
            if constraint.identifier == "height"{
                constraint.constant = constraint.constant == 0 ? 30:0
            }
        }
    }
    UIView.animate(withDuration: 0.3) { () -> Void in
        self.view.layoutIfNeeded()
    }
}

Upvotes: 0

quemeful
quemeful

Reputation: 9868

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
}

Upvotes: 12

strwils
strwils

Reputation: 697

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++;
}

Upvotes: 10

Related Questions