在IB中无法更改为不同大小类定义的约束IBOutlet

8

我为这个案例制作了一个特殊的测试应用程序。(很抱歉它已被删除)

我在我的控制器视图中添加了一个视图,在Storyboard中设置了自动布局约束,并使其中一个(垂直间距)对不同大小类别有所不同。从IB的屏幕截图

因此,对于任何高度、任何宽度,该值为100,对于常规高度、常规宽度,该值为0。 它工作得很好,在iPhone上,从顶部的垂直距离为100,在iPad上则为0。

此外,我为这个约束制作了IBOutlet,并希望在运行时将其更改为10

@property (weak, nonatomic) IBOutlet NSLayoutConstraint *topVerticalConstraint;

似乎我无法更改它,因为它没有任何效果。
- (void)viewDidLoad
{
    [super viewDidLoad];
    self.topVerticalConstraint.constant = 10; // it doesn't work
}

尽管我在“Interface Builder”中删除“常规高度,常规宽度”的值时可以工作,但是我错过了什么关于大小类的内容吗?

你是否已经在界面生成器中将NSLayoutConstraint正确地与顶部约束绑定? - Utsav Parikh
很确定约束条件还没有在-viewDidLoad中设置。你尝试在-viewDidLayoutSubviews中更改约束条件了吗? - NKorotkov
1
@UtsavParikh 当然可以。当我在“Interface Builder”中删除w R h R的约束条件时,同样的项目示例就能够正常工作。 - Sander
@NKorotkov 是的,将其替换为-viewDidLayoutSubviews有帮助。非常感谢。你是指该属性为空还是尚未应用于视图? - Sander
@Sander属性不为nil,但是你在-viewDidLoad中对它所做的更改将被IB设置的参数覆盖。 - NKorotkov
在 -viewDidLayoutSubviews 中更改约束只会覆盖每次布局调用的所有内容。我认为现在 Interface Builder 的功能超越了代码支持(XCode 7.0.1,Swift 2.0)。请参见我的答案以进行一次性调用。 - MandisaW
6个回答

14
问题在于,约束条件直到在 -viewWillLayoutSubviews-viewDidLayoutSubviews 之间的布局事件发生后才被完全定义,在此期间,来自 IB 的所有参数都会发挥作用。
我的经验法则是:
  • 如果您使用框架手动定位视图,则可以在 -viewDidLoad 中尽早完成它;
  • 如果您使用自动布局约束进行定位,则应在 -viewDidLayoutSubviews 中尽早进行调整。
第二条语句仅考虑了在 IB 中进行的约束代码调整。在 -viewDidLoad 中进行的调整将被布局期间设置的 IB 参数覆盖。如果您使用代码添加约束,则可以在 -viewDidLoad 中设置它们,因为没有任何东西会覆盖它们。
我稍微修改了您的代码,现在它可以工作了:
#import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *topVerticalConstraint;
@property (weak, nonatomic) IBOutlet UIView *square;

@property (assign, nonatomic) BOOL firstLayout;

@end

@implementation ViewController

- (void)viewDidLoad

{
    [super viewDidLoad];

    self.firstLayout = YES;
}

- (void)viewDidLayoutSubviews {

    [super viewDidLayoutSubviews];

    if (self.firstLayout) {

        self.topVerticalConstraint.constant = 10;
        self.firstLayout = NO;

    }
}

@end

请注意,-viewDidLayoutSubviews 在 ViewController 的生命周期中会被调用多次,因此您必须确保在初始加载时只进行一次调整。


4

问题:

如果你在 IB 中为不同的大小类别设置了不同的约束值,如下图所示:

enter image description here

那么你就不能像下面这样在代码中更改常量值:

self.adHeightConstraint.constant = 0;  // value set to 0
[self.view layoutIfNeeded];  // value get back to IB value (44 or 36)

在这种情况下,您可能会发现您的常量值仅在视图重新计算之前保持不变。因此,在 [self.view layoutIfNeeded] 之后,常量的值重置为在IB中设置的任何值。

解决方案:

  1. Add the second constraint of the same attribute (in my case it was the height) with desired value. You may set this value in IB or change it in the code.
  2. Set low priority for this new constraint. Since it's low priority, it won't be any conflict.
  3. Now when you need to apply the new constant, simple disable the first constraint:

    self.adHeightConstraint.active = NO;
    [self.view layoutIfNeeded];
    

这是一个完美的答案!我在第二个约束条件中使用了优先级999。 - foxanna

1
我遇到了同样的问题,但似乎与viewDidLoad和viewDidLayoutSubviews无关。Interface Builder可以管理不同大小类别的备用约束常量,但是当您尝试在代码中更新NSLayoutConstraint.constant时,该常量与任何特定的大小类别(包括活动的大小类别)都没有关联。
从苹果文档(XCode 7,Interface Builder)更改大小类别的约束常量看到: XCode 7 Interface Builder constraints for multiple size classes 我的解决方案是从IB中删除备用常量,并在代码中管理基于大小的约束常量开关,仅针对那些在代码中更新/修改的特定约束条件。任何仅通过storyboard / IB进行管理的约束都可以像往常一样使用备用大小常量。
// XCode 7.0.1, Swift 2.0
static var isCompactHeight : Bool = false;
static var heightOffset : CGFloat {
    return (isCompactHeight ? compactHeightOffset : regularHeightOffset);
}

/* applyTheme can be called as early as viewDidLoad */
func applyTheme() {
    // This part could go wherever you handle orientation changes
    let appDelegate = UIApplication.sharedApplication().delegate;
    let window = appDelegate?.window;
    let verticalSizeClass = window??.traitCollection.verticalSizeClass ?? UIUserInterfaceSizeClass.Unspecified;

    isCompactHeight = (verticalSizeClass == UIUserInterfaceSizeClass.Compact);

    // Use heightOffset
    changingConstraint.constant = heightOffset;
}

我希望Swift/XCode的后续版本能够引入考虑基于尺寸的备选项的getter和setter,这将反映已经通过IB可用的功能。


0

在 viewDidLayoutSubviews 中更改约束的常量

- (void) viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];
    if (IS_IPHONE4) {
        self.topConstraint.constant = 10;
        self.bottomButtonTop.constant = 10;
        self.pageControlTopConstraint.constant = 5;
    }
}

0

我在示例项目中检查了相同的情况,它是可以工作的,也许你忘记将NSLayoutConstraint topVerticalConstraint与Storyboard连接起来了。


0
最简单的解决方案:
if (self.view.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassRegular && self.view.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact) {
    // for iPhone
    cnicTopConstraint.constant = -60;    
} else {
    // for iPad
    cnicTopConstraint.constant = -120;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接