Ios 我可以更改NSLayoutConstraint的乘数属性吗?

Ios 我可以更改NSLayoutConstraint的乘数属性吗?,ios,iphone,objective-c,nslayoutconstraint,Ios,Iphone,Objective C,Nslayoutconstraint,我在一个superview中创建了两个视图,然后在视图之间添加了约束: _indicatorConstrainWidth = [NSLayoutConstraint constraintWithItem:self.view1 attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.view2 attribute:NSLayoutAttributeWidth multiplier:1.0f const

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

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

如何更改乘法器属性


我的解决方法是删除旧约束,并为
乘数

添加具有不同值的新约束
乘数
属性为只读。您必须删除旧的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() 
}

Andrew Schreiber答案的Objective-C版本

为NSLayoutConstraint类创建类别,并在.h文件中添加如下方法

    #import <UIKit/UIKit.h>

@interface NSLayoutConstraint (Multiplier)
-(instancetype)updateMultiplier:(CGFloat)multiplier;
@end
稍后在ViewController中,为要更新的约束创建出口

@property (strong, nonatomic) IBOutlet NSLayoutConstraint *topConstraint;
并随时更新乘数,如下图所示

self.topConstraint = [self.topConstraint updateMultiplier:0.9099];
您可以更改“常量”属性,而不是通过一点数学运算来实现相同的目标。假设约束的默认乘数为1.0f。这是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.

在这个问题上。这难道不意味着我们应该能够修改乘数(因为它是一个var)?

这里有一个基于@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()
}

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


就是这样,快乐编码。

以上代码都不适合我 所以在尝试修改我自己的代码之后 此代码在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() 
}

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

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

我命名了一个比例键盘和另一个比例电话

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

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

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

我正在使用xCode 10和swift 5.0

是的,我们可以更改乘数值,只是扩展了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

  • 为imageView创建纵横比约束,并将乘数设置为1,将常量设置为0
  • 为纵横比约束创建一个出口
  • 根据加载的图像,在运行时更改约束常量值:

  • @IBOutlet弱var viewHeightConstraint:NSLayoutConstraint

    让heightOfSuperview=self.view.bounds.height

    viewHeightConstraint.constant=监视高度*0.5


    //这与乘数的效果相同

    您当前删除旧约束并添加新约束的方法是正确的选择。我知道这感觉不“对”,但这是你应该做的。我认为乘数不应该是常数。这是一个糟糕的设计。@Borzh为什么我通过乘法器进行自适应约束。是的,我还想改变乘数,这样视图可以保持它与父视图的比率(不是宽度/高度,而是宽度或高度),但奇怪的是苹果不允许这样做。我的意思是,这是苹果公司的糟糕设计,不是你的。这是一个伟大的演讲,涵盖了这一点,而且乘数是只读的,这似乎更奇怪。我没想到@具有讽刺意味的是,“常量”值可以改变,而乘数却不能。这是错误的。NSLayoutConstraint指定仿射(in)相等约束。例如:“View1.bottomY=A*View2.bottomY+B”'A'是乘数,'B'是常数。无论您如何调整B,它都不会产生与更改A的值相同的方程式。@FruityGeek好的,因此您使用
    View2.bottomY
    的当前值来计算约束的新常量。这是有缺陷的,因为
    View2.bottomY
    的旧值将在常量中烘焙,因此您必须放弃声明式样式,并随时手动更新约束
    View2.bottomY
    更改。在这种情况下,您也可以手动布局。如果我希望NSLayoutConstraint在没有额外代码的情况下响应边界更改或设备旋转,这将不起作用。@MICNGUEN是的,您可以:)如果您需要两组以上的约束,您可以添加任意数量的约束并更改它们的设置
    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
        }
    }
    
    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