我可以更改 NSLayoutConstraint 的乘法器属性吗?

我在一个超视图中创建了两个视图,然后在视图之间添加了约束:

_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];

现在我想用动画改变乘法器的属性,但是我不知道如何改变乘法器的属性。(我在头文件 NSLayoutConstraint.h 中找到了 private 属性中的 _ systems,但它是 private。)

如何改变乘法器的性质?

我的解决办法是移除旧的约束,并添加一个新的 multipler值不同的约束。

100374 次浏览

multiplier属性是只读的。您必须删除旧的 NSLayoutConstraint 并用一个新的替换它来修改它。

但是,因为您知道您想要更改乘数,所以当需要更改时(通常代码更少) ,您可以自己通过乘数来更改常数。

如果你只有两套乘数需要应用,从 IOS8开始,你可以添加这两套约束,并决定哪些应该在任何时候活动:

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版本

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

我用来更改现有布局约束的 乘数的助手函数。它创建并激活一个新约束,并停用旧约束。

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

使用率,乘数改为1.2:

constraint = MyConstraint.changeMultiplier(constraint, multiplier: 1.2)

下面是 Swift 中的 NSLayoutConstraint 扩展,它使得设置一个新的乘数变得非常容易:

在 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
}
}

演示用法:

@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()
}

目标-C 版本的 Andrew Schreiber 回答

为 NSLayoutConstraint Class 创建类别,并像下面这样在.h 文件中添加方法

    #import <UIKit/UIKit.h>


@interface NSLayoutConstraint (Multiplier)
-(instancetype)updateMultiplier:(CGFloat)multiplier;
@end

在.m 文件中

#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

稍后在 ViewController 中为要更新的约束创建出口。

@property (strong, nonatomic) IBOutlet NSLayoutConstraint *topConstraint;

随时随地更新乘数,如下所示。

self.topConstraint = [self.topConstraint updateMultiplier:0.9099];

您可以改变“常量”属性,通过一些数学运算来实现相同的目标。假设约束的默认乘数为1.0 f。这是 Xamarin C # 代码,可以很容易地转换为 Objective-c

private void SetMultiplier(nfloat multiplier)
{
FirstItemWidthConstraint.Constant = -secondItem.Frame.Width * (1.0f - multiplier);
}

人们可以读到:

var multiplier: CGFloat
The multiplier applied to the second attribute participating in the constraint.

这是不是意味着一个人应该能够修改乘数(因为它是一个变量) ?

下面是一个基于@Tianfu 的 C # 回答的答案。其他需要激活和取消约束的答案对我不起作用。

    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()
}

上面的代码对我都不管用 所以在试图修改我自己的代码之后 这个代码可以在 Xcode 10和 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()
}

正如在其他答案中解释的那样: 您需要移除约束并创建新的约束。


通过使用 inout参数为 NSLayoutConstraint创建静态方法,可以避免返回新的约束,该方法允许重新分配传递的约束

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
}


}

示例用法:

@IBOutlet weak var constraint: NSLayoutConstraint!


override func viewDidLoad() {
NSLayoutConstraint.setMultiplier(0.8, of: &constraint)
// view.layoutIfNeeded()
}

通过改变代码中的活动约束来进行切换,许多其他答案建议这样做对我来说并不管用。所以我创建了两个约束,一个安装了,另一个没有,绑定到代码,然后通过删除一个并添加另一个来切换。

为了完整起见,要绑定约束,使用鼠标右键将约束拖动到代码中,就像任何其他图形元素一样:

enter image description here

我命名了一个比例的 IPad 和另一个比例的 IPhone。

然后,在 viewDidLoad 添加以下代码

override open func viewDidLoad() {
super.viewDidLoad()
if ... {


view.removeConstraint(proportionIphone)
view.addConstraint(proportionIpad)
}
}

我用的是 xCode 10和 Swift 5.0

是的 w 可以改变乘数值只是作为 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 = shouldBeArchived
newConstraint.identifier = identifier


NSLayoutConstraint.activate([newConstraint])
return newConstraint
}


self.mainImageViewHeightMultiplier = self.mainImageViewHeightMultiplier.setMultiplier(375.0/812.0)

在 Swift 5.x 中,你可以使用:

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

我有办法,不需要重新创建约束。

假设您有一个 imageView,您希望约束它的长宽比以匹配图像长宽比。

  1. 为 imageView 创建一个高宽比约束,并将乘数设置为1,常数设置为0。
  2. 为高宽比约束创建一个出口。
  3. 根据加载的图像在运行时更改约束常数值:
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

@ IBOutlet 弱 var viewHeightConstraint: NSLayoutConstraint!

Let heightOfSuperview = self. view.bound s.height

Constant = heightOfSuperview * 0.5

//这与乘数效应相同

Swift 5 +

基于 叶夫根尼的 回答,这里是一个优雅的方式来改变 multiplier通过 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])
}
}

用法:

myConstraint.change(multiplier: 0.6)

答案很简单,不需要延期,我试过了,效果不错。

因此,由于乘数是一个 get only 属性,我们可以简单地通过以下方式设置乘数:

yourConstraintOutlet.setValue(yourDesiredMultiplierValue, forKey: "multiplier")


yourConstraintOutlet.setValue(0.75, forKey: "multiplier")

Xcode 13.3.1,Swift 5.6(Swift lang-5.6.0.323.62 clang-1316.0.20.8)

这是基于@Ullas Pujary 的回答,我让它更快一点,删除 firstItemandsecond Item’上的警告

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