自动布局问题

Posted

技术标签:

【中文标题】自动布局问题【英文标题】:Autolayout issue 【发布时间】:2013-12-02 14:52:07 【问题描述】:

我正在开发一个使用自动布局的应用程序。我正在执行以下步骤:

    第一步:在viewDidLoad中创建一个按钮

    [super viewDidLoad];
    
    self.view.translatesAutoresizingMaskIntoConstraints = NO;
    
    _button1 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    _button1.translatesAutoresizingMaskIntoConstraints = NO;
    [_button1 setTitle:@"B" forState:UIControlStateNormal];
    [self.view addSubview:_button1];
    

    第 2 步:在 updateViewConstraints 方法中实现约束

    [super updateViewConstraints];
    
    [self.view removeConstraints:self.view.constraints];
    
    if (UIInterfaceOrientationIsPortrait([UIApplication sharedApplication].statusBarOrientation))
    
        NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0f constant:100.0f];
        [self.view addConstraint:constraint];
    
        NSLayoutConstraint *constraint1 = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.0f constant:-100.0f];
        [self.view addConstraint:constraint1];
    
        NSLayoutConstraint *constraint2 = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0f constant:200.0f];
        [self.view addConstraint:constraint2];
    
        NSLayoutConstraint *constraint3 = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0f constant:-100.0f];
        [self.view addConstraint:constraint3];
    
    
        _button1.backgroundColor = [UIColor redColor];
     else
        NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0f constant:200.0f];
        [self.view addConstraint:constraint];
    
        NSLayoutConstraint *constraint1 = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.0f constant:-200];
        [self.view addConstraint:constraint1];
    
        NSLayoutConstraint *constraint2 = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0f constant:50.0f];
        [self.view addConstraint:constraint2];
    
        NSLayoutConstraint *constraint3 = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0f constant:-50.0f];
        [self.view addConstraint:constraint3];
    
        _button1.backgroundColor = [UIColor blueColor];
    
    

但是当我切换设备方向时,控制台会打印以下内容:

无法同时满足约束。可能至少有一个 以下列表中的约束是您不想要的。尝试 这个:(1)查看每个约束并尝试找出你 不要期待; (2) 找到添加了不需要的约束的代码或 约束并修复它。 (注:如果你看到 NSAutoresizingMaskLayoutConstraints 你不明白的,参考 到 UIView 属性的文档 translatesAutoresizingMaskIntoConstraints) ( "UIView:0x8a461c0 (名称: '|':UIWindow:0x8a42970 )>", "", "", "UIButton:0x8a45ea0 (名称: '|':UIView:0x8a461c0 )>", "")

将尝试通过打破约束来恢复

中断 objc_exception_throw 以在调试器中捕获它。这 UIView 上的 UIConstraintBasedLayoutDebugging 类别中的方法 中列出的也可能有帮助。

谁能告诉我这个布局有什么问题?

【问题讨论】:

我将您的代码复制到一个新项目中并放入一个 ViewController 中,它工作得很好。所以也许你自己尝试一下,也许你在项目中遗漏了一些东西? @matths 这个问题会在 iPhone 设备/模拟器上表现出来,并且只有当你从横向旋转回纵向时才会出现。 【参考方案1】:

问题是你在updateViewConstraints 中调用[super updateViewConstraints],而按钮仍然有约束。因此,当您从横向过渡到纵向时,您仍然有横向按钮约束(纵向无法满足),但要求主视图更新其(纵向)约束。如果您在删除所有现有按钮约束后将呼叫移至 [super updateViewConstraints] 的任何位置,那么您应该处于良好状态。


几个旁白:

    如果使用情节提要/NIBS,您应该删除以下行:

    self.view.translatesAutoresizingMaskIntoConstraints = NO;
    

    但请保留以下内容:

    _button1.translatesAutoresizingMaskIntoConstraints = NO;
    

    我会警惕大规模取消所有限制。我通常会保留要删除的约束数组,这样我就可以轻松删除我需要删除并将重建的约束。在您的情况下,删除所有可能没问题,但是随着您向视图添加越来越多的约束,可能更容易跟踪要删除和重建的内容:

    @property (nonatomic, strong) NSArray *verticalConstraints;
    @property (nonatomic, strong) NSArray *horizontalConstraints;
    

    我可能会建议使用 VFL,它更简洁一些:

    - (void)updateViewConstraints
    
        if (self.horizontalConstraints)
            [self.view removeConstraints:self.horizontalConstraints];
    
        if (self.verticalConstraints)
            [self.view removeConstraints:self.verticalConstraints];
    
        [super updateViewConstraints];
    
        NSDictionary *views   = NSDictionaryOfVariableBindings(_button1);            
        NSDictionary *metrics = nil;
    
        if (UIInterfaceOrientationIsPortrait([UIApplication sharedApplication].statusBarOrientation))
        
            metrics = @@"left"   : @100,
                        @"right"  : @100,
                        @"top"    : @200,
                        @"bottom" : @100;
    
            _button1.backgroundColor = [UIColor redColor];
         else
            metrics = @@"left"   : @200,
                        @"right"  : @200,
                        @"top"    : @50,
                        @"bottom" : @50;
    
            _button1.backgroundColor = [UIColor blueColor];
        
    
        self.horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(left)-[_button1]-(right)-|" options:0 metrics:metrics views:views];
        self.verticalConstraints   = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(top)-[_button1]-(bottom)-|" options:0 metrics:metrics views:views];
        [self.view addConstraints:self.horizontalConstraints];
        [self.view addConstraints:self.verticalConstraints];
    
    

【讨论】:

我完全按照你的吩咐做了,但徒劳无功。这让我发疯了。 @rokridi 当我 (a) 删除 self.view.translatesAutoresizingMaskIntoConstraints = NO; 行,然后 (b) 将 [super updateViewConstraints] 移动到 [self.view removeConstraints:self.view.constraints] 下方时,错误消失了。 是的,先生!我刚刚完成了它,它工作得很好。谢谢你。但我有一个说法:正如一些教程中提到的(即:ios 6 by Tutorials)我们应该调用 self.view.translatesAutoresizingMaskIntoConstraints = NO。所以,你告诉我这没有必要? @rokridi 这几乎是正确的。您应该在您自己添加并负责定义约束的那些视图(并且仅限于那些视图)上将translatesAutoresizeMasksIntoConstraints 设置为NO。因此,为_button1 设置该属性,但不要为self.view(NIB/故事板为您创建的)设置该属性。 只是为了您的信息,我既没有使用故事板也没有使用 nib 文件(我都是以编程方式完成的)。结论:你的回答是正确的,我非常感谢你。我接受并打勾。【参考方案2】:

这也可以在不检查方向的情况下通过使用约束的乘数和常量值来创建一个适用于纵向和横向的单个约束(对于每个方向)(如果您在情节提要中制作视图,您需要在添加这些约束之前删除您在那里所做的任何约束——您可以通过在属性检查器中为要删除的每个约束选中“占位符 - 构建时删除”框来自动完成此操作)。在您的特定情况下,我认为这些值有效:

- (void)viewDidLoad 
    [super viewDidLoad];

    _button1 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    _button1.translatesAutoresizingMaskIntoConstraints = NO;
    [_button1 setTitle:@"B" forState:UIControlStateNormal];
    [self.view addSubview:_button1];
    NSLayoutConstraint *topCon = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeTop relatedBy:0 toItem:self.view attribute:NSLayoutAttributeBottom multiplier:.9375 constant:-250];
    NSLayoutConstraint *bottomCon = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeBottom relatedBy:0 toItem:self.view attribute:NSLayoutAttributeBottom multiplier:.6875 constant:50];
    NSLayoutConstraint *leftCon = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeLeft  relatedBy:0 toItem:self.view attribute:NSLayoutAttributeRight multiplier:.625 constant:-100];
    NSLayoutConstraint *rightCon = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeRight relatedBy:0 toItem:self.view attribute:NSLayoutAttributeRight multiplier:.375 constant:100];
    [self.view addConstraints:@[topCon,bottomCon,rightCon,leftCon]];

请注意,self.view 的属性对于顶部约束是底部,对于左侧约束来说是右侧。使用乘数时,必须这样做,因为 left 和 top 属性值为零,所以乘以任何东西都没用。

手动计算这些值很痛苦,所以我实际上并没有那样设置它们。相反,我在 NSLayoutConstraint 上编写了一个类别,允许我设置这样的约束(可以在 http://jmp.sh/v/fgHhRNX2twlrgG338CDz 找到具有该类别的 exampleProject):

[self.view addConstraint:[NSLayoutConstraint rightConstraintForView:_button1 viewAttribute:NSLayoutAttributeRight superview:self.view portraitValue:100 landscapeValue:200]];
[self.view addConstraint:[NSLayoutConstraint topConstraintForView:_button1 viewAttribute:NSLayoutAttributeTop superview:self.view portraitValue:200 landscapeValue:50]];
[self.view addConstraint:[NSLayoutConstraint bottomConstraintForView:_button1 viewAttribute:NSLayoutAttributeBottom superview:self.view portraitValue:100 landscapeValue:50]];
[self.view addConstraint:[NSLayoutConstraint leftConstraintForView:_button1 viewAttribute:NSLayoutAttributeLeft superview:self.view portraitValue:100 landscapeValue:200]];

【讨论】:

【参考方案3】:

通常布局约束在 IB 中构建,然后调整方向更改,而不是像您想要的那样丢弃和重新创建方向更改约束。

无论如何,问题似乎在于您没有删除所有必需的约束。 [self.view removeConstraints:self.view.constraints]; 行仅删除了视图自身的约束,并忽略了与 view 相关的其他视图(即超级视图)可能存在约束这一事实。

我不确定这是否是您的问题,但我会尝试调整现有约束,看看是否能解决问题。如果对您有帮助,您可以将 IBOutlets 设置为布局约束。

【讨论】:

我要做的就是在设备方向改变时改变元素的位置。我不知道应该用哪种方法计算元素的新位置。【参考方案4】:

我将您的内容复制并粘贴到一个全新的项目中,并且效果很好。因此,您的项目中可能还有更多可能会干扰的东西。您在使用情节提要吗?

#import "DemoViewController.h"

@interface DemoViewController()

@property (nonatomic, strong) UIButton *button1;

@end

@implementation DemoViewController

- (void)viewDidLoad

    [super viewDidLoad];

    self.view.translatesAutoresizingMaskIntoConstraints = NO;

    _button1 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    _button1.translatesAutoresizingMaskIntoConstraints = NO;
    [_button1 setTitle:@"B" forState:UIControlStateNormal];
    [self.view addSubview:_button1];


- (void)updateViewConstraints

    [super updateViewConstraints];

    [self.view removeConstraints:self.view.constraints];

    if (UIInterfaceOrientationIsPortrait([UIApplication sharedApplication].statusBarOrientation))
    
        NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0f constant:100.0f];
        [self.view addConstraint:constraint];

        NSLayoutConstraint *constraint1 = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.0f constant:-100.0f];
        [self.view addConstraint:constraint1];

        NSLayoutConstraint *constraint2 = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0f constant:200.0f];
        [self.view addConstraint:constraint2];

        NSLayoutConstraint *constraint3 = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0f constant:-100.0f];
        [self.view addConstraint:constraint3];


        _button1.backgroundColor = [UIColor redColor];
     else
        NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0f constant:200.0f];
        [self.view addConstraint:constraint];

        NSLayoutConstraint *constraint1 = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.0f constant:-200];
        [self.view addConstraint:constraint1];

        NSLayoutConstraint *constraint2 = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0f constant:50.0f];
        [self.view addConstraint:constraint2];

        NSLayoutConstraint *constraint3 = [NSLayoutConstraint constraintWithItem:_button1 attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0f constant:-50.0f];
        [self.view addConstraint:constraint3];

        _button1.backgroundColor = [UIColor blueColor];
    


@end

和 AppDelegate:

#import "AppDelegate.h"
#import "DemoViewController.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    self.window.rootViewController = [[DemoViewController alloc] init];
    [self.window makeKeyAndVisible];
    return YES;


@end

【讨论】:

我正在使用 Xcode 5 并选择了一个生成故事板的单一视图应用程序模板。 我使用的是“空应用程序”并且没有 Storyboard。所以答案实际上是你干扰了你的情节提要导致的约束。提示:尝试一一删除您的约束,看看是哪一个导致了错误/警告消息。 我创建了一个新的空项目并添加了与您相同的代码(没有情节提要)。请记住,当您切换设备方向(纵向 --> 横向 --> 纵向)时会出现问题【参考方案5】:

如果你删除这一行

self.view.translatesAutoresizingMaskIntoConstraints = NO;

那我相信你应该不再有这个问题了,我已经见过几次了,如果你使用故事板,那么添加这行代码会导致在使用应用程序时出现这些类型的问题。

【讨论】:

我将 Xcode 5 生成的默认模板用于单视图应用程序。主要故事板可能是问题的根源吗? 当我为 translatesAutoresizingMaskIntoConstraints 添加 NO 时,我会考虑删除该行并查看它是否能解决问题,使用情节提要和视图控制器上的视图,正如您所说的那样,它会崩溃

以上是关于自动布局问题的主要内容,如果未能解决你的问题,请参考以下文章

为啥自动布局与此布局有问题

自动布局是拉伸图像,自动布局,ios

在 iOS 6 中启用自动布局,在 < iOS6 中禁用自动布局

具有自动布局的动态 uiview 布局

Xcode 6 - 另一个自动布局视图中的自动布局视图

使用手动约束的自动布局[关闭]