164

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

_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

4

16 回答 16

189

这是 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() 
}
于 2015-10-07T22:06:07.160 回答
128

如果您只有两组需要应用的乘数,从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 ...]

斯威夫特 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
于 2015-01-08T00:16:55.400 回答
58

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

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

于 2013-10-25T15:27:56.750 回答
52

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

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)
于 2015-09-30T06:57:09.613 回答
44

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];
于 2016-05-16T10:44:57.527 回答
14

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

private void SetMultiplier(nfloat multiplier)
{
    FirstItemWidthConstraint.Constant = -secondItem.Frame.Width * (1.0f - multiplier);
}
于 2017-02-21T20:30:15.207 回答
12

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


您可以通过创建NSLayoutConstraint带有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() 
}
于 2019-03-20T13:08:08.370 回答
7

在 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
    }
}
于 2020-10-09T12:52:50.223 回答
5

在尝试修改我自己的代码此代码后,以上代码均不适合我此代码适用于 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() 
 }
于 2019-03-17T08:23:45.767 回答
2

是的 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)
于 2019-06-13T06:52:41.340 回答
2

斯威夫特 5+

根据Evgenii 的 回答,这里有一种优雅的方式来改变multiplierthrough 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)
于 2021-11-21T21:50:15.903 回答
1

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

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

在此处输入图像描述

我命名了一个proportioniPad和另一个proportionIPhone。

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

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

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

我正在使用 xCode 10 和 swift 5.0

于 2019-05-18T19:30:04.133 回答
1

@IBOutlet 弱变量 viewHeightConstraint:NSLayoutConstraint!

让 heightOfSuperview = self.view.bounds.height

viewHeightConstraint.constant = heightOfSuperview * 0.5

// 这与乘数的效果相同

于 2020-12-31T07:01:44.257 回答
0

我有办法。无需重新创建约束。

假设您有一个 imageView,您希望限制其纵横比以匹配图像纵横比。

  1. 为 imageView 创建一个宽高比约束,并将 multiplier 设置为 1,将 constant 设置为 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
于 2020-12-26T03:05:36.327 回答
0

这是基于@Tianfu's answer in 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()
}
于 2018-01-21T20:51:05.550 回答
-2

可以阅读:

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

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

于 2017-10-11T07:26:59.020 回答