Can I change multiplier property for NSLayoutConstraint?

IosIphoneObjective CNslayoutconstraint

Ios Problem Overview


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.

Ios Solutions


Solution 1 - Ios

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 2 - Ios

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 3 - Ios

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 - Ios

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 - Ios

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 - Ios

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 - Ios

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 - Ios

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 - Ios

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 - Ios

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 - Ios

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 - Ios

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:

enter image description here

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 - Ios

@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 - Ios

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 - Ios

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 - Ios

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.

  1. Create an aspect ratio constraint for the imageView and set multiplier to 1 and constant to 0.
  2. Create an outlet for the aspect ratio constraint.
  3. 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 - Ios

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)?

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBimawaView Question on Stackoverflow
Solution 1 - IosAndrew SchreiberView Answer on Stackoverflow
Solution 2 - IosJa͢ckView Answer on Stackoverflow
Solution 3 - IosFruity GeekView Answer on Stackoverflow
Solution 4 - IosEvgeniiView Answer on Stackoverflow
Solution 5 - IosKoti TummalaView Answer on Stackoverflow
Solution 6 - IosTianfu DaiView Answer on Stackoverflow
Solution 7 - IosRobert DreslerView Answer on Stackoverflow
Solution 8 - IosRadu UrsacheView Answer on Stackoverflow
Solution 9 - IosUllas PujaryView Answer on Stackoverflow
Solution 10 - IosĐorđe NilovićView Answer on Stackoverflow
Solution 11 - IosRajan SinghView Answer on Stackoverflow
Solution 12 - IosMiguelSlvView Answer on Stackoverflow
Solution 13 - Iospayal_makwanaView Answer on Stackoverflow
Solution 14 - IosAshish BahlView Answer on Stackoverflow
Solution 15 - IosRawMeanView Answer on Stackoverflow
Solution 16 - IosPapillonView Answer on Stackoverflow
Solution 17 - IosMichelView Answer on Stackoverflow