我可以更改NSLayoutConstraint的multiplier属性吗?


141

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

_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的私有属性中找到了_coefficient,但它是私有的。)

如何更改多重属性?

我的解决方法是删除旧的约束,并为添加新的约束multipler


1
您当前删除旧约束并添加新约束的方法是正确的选择。我了解这并不感觉“正确”,但这是您应该这样做的方式。
罗布(Rob)

4
我认为乘数不应该是常数。这是一个糟糕的设计。
2015年

1
@Borzh为什么我通过乘数来做出自适应约束。对于可调整大小的ios。
比马瓦2015年

1
是的,我还想更改乘数,以便视图可以保持其与父视图的比率(不是宽度/高度,而是宽度或高度),但是奇怪的是苹果不允许这样做。我的意思是这是苹果的错误设计,而不是您的。
2015年

这是一个伟大的谈话,包括这更youtube.com/watch?v=N5Ert6LTruY
DogCoffee

Answers:


117

如果只有两套乘数需要应用,则从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

2
@micnguyen是的,你可以:)
杰克

7
如果需要两组以上的约束,则可以根据需要添加任意数量的约束,并更改其优先级属性。这样,对于2个约束,您不必将一个约束设置为活动状态,而将另一个约束设置为非活动状态,只需将优先级设置为较高或较低。在上面的示例中,将standardConstraint优先级设置为997,而要使其处于活动状态时,只需将zoomedConstraint的优先级设置为995,否则将其设置为999。还要确保XIB的优先级未设置为1000,否则您将无法更改它们,并且会遇到严重的崩溃。

1
@WonderDog有什么特别的优点吗?在我看来,启用和禁用的习语比必须制定相对优先级要容易理解。
千斤顶

2
@WonderDog / Jack因为我的约束是在情节提要中定义的,所以我最终结合了您的方法。如果我创建了两个约束,它们的优先级相同但乘数不同,那么它们就会发生冲突,并给我带来很多麻烦。而且据我所知,您不能通过IB将其设置为非活动状态。因此,我创建了优先级为1000的主约束,并创建了优先级为999的辅助约束(在IB中发挥出色)。然后在一个动画块中,将主要对象的active属性设置为false,然后将其很好地动画化为辅助对象。谢谢你们的帮助!
克莱·加勒特

2
请记住,如果激活和停用约束,则可能需要强烈引用约束,因为“激活或停用约束调用addConstraint(_:)并且removeConstraint(_:)在视图上是此约束所管理项目的最接近的共同祖先”。
2015年

166

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

11
NSLayoutConstraint.deactivateConstraints([self])应该在创建之前完成newConstraint,否则可能会导致警告:无法同时满足约束。另外newConstraint.active = true是不必要的,因为NSLayoutConstraint.activateConstraints([newConstraint])不正是同样的事情。
tzaloga '16

1
激活和停用在ios8之前不起作用,还有其他替代方法吗?
narahari_arjun

2
再次更改乘数时,此方法不起作用,它的值为nil
J. Doe

4
感谢您为我们节省时间!我将函数重命名为某种cloneWithMultiplier使其更明确的功能,即它不仅会产生副作用,而且还会返回新的约束条件
Alexandre G

5
请注意,如果将乘数设置为0.0,将无法再次更新。
丹尼尔·斯托姆

57

multiplier属性是只读的。您必须删除旧的NSLayoutConstraint并用新的NSLayoutConstraint对其进行修改。

但是,由于您知道要更改乘数,因此只需在需要更改时自己乘以常数即可更改常数,而这通常是更少的代码。


62
乘法器是只读的似乎有些奇怪。我没想到!
jowie 2014年

134
@jowie具有讽刺意味的是,“常数”值可以更改而乘数不能更改。
Tomas Andrle

7
错了 NSLayoutConstraint指定一个仿射(不相等)约束。例如:“ View1.bottomY = A * View2.bottomY + B”'A'是乘数,'B'是常数。无论您如何调整B,它都不会产生与更改A的值相同的方程式。
塔玛斯·扎霍拉(TamásZahola)2015年

8
@FruityGeek好的,因此您正在使用View2.bottomY当前值来计算约束的常数。这是有缺陷的,因为View2.bottomY的旧值将在常量中烘焙,因此您必须放弃声明式样式,并在View2.bottomY更改时手动更新约束。在这种情况下,您也可以进行手动布局。
塔玛斯·扎霍拉(TamásZahola)2015年

2
如果我希望NSLayoutConstraint在没有额外代码的情况下响应边界更改或设备旋转,这将无法正常工作。
tzaloga '16

49

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

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)

1
它适用于iOS 8吗?或者可以在早期版本中使用
Durai Amuthan.H

1
NSLayoutConstraint.deactivate功能仅适用于iOS 8.0+。
Evgenii

你为什么要退货?
mskw

@mskw,该函数返回其创建的新约束对象。前一个可以丢弃。
伊夫根尼

42

Andrew Schreiber的Objective-C版本答案

创建NSLayoutConstraint类的类别,然后将方法添加到.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];

我已将此示例代码中的取消active = true激活与激活相同,因此导致在调用取消激活之前添加了重复约束,这在某些情况下会导致约束错误。
Jules

13

您可以更改“ constant”属性,而只需一点数学就可以达到相同的目标。假设约束的默认乘数为1.0f。这是Xamarin C#代码,可以轻松将其转换为Objective-C

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

这是一个不错的解决方案
J. Doe

3
如果执行上述代码后,secondItem的框架更改宽度,则此操作将中断。如果secondItem永远不会更改大小是可以的,但是,如果secondItem确实更改大小,则约束将不再为布局计算正确的值。
约翰·斯蒂芬

正如约翰·史蒂芬(John Stephen)指出的那样,这不适用于动态视图和设备:它不会随布局自动更新。
Womble

1
对于我的情况,这是一个很好的解决方案,其中第二个项目的宽度实际上是[UIScreen mainScreen] .bounds.size.width(永远不变)
Arik Segal

7

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


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

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

2

在尝试修改自己的代码后,上面的代码均对我无用。此代码在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() 
 }

谢谢,似乎工作正常
Radu Ursache

欢迎radu-ursache
Ullas Pujary

2

是的,只要更改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)

1

如许多其他答案所建议的那样,通过更改代码中的活动约束进行切换对我不起作用。因此,我创建了2个约束,一个已安装,另一个未安装,将两者都绑定到代码,然后通过删除一个并添加另一个来进行切换。

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

在此处输入图片说明

我将一个比例命名为iPad,另一个比例命名为iPhone。

然后,在viewDidLoad处添加以下代码

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

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

我正在使用xCode 10和Swift 5.0


0

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

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

0

要更改乘数的值,请执行以下步骤:1.为所需约束(例如someConstraint)创建出口。2.更改乘数,例如someConstraint.constant * = 0.8或任何乘数。

就是这样,快乐的编码。


-1

可以读到:

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

在此文档页面上。这不是意味着一个人应该能够修改乘数(因为它是一个变量)?


var multiplier: CGFloat { get }是定义,这意味着它仅具有吸气剂。
强尼
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.