我可以更改 NSLayoutConstraint 的乘数属性吗?
Posted
技术标签:
【中文标题】我可以更改 NSLayoutConstraint 的乘数属性吗?【英文标题】:Can I change multiplier property for NSLayoutConstraint? 【发布时间】:2013-10-25 15:16:09 【问题描述】:我在一个superview中创建了两个view,然后在view之间添加了约束:
_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
添加具有不同值的新约束。
【问题讨论】:
您当前删除旧约束并添加新约束的方法是正确的选择。我知道这感觉不“正确”,但这是您应该这样做的方式。 我认为乘数不应该是常数。这是一个糟糕的设计。 @Borzh 为什么通过乘数我做出自适应约束。对于可调整大小的 ios。 是的,我还想更改乘数,以便视图可以保持它与父视图的比例(不是宽度/高度,而是宽度或高度),但奇怪的是苹果不允许这样做。我的意思是这是苹果的糟糕设计,而不是你的。 这是一个很棒的演讲,涵盖了这个以及更多youtube.com/watch?v=N5Ert6LTruY 【参考方案1】:这是 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()
【讨论】:
NSLayoutConstraint.deactivateConstraints([self])
应该在创建newConstraint
之前完成,否则可能会导致警告:无法同时满足约束。另外newConstraint.active = true
是不必要的,因为NSLayoutConstraint.activateConstraints([newConstraint])
做同样的事情。
activate 和 deactivate 在 ios8 之前不起作用,有什么替代方法吗??
这在再次更改乘数时不起作用,它得到一个 nil 值
感谢您为我们节省时间!我会将函数重命名为 cloneWithMultiplier
之类的名称,以使其更明确地表明它不仅应用副作用,而且返回新约束
请注意,如果您将乘数设置为0.0
,您将无法再次更新。【参考方案2】:
如果您只有两组需要应用的乘数,则从 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
【讨论】:
@micnguyen 是的,你可以:) 如果您需要超过 2 组约束,您可以根据需要添加任意数量并更改它们的优先级属性。这样,对于两个约束,您不必将一个设置为活动,另一个设置为非活动,您只需将优先级设置为更高或更低。在上面的示例中,将 standardConstraint 优先级设置为,假设为 997,当您希望它处于活动状态时,只需将 zoomedConstraint 的优先级设置为 995,否则将其设置为 999。还要确保 XIB 没有将优先级设置为 1000,否则您将无法更改它们,并且会发生严重崩溃。 @WonderDog 这有什么特别的优势吗?在我看来,启用和禁用的习惯用法比必须确定相对优先级更容易理解。 @WonderDog / Jack 我最终结合了你的方法,因为我的约束是在情节提要中定义的。如果我创建了两个具有相同优先级但乘数不同的约束,它们就会发生冲突并给我很多红色。据我所知,您不能通过 IB 将其设置为非活动状态。所以我创建了优先级为 1000 的主要约束和我想要以优先级 999 进行动画处理的次要约束(在 IB 中很好用)。然后在一个动画块中,我将主要的 active 属性设置为 false 并且它很好地动画到次要的。感谢两位的帮助! 请记住,如果激活和停用它们,您可能希望对约束进行强引用,因为“激活或停用约束在最接近的共同祖先的视图上调用addConstraint(_:)
和 removeConstraint(_:)
受此约束管理的项目”。【参考方案3】:
multiplier
属性是只读的。您必须删除旧的 NSLayoutConstraint 并用新的替换它来修改它。
但是,由于您知道要更改乘数,因此您可以在需要更改时自己乘以来更改常数,这通常需要更少的代码。
【讨论】:
乘数是只读的似乎很奇怪。没想到! @jowie 具有讽刺意味的是,“常数”值可以更改而乘数不能更改。 这是错误的。 NSLayoutConstraint 指定了一个仿射(非)相等约束。例如:“View1.bottomY = A * View2.bottomY + B” 'A' 是乘数,'B' 是常数。无论您如何调整 B,它都不会产生与更改 A 的值相同的方程式。 @FruityGeek 好的,所以您使用View2.bottomY
的 current 值来计算约束的 new 常数。这是有缺陷的,因为View2.bottomY
的旧值将被烘焙到常量中,因此您必须放弃声明式样式并在View2.bottomY
更改时手动更新约束。在这种情况下,您不妨手动布局。
如果我希望 NSLayoutConstraint 响应边界更改或设备旋转而无需额外代码,这将不起作用。【参考方案4】:
我用来更改现有布局约束的 乘数 的辅助函数。它创建并激活一个新的约束并停用旧的约束。
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)
【讨论】:
它适用于 iOS 8 吗?或者可以在早期版本中使用NSLayoutConstraint.deactivate
函数仅适用于 iOS 8.0+。
为什么要退货?
@mskw,该函数返回它创建的新约束对象。上一个可以丢弃。【参考方案5】:
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];
【讨论】:
我在这个示例代码中移动了 deactivate,因为active = true
与 activate 相同,因此会导致在调用 deactivate 之前添加重复的约束,这在某些情况下会导致约束错误。
【参考方案6】:
您可以更改“常量”属性,以通过一些数学运算来实现相同的目标。假设约束的默认乘数是 1.0f。这是 Xamarin C# 代码,可以很容易地翻译成 Objective-c
private void SetMultiplier(nfloat multiplier)
FirstItemWidthConstraint.Constant = -secondItem.Frame.Width * (1.0f - multiplier);
【讨论】:
这是一个不错的解决方案 如果在执行上述代码后 secondItem 的 frame 改变了宽度,这将中断。如果 secondItem 永远不会改变大小也没关系,但如果 secondItem 确实改变了大小,那么约束将不再计算布局的正确值。 正如 John Stephen 所指出的,这不适用于动态视图、设备:它不会与布局一起自动更新。 对我来说很好的解决方案,其中第二个项目的宽度实际上是 [UIScreen mainScreen].bounds.size.width (永远不会改变)【参考方案7】:正如在其他答案中解释的那样:您需要删除约束并创建新约束。
您可以通过使用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()
【讨论】:
【参考方案8】:在 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
【讨论】:
【参考方案9】:以上代码都不适合我 所以在尝试修改我自己的代码后这个代码 此代码适用于 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()
【讨论】:
【参考方案10】:是的 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)
【讨论】:
【参考方案11】:Swift 5+
基于Evgenii'sanswer,这里有一个优雅的方法来改变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)
【讨论】:
【参考方案12】:按照许多其他答案的建议,通过更改代码中的活动约束来切换对我不起作用。所以我创建了 2 个约束,一个已安装,另一个未安装,将两者绑定到代码,然后通过删除一个并添加另一个来切换。
为了完整起见,绑定约束使用鼠标右键将约束拖到代码中,就像任何其他图形元素一样:
我命名了一个proportioniPad和另一个proportionIPhone。
然后,在 viewDidLoad 处添加以下代码
override open func viewDidLoad()
super.viewDidLoad()
if ...
view.removeConstraint(proportionIphone)
view.addConstraint(proportionIpad)
我正在使用 xCode 10 和 swift 5.0
【讨论】:
我做了类似的事情,但实际上你只需要链接 1 个约束。第二个可能会以较低的优先级添加,然后删除约束 1 将自动激活约束 2。【参考方案13】:@IBOutlet 弱变量 viewHeightConstraint: NSLayoutConstraint!
让 heightOfSuperview = self.view.bounds.height
viewHeightConstraint.constant = heightOfSuperview * 0.5
//这与乘数的效果相同
【讨论】:
【参考方案14】:这是基于@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()
【讨论】:
【参考方案15】:我有办法。无需重新创建约束。
假设您有一个 imageView,您希望限制其纵横比以匹配图像纵横比。
-
为 imageView 创建一个纵横比约束,并将 multiplier 设置为 1,将 constant 设置为 0。
为纵横比约束创建一个出口。
在运行时根据您加载的图像更改约束常量值:
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
【讨论】:
有趣的组合。【参考方案16】:可以阅读:
var multiplier: CGFloat
The multiplier applied to the second attribute participating in the constraint.
关于这个documentation page。这是否意味着一个人应该能够修改乘数(因为它是一个 var)?
【讨论】:
var multiplier: CGFloat get
是定义,这意味着它只有一个吸气剂。以上是关于我可以更改 NSLayoutConstraint 的乘数属性吗?的主要内容,如果未能解决你的问题,请参考以下文章
NSLayoutConstraint 更改后,systemLayoutSizeFittingSize 不起作用
更改 NSLayoutConstraint 时的 SWIFT 动画
如何以编程方式更改或更新 NSLayoutConstraint