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

Bim*_*awa 135 iphone objective-c ios nslayoutconstraint

我在一个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];
Run Code Online (Sandbox Code Playgroud)

现在我想用动画更改乘数属性,但我无法弄清楚如何更改多重属性.(我在头文件NSLayoutConstraint.h中的私有属性中找到了_coefficient,但它是私有的.)

如何更改多重属性?

我的解决方法是删除旧约束并添加具有不同值的新约束multipler.

And*_*ber 156

这是Swift中的NSLayoutConstraint扩展,它使得设置新的乘法器变得非常简单:

import UIKit

extension NSLayoutConstraint {

    func setMultiplier(multiplier:CGFloat) -> NSLayoutConstraint {

        NSLayoutConstraint.deactivateConstraints([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.activateConstraints([newConstraint])
        return newConstraint
    }
}
Run Code Online (Sandbox Code Playgroud)

在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
    }
}
Run Code Online (Sandbox Code Playgroud)

演示用法:

@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() 
}
Run Code Online (Sandbox Code Playgroud)

  • `NSLayoutConstraint.deactivateConstraints([self])`应该在创建`newConstraint`之前完成,否则可能会导致警告:_Unable同时满足constraints_.另外`newConstraint.active = true`是不必要的,因为`NSLayoutConstraint.activateConstraints([newConstraint])`完全相同. (10认同)
  • 感谢您为我们节省时间!我会将函数重命名为类似 `cloneWithMultiplier` 的名称,以使其更明确,它不仅应用副作用,而且返回一个新的约束 (4认同)
  • 请注意,如果将乘数设置为“ 0.0”,则将无法再次更新。 (3认同)
  • 再次更改乘数时此方法不起作用,它得到一个零值 (2认同)

Ja͢*_*͢ck 106

如果您只需要应用两组乘数,那么从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 ...]
Run Code Online (Sandbox Code Playgroud)

  • @CullenSUN应该从我的回答中明显看出"iOS 8 ...":p (15认同)
  • 如果您需要超过2组约束,则可以添加任意数量的约束并更改其优先级属性.这样,对于2个约束,您不必将其中一个设置为活动而另一个设置为非活动,您只需将优先级设置为更高或更低.在上面的示例中,将standardConstraint优先级设置为997,并且当您希望它处于活动状态时,只需将zoomedConstraint的优先级设置为995,否则将其设置为999.另外,请确保XIB没有将优先级设置为1000,否则你将无法改变它们,你会得到一个很棒的崩溃. (7认同)
  • @WonderDog / Jack 我最终结合了你的方法,因为我的约束是在故事板中定义的。如果我创建了两个具有相同优先级但乘数不同的约束,它们就会发生冲突并给我很多红色。而且据我所知,您不能通过 IB 将其设置为非活动状态。所以我创建了优先级为 1000 的主要约束和我想要动画的次要约束,优先级为 999(在 IB 中很好用)。然后在一个动画块中,我将主要的 active 属性设置为 false 并且它很好地动画到次要的。感谢两位的帮助! (3认同)
  • @micnguyen是的,你可以:) (2认同)
  • 请记住,如果激活和停用约束,您可能需要对约束的强引用,因为“激活或停用约束会在最接近的公共祖先视图上调用 `addConstraint(_:)` 和 `removeConstraint(_:)`此约束管理的项目的数量”。 (2认同)

Fru*_*eek 54

multiplier物业是只读的.您必须删除旧的NSLayoutConstraint并将其替换为新的NSLayoutConstraint以进行修改.

但是,由于您知道要更改乘数,因此可以通过在需要更改(通常更少的代码)时自己乘以常量来更改常量.

  • @jowie具有讽刺意味的是,"常数"值可以改变而乘数不能. (124认同)
  • 乘数是只读的似乎很奇怪.我没想到! (59认同)
  • @FruityGeek没关系,所以你使用`View2.bottomY`的_current_值来计算约束的_new_常量.这是有缺陷的,因为`View2.bottomY`的旧值将在常量中被烘焙,因此您必须放弃声明式样式并随时"View2.bottomY"更改时手动更新约束.在这种情况下,您可能还需要手动布局. (8认同)
  • 如果常量为0.0,则此解决方案不起作用. (7认同)
  • 这是错的.NSLayoutConstraint指定仿射(in)等式约束.例如:"View1.bottomY = A*View2.bottomY + B"'A'是乘数,'B'是常数.无论你如何调整B,它都不会产生与改变A值相同的等式. (7认同)
  • 当我希望 NSLayoutConstraint 在没有额外代码的情况下响应边界更改或设备旋转时,这将不起作用。 (2认同)

Evg*_*nii 48

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

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
  }
}
Run Code Online (Sandbox Code Playgroud)

用法,将乘数改为1.2:

constraint = MyConstraint.changeMultiplier(constraint, multiplier: 1.2)
Run Code Online (Sandbox Code Playgroud)


小智 39

Objective Schriber的Objective-C版本答案

为NSLayoutConstraint类创建类别,并将此方法添加到.h文件中

    #import <UIKit/UIKit.h>

@interface NSLayoutConstraint (Multiplier)
-(instancetype)updateMultiplier:(CGFloat)multiplier;
@end
Run Code Online (Sandbox Code Playgroud)

在.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
Run Code Online (Sandbox Code Playgroud)

稍后在ViewController中为要更新的约束创建插座.

@property (strong, nonatomic) IBOutlet NSLayoutConstraint *topConstraint;
Run Code Online (Sandbox Code Playgroud)

并在下面的任何地方更新乘数.

self.topConstraint = [self.topConstraint updateMultiplier:0.9099];
Run Code Online (Sandbox Code Playgroud)


Rob*_*ler 11

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


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

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
    }

}
Run Code Online (Sandbox Code Playgroud)

用法示例:

@IBOutlet weak var constraint: NSLayoutConstraint!

override func viewDidLoad() {
    NSLayoutConstraint.setMultiplier(0.8, of: &constraint)
    // view.layoutIfNeeded() 
}
Run Code Online (Sandbox Code Playgroud)


小智 10

您可以更改"常量"属性,以通过一点点数学实现相同的目标.假设约束的默认乘数是1.0f.这是Xamarin C#代码,可以很容易地转换为objective-c

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

  • 如果执行上述代码后,secondItem的框架更改宽度,则此操作将中断。如果secondItem永远不会更改大小是可以的,但是,如果secondItem确实更改大小,则约束将不再为布局计算正确的值。 (2认同)
  • 对于我的情况来说这是一个很好的解决方案,其中第二个项目的宽度实际上是 [UIScreen mainScreen].bounds.size.width (永远不会改变) (2认同)

Đor*_*vić 10

斯威夫特 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])
    }
}
Run Code Online (Sandbox Code Playgroud)

以及用法:

myConstraint.change(multiplier: 0.6)
Run Code Online (Sandbox Code Playgroud)


Rad*_*che 6

在 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
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 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() 
 }
Run Code Online (Sandbox Code Playgroud)