iOS:使用 NSLayoutConstraint 将对象堆叠在一起

Posted

技术标签:

【中文标题】iOS:使用 NSLayoutConstraint 将对象堆叠在一起【英文标题】:iOS: Stack objects on top of each other using NSLayoutConstraint 【发布时间】:2013-12-20 18:16:31 【问题描述】:

我想将几个按钮堆叠在一起,然后显示/隐藏它们作为一种切换方式。 (想想视频播放器中的播放/暂停/重播按钮)。

是否可以使用 NSLayoutConstraint 来做到这一点?

或者,也许有更好的方法在 ios 中创建多状态按钮?

这是我尝试过的,但显然不起作用,因为它将它们放在一起:

NSArray *constraints = [NSLayoutConstraint
  constraintsWithVisualFormat:@"|[_playButton][_pauseButton][_replayButton][_scrubber][_minimizeButton]|"
                      options:0
                      metrics:nil
                      views:viewsDictionary];

使用下面@dasdom 的一些技巧,我想出了以下几点:

NSArray *constraints = [NSLayoutConstraint
  constraintsWithVisualFormat:@"|[_pauseButton]-[_scrubber]-[_minimizeButton]|"
                      options:NSLayoutFormatAlignAllCenterY
                      metrics:nil
                      views:viewsDictionary];

[constraints arrayByAddingObjectsFromArray:[NSLayoutConstraint
  constraintsWithVisualFormat:@"|[_playButton]"
                      options:NSLayoutFormatAlignAllCenterY
                      metrics:nil
                      views:viewsDictionary]];

[constraints arrayByAddingObjectsFromArray:[NSLayoutConstraint
  constraintsWithVisualFormat:@"|[_replayButton]"
                      options:NSLayoutFormatAlignAllCenterY
                      metrics:nil
                        views:viewsDictionary]];

[self addConstraints:constraints];

非常接近 - 水平间距是正确的,并且对于第一条规则中的项目(pauseButton、scrubber、minimize)它们垂直对齐正确,但是播放和重播按钮太高了......好像随后的NSLayoutFormatAlignAllCenterY 将被忽略。

Edit2:这是我最终得到的代码,比我想象的要多得多,但似乎运行良好:

NSDictionary *metrics = @
    @"buttonPadding": @(kBarPaddingX)
;
// Align along Y center and set order, so scrubber stretches in the middle.
NSString *controlsVisualFormat =
  @"|-buttonPadding-[_playButton]-[_scrubber]-[_minimizeButton]-buttonPadding-|";
NSArray *constraints = [NSLayoutConstraint
  constraintsWithVisualFormat:controlsVisualFormat
                      options:NSLayoutFormatAlignAllCenterY
                      metrics:metrics
                      views:viewsDictionary];

// Set alignment of pauseButton and replayButton
constraints = [constraints arrayByAddingObjectsFromArray:[NSLayoutConstraint
  constraintsWithVisualFormat:@"|-buttonPadding-[_pauseButton]"
                      options:NSLayoutFormatAlignAllCenterY
                      metrics:metrics
                      views:viewsDictionary]];
constraints = [constraints arrayByAddingObjectsFromArray:[NSLayoutConstraint
  constraintsWithVisualFormat:@"|-buttonPadding-[_replayButton]"
                      options:NSLayoutFormatAlignAllCenterY
                      metrics:metrics
                        views:viewsDictionary]];

// Not sure why using NSLayoutFormatAlignAllCenterY above doesn't center the buttons vertically,
// so we need another set of constraints to center them vertically.
constraints = [constraints arrayByAddingObject:
  [NSLayoutConstraint constraintWithItem:_pauseButton
                               attribute:NSLayoutAttributeCenterY
                               relatedBy:NSLayoutRelationEqual
                                  toItem:_pauseButton.superview
                               attribute:NSLayoutAttributeCenterY
                              multiplier:1.0f
                                constant:0]];
constraints = [constraints arrayByAddingObject:
  [NSLayoutConstraint constraintWithItem:_replayButton
                               attribute:NSLayoutAttributeCenterY
                               relatedBy:NSLayoutRelationEqual
                                  toItem:_replayButton.superview
                               attribute:NSLayoutAttributeCenterY
                              multiplier:1.0f
                                constant:0]];

【问题讨论】:

我知道您已经接受了答案,但您确定您的布局没有歧义吗?在 viewDidAppear: 中设置断点,然后在调试器中剪切并粘贴以下内容: po [[UIWindow keyWindow] _autolayoutTrace]. 是的,有什么解决办法吗? 您的contraintsWithVisualFormat前面是否需要包含H:V: 【参考方案1】:

你可以试试

NSArray *constraints = [NSLayoutConstraint
constraintsWithVisualFormat:@"|[_playButton(==_pauseButton)]"
                  options:0
                  metrics:nil
                  views:viewsDictionary];

constraints = [NSLayoutConstraint
constraintsWithVisualFormat:@"V:|[_playButton(==_pauseButton)]"
                  options:0
                  metrics:nil
                  views:viewsDictionary];

[self addConstraint:[NSLayoutConstraint constraintWithItem:_playButton 
    attribute:NSLayoutAttributeCenterY 
    relatedBy:NSLayoutRelationEqual 
    toItem:_pauseButton 
    attribute:NSLayoutAttributeCenterY 
    multiplier:1.0f 
    constant:0.0f]];

[self addConstraint:[NSLayoutConstraint constraintWithItem:_playButton 
    attribute:NSLayoutAttributeCenterX 
    relatedBy:NSLayoutRelationEqual 
    toItem:_pauseButton 
    attribute:NSLayoutAttributeCenterX 
    multiplier:1.0f 
    constant:0.0f]];

【讨论】:

你的意思是用_playButton代替_infoButton吗? 这段代码给了我一些尝试的想法,所以我更新了我的代码(见更新的 q)。我现在只需要弄清楚播放/重播按钮的垂直间距。知道为什么NSLayoutFormatAlignAllCenterY 不适用于这些吗?由于某种原因,它们高于其他元素。 给了你答案,虽然它需要更多的代码(见我的第二次编辑!)干杯!【参考方案2】:

这个答案解决了Edit2下的代码。

你很好地构建了一个漂亮的水平行各种按钮,即_playButton -> _minimizeButton。但是,格式选项NSLayoutFormatAlignAllCenterY 仅将每个按钮相对于所有其他按钮垂直对齐。您还没有建立一个规则来沿垂直轴定位按钮行相对于行的容器视图

由于所有按钮已经相互垂直对齐,因此您只需为其中一个按钮(任何按钮)添加垂直轴类型约束。这是一个假设的示例,它将播放按钮的顶部固定在容器视图顶部的 40 点上。如果你想要一个更实际的例子,我需要对整体布局有更好的了解。

[[containerView addConstraint:[NSLayoutConstraint constraintWithItem:_playButton attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:containerView attribute:NSLayoutAttributeTop multiplier:1.0f constant:40.0f]];

【讨论】:

以上是关于iOS:使用 NSLayoutConstraint 将对象堆叠在一起的主要内容,如果未能解决你的问题,请参考以下文章

iOS开发NSLayoutConstraint代码自动布局

iOS原生自动布局NSLayoutConstraint

IOS开发通过代码方式使用AutoLayout (NSLayoutConstraint + Masonry) 转载

iOS7中的NSLayoutConstraint动画持续时间

如何在 iOS 8 中更改 NSLayoutConstraint 的“乘数”属性的值

iOS 以编程方式为表格视图单元格内容创建 NSLayoutConstraint