'Can I change multiplier property for NSLayoutConstraint?
I created two views in one superview, and then added constraints between views:
_indicatorConstrainWidth = [NSLayoutConstraint constraintWithItem:self.view1 attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.view2 attribute:NSLayoutAttributeWidth multiplier:1.0f constant:0.0f];
[_indicatorConstrainWidth setPriority:UILayoutPriorityDefaultLow];
_indicatorConstrainHeight = [NSLayoutConstraint constraintWithItem:self.view1 attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.view2 attribute:NSLayoutAttributeHeight multiplier:1.0f constant:0.0f];
[_indicatorConstrainHeight setPriority:UILayoutPriorityDefaultLow];
[self addConstraint:_indicatorConstrainWidth];
[self addConstraint:_indicatorConstrainHeight];
Now I want to change multiplier property with animation, but I can't figure out how to change the multipler property. (I found _coefficient in private property in header file NSLayoutConstraint.h, but it private.)
How do I change multipler property?
My workaround is to remove the old constraint and add the new one with a different value for multipler
.
Solution 1:[1]
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
Solution 2:[2]
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()
}
Solution 3:[3]
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.
Solution 4:[4]
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)
Solution 5:[5]
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];
Solution 6:[6]
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);
}
Solution 7:[7]
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()
}
Solution 8:[8]
in Swift 5.x you can use:
extension NSLayoutConstraint {
func setMultiplier(multiplier: CGFloat) -> NSLayoutConstraint {
guard let firstItem = firstItem else {
return self
}
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
}
}
Solution 9:[9]
NONE OF THE ABOVE CODE WORKED FOR ME SO AFTER TRYING TO MODIFY MY OWN CODE THIS CODE This code is working in Xcode 10 and swift 4.2
import UIKit
extension NSLayoutConstraint {
/**
Change multiplier constraint
- parameter multiplier: CGFloat
- returns: NSLayoutConstraintfor
*/i
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
}
}
@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()
}
Solution 10:[10]
Swift 5+
Based on Evgenii's answer, here is an elegant way to change the multiplier
through extension
.
extension NSLayoutConstraint {
func change(multiplier: CGFloat) {
let newConstraint = NSLayoutConstraint(item: firstItem,
attribute: firstAttribute,
relatedBy: relation,
toItem: secondItem,
attribute: secondAttribute,
multiplier: multiplier,
constant: constant)
newConstraint.priority = self.priority
NSLayoutConstraint.deactivate([self])
NSLayoutConstraint.activate([newConstraint])
}
}
And the usage:
myConstraint.change(multiplier: 0.6)
Solution 11:[11]
Yes w can change multiplier values just make an extension of NSLayoutConstraint and use it like ->
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 = shouldBeArchived
newConstraint.identifier = identifier
NSLayoutConstraint.activate([newConstraint])
return newConstraint
}
self.mainImageViewHeightMultiplier = self.mainImageViewHeightMultiplier.setMultiplier(375.0/812.0)
Solution 12:[12]
Switch by changing the active constraint in code as suggested by many other answers did not work for me. So i created 2 constrains, one installed and the other not, bind both to the code, and then switch by removing one and adding the other.
For the sake of completeness, to bind the constrain drag the constrain to the code using mouse right button, just like any other graphic element:
I named one proportionIPad and the other proportionIPhone.
Then, add the following code, at viewDidLoad
override open func viewDidLoad() {
super.viewDidLoad()
if ... {
view.removeConstraint(proportionIphone)
view.addConstraint(proportionIpad)
}
}
I am using xCode 10 and swift 5.0
Solution 13:[13]
@IBOutlet weak var viewHeightConstraint: NSLayoutConstraint!
let heightOfSuperview = self.view.bounds.height
viewHeightConstraint.constant = heightOfSuperview * 0.5
// this has the same effect as multiplier
Solution 14:[14]
Simple answer, no extensions required. I tried for my case, worked fine for me.
So as multiplier is a get only property, we can simply set multiplier in the following way :
yourConstraintOutlet.setValue(yourDesiredMultiplierValue, forKey: "multiplier")
yourConstraintOutlet.setValue(0.75, forKey: "multiplier")
Solution 15:[15]
Here is an answer based on @Tianfu's answer in C#. Other answers that require activation and deactivation of constraints did not work for me.
var isMapZoomed = false
@IBAction func didTapMapZoom(_ sender: UIButton) {
let offset = -1.0*graphHeightConstraint.secondItem!.frame.height*(1.0 - graphHeightConstraint.multiplier)
graphHeightConstraint.constant = (isMapZoomed) ? offset : 0.0
isMapZoomed = !isMapZoomed
self.view.layoutIfNeeded()
}
Solution 16:[16]
I have a way. No need to re-create a constraint.
Assuming you have an imageView which you want to constraint its aspect ratio to match the image aspect ratio.
- Create an aspect ratio constraint for the imageView and set multiplier to 1 and constant to 0.
- Create an outlet for the aspect ratio constraint.
- Change the constraint constant value at runtime, according the the image you load:
let multiplier = image.size.width / image.size.height
let (w, h) = (imageView.bounds.width, imageView.bounds.height)
let expectedW = h * multiplier
let diff = expectedW - h
imageViewAspectConstraint.constant = image.size.width >= image.size.height ? diff : -diff // multiplier is read-only, but constant is RW
Solution 17:[17]
One can read:
var multiplier: CGFloat
The multiplier applied to the second attribute participating in the constraint.
on this documentation page. Doesn't that mean that one should be able to modify multiplier (since it is a var)?
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow