You have to remove the old NSLayoutConstraint and replace it with a new one to modify it. However, since you know you want to change the multiplier, you can just change the constant by multiplying it yourself when changes are needed which is often less code.
Since multiplier is a read-only property and you can't change it, you need to replace the constraint with its modified clone.
Multiplier is there for creating Proportional Constraint. Auto Layout calculates the first item's attribute to be the product of the second item's attribute and this multiplier . Any value other than 1 creates a proportional constraint.
Here is an NSLayoutConstraint extension in Swift that makes setting a new multiplier pretty easy:
In Swift 3.0+
import UIKit
extension NSLayoutConstraint {
/**
Change multiplier constraint
- parameter multiplier: CGFloat
- returns: NSLayoutConstraint
*/
func setMultiplier(multiplier:CGFloat) -> NSLayoutConstraint {
NSLayoutConstraint.deactivate([self])
let newConstraint = NSLayoutConstraint(
item: firstItem,
attribute: firstAttribute,
relatedBy: relation,
toItem: secondItem,
attribute: secondAttribute,
multiplier: multiplier,
constant: constant)
newConstraint.priority = priority
newConstraint.shouldBeArchived = self.shouldBeArchived
newConstraint.identifier = self.identifier
NSLayoutConstraint.activate([newConstraint])
return newConstraint
}
}
Demo usage:
@IBOutlet weak var myDemoConstraint:NSLayoutConstraint!
override func viewDidLoad() {
let newMultiplier:CGFloat = 0.80
myDemoConstraint = myDemoConstraint.setMultiplier(newMultiplier)
//If later in view lifecycle, you may need to call view.layoutIfNeeded()
}
If you have only have two sets of multipliers that need to be applied, from iOS8 onwards you can add both sets of constraints and decide which should be active at any time:
NSLayoutConstraint *standardConstraint, *zoomedConstraint;
// ...
// switch between constraints
standardConstraint.active = NO; // this line should always be the first line. because you have to deactivate one before activating the other one. or they will conflict.
zoomedConstraint.active = YES;
[self.view layoutIfNeeded]; // or using [UIView animate ...]
Swift 5.0 version
var standardConstraint: NSLayoutConstraint!
var zoomedConstraint: NSLayoutConstraint!
// ...
// switch between constraints
standardConstraint.isActive = false // this line should always be the first line. because you have to deactivate one before activating the other one. or they will conflict.
zoomedConstraint.isActive = true
self.view.layoutIfNeeded() // or using UIView.animate
The multiplier
property is read only. You have to remove the old NSLayoutConstraint and replace it with a new one to modify it.
However, since you know you want to change the multiplier, you can just change the constant by multiplying it yourself when changes are needed which is often less code.
A helper function I use to change multiplier of an existing layout constraint. It creates and activates a new constraint and deactivates the old one.
struct MyConstraint {
static func changeMultiplier(_ constraint: NSLayoutConstraint, multiplier: CGFloat) -> NSLayoutConstraint {
let newConstraint = NSLayoutConstraint(
item: constraint.firstItem,
attribute: constraint.firstAttribute,
relatedBy: constraint.relation,
toItem: constraint.secondItem,
attribute: constraint.secondAttribute,
multiplier: multiplier,
constant: constraint.constant)
newConstraint.priority = constraint.priority
NSLayoutConstraint.deactivate([constraint])
NSLayoutConstraint.activate([newConstraint])
return newConstraint
}
}
Usage, changing multiplier to 1.2:
constraint = MyConstraint.changeMultiplier(constraint, multiplier: 1.2)
Objective-C Version for Andrew Schreiber answer
Create the category for NSLayoutConstraint Class and add the method in .h file like this
#import <UIKit/UIKit.h>
@interface NSLayoutConstraint (Multiplier)
-(instancetype)updateMultiplier:(CGFloat)multiplier;
@end
In the .m file
#import "NSLayoutConstraint+Multiplier.h"
@implementation NSLayoutConstraint (Multiplier)
-(instancetype)updateMultiplier:(CGFloat)multiplier {
[NSLayoutConstraint deactivateConstraints:[NSArray arrayWithObjects:self, nil]];
NSLayoutConstraint *newConstraint = [NSLayoutConstraint constraintWithItem:self.firstItem attribute:self.firstAttribute relatedBy:self.relation toItem:self.secondItem attribute:self.secondAttribute multiplier:multiplier constant:self.constant];
[newConstraint setPriority:self.priority];
newConstraint.shouldBeArchived = self.shouldBeArchived;
newConstraint.identifier = self.identifier;
newConstraint.active = true;
[NSLayoutConstraint activateConstraints:[NSArray arrayWithObjects:newConstraint, nil]];
//NSLayoutConstraint.activateConstraints([newConstraint])
return newConstraint;
}
@end
Later in the ViewController create the outlet for the constraint you want to update.
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *topConstraint;
and update the multiplier where ever you want like below..
self.topConstraint = [self.topConstraint updateMultiplier:0.9099];
You can change the "constant" property instead to achieve the same goal with a little math. Assume your default multiplier on the constraint is 1.0f. This is Xamarin C# code which can be easily translated to objective-c
private void SetMultiplier(nfloat multiplier)
{
FirstItemWidthConstraint.Constant = -secondItem.Frame.Width * (1.0f - multiplier);
}
As is in other answers explained: You need to remove constraint and create new one.
You can avoid returning new constraint by creating static method for NSLayoutConstraint
with inout
parameter, which allows you to reassign passed constraint
import UIKit
extension NSLayoutConstraint {
static func setMultiplier(_ multiplier: CGFloat, of constraint: inout NSLayoutConstraint) {
NSLayoutConstraint.deactivate([constraint])
let newConstraint = NSLayoutConstraint(item: constraint.firstItem, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: constraint.secondItem, attribute: constraint.secondAttribute, multiplier: multiplier, constant: constraint.constant)
newConstraint.priority = constraint.priority
newConstraint.shouldBeArchived = constraint.shouldBeArchived
newConstraint.identifier = constraint.identifier
NSLayoutConstraint.activate([newConstraint])
constraint = newConstraint
}
}
Example usage:
@IBOutlet weak var constraint: NSLayoutConstraint!
override func viewDidLoad() {
NSLayoutConstraint.setMultiplier(0.8, of: &constraint)
// view.layoutIfNeeded()
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With