How to change bottom layout constraint in iOS, Swift - ios

I have scroll view as #IBOutlet
#IBOutlet weak var mainScrollView: UIScrollView!
I want to change the
"Bottom space to: Bottom Layout Guide"
constraint programmatically.
First Item : Bottom Layout Guide.Top
Relation : Equal
Second Item: Scroll View.Bottom
Constant: 0 -> 50 // (I want to change this programmatically)
Priority: 1000
Multiplier: 1
How can I do this?

Take the constraint as IBOutlet of NSLayoutConstraint.
Set the constraint outlets and change constant value by :
self.sampleConstraint.constant = 20
self.view.layoutIfNeeded()

If you are adding constraint programatically like this:
var constraintButton = NSLayoutConstraint (item: buttonPlay,
attribute: NSLayoutAttribute.Bottom,
relatedBy: NSLayoutRelation.Equal,
toItem: self.view,
attribute: NSLayoutAttribute.Bottom,
multiplier: 1,
constant: 0)
// Add the constraint to the view
self.view.addConstraint(constraintButton)
Then you can update it this way:
self.constraintButton.constant = 50
self.view.layoutIfNeeded()
And if you want that with animation you can do it this way:
self.view.layoutIfNeeded()
UIView.animateWithDuration(1, animations: {
self.constraintButton.constant = 50
self.view.layoutIfNeeded()
})
Hope it helps.

Create an IBOutlet for your constraint:
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *bottomContraint;
And when you need to change it call:
bottomContstraint.constant = //your value
view.layoutIfNeeded()
Also you can animate constraint change like that:
bottomContstraint.constant = //your value
UIView.animateWithDuration(0.5, animations: {
self.view.layoutIfNeeded()
})

Related

How to change aspectRatio of IBOutlet for a view programmatically?

Okay here is my constraints.
The view is aligned centreX and centreY and has leading, trailing, top & bottom as >= 0.
It also has another constraint of aspectRatio.
Now I have created the #IBOutlet of aspectRation constraint.
#IBOutlet weak var contentViewAspectRatio: NSLayoutConstraint!
The problem is I don't know how to change the aspectRatio programmatically.
if value {
//Change aspect ratio to 16/9
} else {
//Change aspect ratio to 19.5/9
}
Any kind of help would be appreciated.
Changing the aspect ratio means changing the multiplier of the constraint which is a read-only so you need to deactivate that constraint and create a new 1 with a new multiplier
let newCon = contentViewAspectRatio.addConstraintWithMul(0.3)
parentViewOfConstraint.removeConstraint(contentViewAspectRatio)
parentViewOfConstraint.addConstraint(newCon)
view.layoutIfNeeded()
contentViewAspectRatio = newCon
extension NSLayoutConstraint {
func addConstraintWithMul(_ multiplier: CGFloat) -> NSLayoutConstraint {
return NSLayoutConstraint(item: self.firstItem!, attribute: self.firstAttribute, relatedBy: self.relation, toItem: self.secondItem, attribute: self.secondAttribute, multiplier: multiplier, constant: self.constant)
}
}
Another option is to create 2 constraints with 2 different aspects and play with their active / priority state in case you have a limited and know number of aspects

Setting Constraint programmatically like in storyboard

I need to set constraint to have a view equal height with multiplier 0.8
I use this code
override func willTransition(to presentationStyle: MSMessagesAppPresentationStyle) {
if(presentationStyle == .compact){
let videoController = recordingController.videoController
let containerView = videoController?.containerView
self.addConstraint(value: 0.1, name: "ViewAltezza",videoView: (videoController?.mainView)!, containerView: containerView!)
print("Compact")
}else if(presentationStyle == .expanded){
let videoController = recordingController.videoController
let containerView = videoController?.containerView
self.addConstraint(value: 0.1, name: "ViewAltezza",videoView: (videoController?.mainView)!, containerView: containerView!)
}
print("Expand")
}
}
func addConstraint(value : CGFloat, name: String, videoView: UIView, containerView: UIView){
videoView.translatesAutoresizingMaskIntoConstraints = false
for constraint in videoView.constraints{
if(constraint.identifier == name){
print("Ecco la constraint \(constraint)")
videoView.removeConstraint(constraint)
let heightConstraint = NSLayoutConstraint(item: containerView , attribute: .height, relatedBy: .equal, toItem: videoView, attribute: .height, multiplier: value, constant: 0.0)
heightConstraint.identifier = name
heightConstraint.priority = UILayoutPriority(rawValue: 1000)
print("Aggiungo questa constraint \(heightConstraint)")
videoView.addConstraint(heightConstraint)
}
}
}
The code seems correct, and the debug shows that the initial constraint and the created constraint are the same... is there any errors ? or just I am forgetting something
I need to do this because in iMessage extension when I create a viewController using present(_viewControllerFromStoryboard . . . ), it seems like the view can't auto resize like in MSMessagesAppViewController when the user expand or compact the iMessage app; so if there is any way to avoiding this and let the viewController auto fix the presentationStyle, it will be easier for me to fix the issue I am experimenting.

Swift 3 How to modify a multiplier constraint programatically [duplicate]

Historic question alert:
Note that 15 years later, you can now simply
yourConstraint.setValue(0.75, forKey: "multiplier")
original QA:
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.
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()
}
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
}
}
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()
}
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)
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)
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")
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
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()
}
#IBOutlet weak var viewHeightConstraint: NSLayoutConstraint!
let heightOfSuperview = self.view.bounds.height
viewHeightConstraint.constant = heightOfSuperview * 0.5
// this has the same effect as multiplier
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
note that you can simply yourConstraint.setValue(0.75, forKey: "multiplier")
or
Xcode 13.3.1, Swift 5.6 (swiftlang-5.6.0.323.62 clang-1316.0.20.8)
This is based on #Ullas Pujary's answer and I make it a little swifter and remove the warnings on firstItemandsecondItem`
extension NSLayoutConstraint {
public static func setMultiplier(_ newMultiplier: CGFloat, of constraint: inout NSLayoutConstraint) {
constraint.isActive = false
guard
let firstItem = constraint.firstItem,
let secondItem = constraint.secondItem
else {
return
}
let newConstraint = NSLayoutConstraint(item: firstItem,
attribute: constraint.firstAttribute,
relatedBy: constraint.relation,
toItem: secondItem,
attribute: constraint.secondAttribute,
multiplier: newMultiplier,
constant: constraint.constant)
newConstraint.priority = constraint.priority
newConstraint.shouldBeArchived = constraint.shouldBeArchived
newConstraint.identifier = constraint.identifier
newConstraint.isActive = true
constraint = newConstraint
}
}
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)?

Programmatically Change multiplier of Alignment Constraint of Center X / Y

How can I programmatically change the multiplier in the simplest way?
For Swift 2.0
So for Y, if you set the top of the image equal with a constant of 0 to the top of the superView. then enter this code:
#IBOutlet weak var topc: NSLayoutConstraint!
let heightOfSuperview = self.view.bounds.height
topc.constant = heightOfSuperview * 0.90 // this has the same effect as multiplier
This is equivalent of Center.y multiplier = 0.9:1
I try to use extension but it not work, but change to the global utility function and it work for me:
public class 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.deactivateConstraints([constraint])
NSLayoutConstraint.activateConstraints([newConstraint])
return newConstraint
}
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")
No need to write extensions or multiply by height of superview.

Can I change multiplier property for NSLayoutConstraint?

Historic question alert:
Note that 15 years later, you can now simply
yourConstraint.setValue(0.75, forKey: "multiplier")
original QA:
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.
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()
}
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
}
}
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()
}
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)
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")
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)
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
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()
}
#IBOutlet weak var viewHeightConstraint: NSLayoutConstraint!
let heightOfSuperview = self.view.bounds.height
viewHeightConstraint.constant = heightOfSuperview * 0.5
// this has the same effect as multiplier
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
note that you can simply yourConstraint.setValue(0.75, forKey: "multiplier")
or
Xcode 13.3.1, Swift 5.6 (swiftlang-5.6.0.323.62 clang-1316.0.20.8)
This is based on #Ullas Pujary's answer and I make it a little swifter and remove the warnings on firstItemandsecondItem`
extension NSLayoutConstraint {
public static func setMultiplier(_ newMultiplier: CGFloat, of constraint: inout NSLayoutConstraint) {
constraint.isActive = false
guard
let firstItem = constraint.firstItem,
let secondItem = constraint.secondItem
else {
return
}
let newConstraint = NSLayoutConstraint(item: firstItem,
attribute: constraint.firstAttribute,
relatedBy: constraint.relation,
toItem: secondItem,
attribute: constraint.secondAttribute,
multiplier: newMultiplier,
constant: constraint.constant)
newConstraint.priority = constraint.priority
newConstraint.shouldBeArchived = constraint.shouldBeArchived
newConstraint.identifier = constraint.identifier
newConstraint.isActive = true
constraint = newConstraint
}
}
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)?

Resources