在IB中创建的约束,旋转后 activateConstraints: 和 deactivateConstraints: 不会持久化。

29

新的NSLayoutConstraint方法activateConstraints:deactivateConstraints:似乎不能正确地使用IB创建的约束(它们可以正确地处理代码创建的约束)。我创建了一个简单的测试应用程序,其中包含一个带有两组约束的按钮。 一组已安装,具有centerX和centerY约束,而另一组未安装,具有顶部和左侧约束(常量为10)。按钮方法会切换这些约束集。以下是代码:

@interface ViewController ()
@property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray *uninstalledConstraints;
@property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray *installedConstraints;
@end

@implementation ViewController


- (IBAction)switchconstraints:(UIButton *)sender {
    [NSLayoutConstraint deactivateConstraints:self.installedConstraints];
    [NSLayoutConstraint activateConstraints:self.uninstalledConstraints];
}


-(void)viewWillLayoutSubviews {
    NSLog(@"installed: %@    uninstalled: %@", ((NSLayoutConstraint *)self.installedConstraints[0]).active ? @"Active" : @"Inactive", ((NSLayoutConstraint *)self.uninstalledConstraints[0]).active ? @"Active" : @"Inactive");

}
当应用程序启动时,按钮位于其安装的约束定义的正确居中位置。在我执行按钮操作方法中的激活/停用后,按钮能够正确移动到新位置,但是当我将视图旋转到横向时,它会移回到最初定义的位置(尽管日志仍显示新激活设置为活动状态)。当我旋转回纵向时,按钮保持在其初始位置(居中于屏幕),现在日志显示初始的约束集合为活动状态,而我激活的那些则为非活动状态。
问题是,这是一个错误,还是这些方法不能与IB定义的约束以这种方式工作?

哦,很好,现在我们开始认真对待问题了。你能把项目发布到 Github 上吗?我一直都在期待着这个。 - matt
@matt,这是链接,http://jmp.sh/GWIN1Bg - rdelmar
没错,但这不是因为你在storyboard中卸载了约束并将其与outlet关联导致的不连贯吗? - matt
我希望你按照我的要求将它放在Github上,这样我就可以向你推送我的修复。相反,我必须用语言来描述它。 - matt
2
我曾经对“卸载”有同样的误解,也遇到了类似的问题,但是我花了很长时间搜索才找到这个问题及其解决方案。因此,我编辑了标题,以帮助页面更容易被搜索到。 - jscs
3个回答

33

问题在于你在Storyboard中使用了与“未安装”约束不一致的内容。它们存在但又不存在。“未安装”约束仅适用于尺寸类!如果您要允许Xcode在旋转时自动交换约束,则可以使用它们。自动。但是,Xcode无法处理您正在执行的操作。但是,如果您在代码中创建第二组约束,一切都将正常工作。

所以,请这样做。删除两个“未安装”的约束,并删除uninstalledConstraints出口。现在将整个视图控制器代码替换为以下内容:

@property (strong, nonatomic) NSMutableArray *c1;
@property (strong, nonatomic) NSMutableArray *c2;
@property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray *installedConstraints;
@property (weak,nonatomic) IBOutlet UIButton *button;
@end

@implementation ViewController {
    BOOL did;
}

- (void)viewDidLayoutSubviews {
    NSLog(@"did");
    if (!did) {
        did = YES;
        self.c1 = [self.installedConstraints mutableCopy];
        self.c2 = [NSMutableArray new];
        [self.c2 addObject:
         [NSLayoutConstraint constraintWithItem:self.button attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTopMargin multiplier:1 constant:30]];
        [self.c2 addObject:
         [NSLayoutConstraint constraintWithItem:self.button attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeadingMargin multiplier:1 constant:30]];
    }
}

- (IBAction)switchconstraints:(UIButton *)sender {
    [NSLayoutConstraint deactivateConstraints:self.c1];
    [NSLayoutConstraint activateConstraints:self.c2];
    NSMutableArray* temp = self.c1;
    self.c1 = self.c2;
    self.c2 = temp;
}

现在反复按按钮。您会发现它在两个位置之间跳动。现在旋转应用程序;按钮保持原位置。


1
问题在于它们完全正交。激活/停用与未安装的约束问题毫无关系。如果您使用了removeConstraintsaddConstraints,这也会变得一团糟。是未安装的约束与其插座导致了所有问题。使用具有多个大小类的已安装/未安装约束非常好记录;这就是它们的作用。我知道您不能只在Storyboard中绘制两组约束,这很令人失望,但事实就是如此。 - matt
4
我看过安装和卸载带有尺寸类别约束的文档,但我似乎错过了任何关于约束“安装”或“卸载”意义的基本解释。所以,我的(误)理解是未安装的约束是IB中创建约束但未将其添加到视图中的等价物。你能指出这个概念在哪里有很好的文档记录吗? - rdelmar
3
有一个关于编写适应性应用程序的WWDC视频,其中讨论了“安装”复选框的作用。 - 我重申,您希望在Interface Builder中设计两组约束条件并不是不合理的。相反,您有一个简单而有说服力的用例。您应该将其提交给苹果作为增强请求。但目前,您已经陷入了一个边缘情况,应避免使用它(正如您自己的结果所证明的那样)。 - matt
@matt 在你创建这些约束条件的self.和button之间有一个空格。 - elliotrock
@matt,你说的是Session 216吗? - jscs
显示剩余2条评论

16

我面临了类似的情况。

我在接口构建器中创建了两组NSLayoutConstraints,每个案例对应一组。其中一组已经“安装”,另一组没有。

当我切换案例时,相应的布局约束集会被激活,另一个则被停用。如问题所述,来回旋转不起作用。

我通过在接口构建器中安装两组来解决这个问题。为了摆脱警告,我为第二组使用了稍低的优先级(999)。

顺便说一句:奇怪的是,在另一个视图控制器上,我使用了“已安装/未安装”的方法,它起作用了。

在无法工作的情况下,视图控制器嵌入了一个容器视图,也许那就是原因。


2016年8月3日,我已经点赞了这个答案。2017年6月21日,我不能再次点赞它 :( - Rick van der Linde
此外,如果您从其父视图中删除containerview,则在屏幕旋转后它会返回,真的很神秘:P 希望苹果尽快修复这些缺陷。 - Matej Vargovčík
我差点花了几个小时将所有的限制条件都移植到代码中,但这个答案避免了这种情况。谢谢。 - Josh Bernfeld

3
您可以使用此工作流程,尽管目前苹果的专家表示这不是受支持的用例。
诀窍是在您的UIViewController子类的viewDidLoad()中将其停用,而不是卸载约束(正如@matt所指出的),在布局发生之前(并且您的冲突约束引起问题)。
我现在正在使用这种方法,它完全有效。当然,理想情况下,我们将能够可视化创建一组约束,并像关键帧一样使用它们,只需激活和停用它们(之间免费动画)。

如果IB中的这两个约束条件出现错误,您就无法继续添加和布局更多带有约束条件的视图。 - kalafun

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