自动执行`setFrame:`UIView的子视图

时间:2014-09-23 00:50:17

标签: ios uikit autolayout

我有一个容器UIView。其子女的职位通过自动布局进行管理。但是,我想将容器本身定位为显式

容器存在于UIView子类中,我称之为VideoPreviewView。容器名为contentOverlayView

这是我的代码,将明显的红色子项添加到填充它的容器中,除了边缘有1个像素的边距:

UIView *const redView = [UIView new];
[redView setTranslatesAutoresizingMaskIntoConstraints: NO];
[redView setBackgroundColor: [UIColor redColor]];
[[videoView contentOverlayView] addSubview: redView];

[[videoView contentOverlayView] addConstraints:
 [NSLayoutConstraint
  constraintsWithVisualFormat:@"H:|-(1@900)-[redView]-(1@900)-|"
  options:0
  metrics:nil
  views:NSDictionaryOfVariableBindings(redView)]];

[[videoView contentOverlayView] addConstraints:
 [NSLayoutConstraint
  constraintsWithVisualFormat:@"V:|-(1@900)-[redView]-(1@900)-|"
  options:0
  metrics:nil
  views:NSDictionaryOfVariableBindings(redView)]];

layoutSubviews

中设置框架

我保持容器视图位于其中的初始方法是简单地覆盖layoutSubviews的{​​{1}}并明确设置容器的框架,如下所示:

VideoPreviewView

......但这没用。如果我在-(void) layoutSubviews { // Other stuff snipped out. const CGRect videoBounds = [self videoBounds]; [_contentOverlayView setFrame: videoBounds]; [super layoutSubviews]; } 上使用recursiveDescription,我会:

videoView

它有足够的宽度来尊重我需要的边距,但是位置完全错误,并且框架没有我想要的那么大。

我在容器视图上使用<VideoPreviewView: 0x146a54f0; frame = (0 0; 320 460); layer = <CALayer: 0x146a5640>> | <UIView: 0x145b6610; frame = (0 0; 2 2); layer = <CALayer: 0x145b6680>> | | <UIView: 0x145bff60; frame = (1 1; 0 0); layer = <CALayer: 0x145bffd0>> 来尝试解决此问题,并且我被告知布局不明确:

_autoLayoutTrace

这告诉我,如果我使用自动布局,则会忽略该帧,因此我需要使用autolayout设置容器的帧。

添加大小限制

其他一些帖子和博客等也暗示了这一行动方案。所以我添加了一些约束来控制容器的宽度和高度:

UIWindow:0x145a3a50
|   UIView:0x145a6e60
|   |   HPBezelView:0x1468d580
|   |   |   UIImageView:0x145aab30
|   |   *VideoPreviewView:0x146a54f0
|   |   |   *UIView:0x145b6610- AMBIGUOUS LAYOUT for UIView:0x145b6610.minX{id: 5}, UIView:0x145b6610.minY{id: 13}, UIView:0x145b6610.Width{id: 8}, UIView:0x145b6610.Height{id: 16}
|   |   |   |   *UIView:0x145bff60- AMBIGUOUS LAYOUT for UIView:0x145bff60.minX{id: 4}, UIView:0x145bff60.minY{id: 12}, UIView:0x145bff60.Width{id: 9}, UIView:0x145bff60.Height{id: 17}

我更新了我的_contentOverlayWidthConstraint = [NSLayoutConstraint constraintWithItem: contentOverlayView attribute: NSLayoutAttributeWidth relatedBy: NSLayoutRelationEqual toItem: nil attribute: NSLayoutAttributeNotAnAttribute multiplier: 1.0 constant: 1]; _contentOverlayHeightConstraint = [NSLayoutConstraint constraintWithItem: contentOverlayView attribute: NSLayoutAttributeHeight relatedBy: NSLayoutRelationEqual toItem: nil attribute: NSLayoutAttributeNotAnAttribute multiplier: 1.0 constant: 1]; [contentOverlayView addConstraints: @[_contentOverlayWidthConstraint, _contentOverlayHeightConstraint]]; 方法来调整这些约束的“常量”:

layoutSubviews

这样做效果好一点 - 红色视图现在可见并且大小正确,但仍然位于错误的位置。我仍然从痕迹中得到歧义:

-(void) layoutSubviews
{
  const CGRect videoBounds = [self videoBounds];
  [_contentOverlayView setFrame: videoBounds];
  [_contentOverlayWidthConstraint setConstant: CGRectGetWidth(videoBounds)];
  [_contentOverlayHeightConstraint setConstant: CGRectGetHeight(videoBounds)];

  [super layoutSubviews];
}

...所以,宽度和高度的模糊性已经消失,但位置模糊仍然存在。

添加位置约束

所以,我添加了两个布局约束来设置容器的位置:

   UIWindow:0x17d76a00
   |   UIView:0x17d7dfd0
   |   |   HPBezelView:0x17d7d7c0
   |   |   |   UIImageView:0x17d82240
   |   |   *VideoPreviewView:0x17d925e0
   |   |   |   UIView:0x17d96790
   |   |   |   *UIView:0x17d96830- AMBIGUOUS LAYOUT for UIView:0x17d96830.minX{id: 9}, UIView:0x17d96830.minY{id: 16}
   |   |   |   |   *UIView:0x17d9ae90- AMBIGUOUS LAYOUT for UIView:0x17d9ae90.minX{id: 8}, UIView:0x17d9ae90.minY{id: 15}

并更新_contentOverlayLeftConstraint = [NSLayoutConstraint constraintWithItem: contentOverlayView attribute: NSLayoutAttributeLeft relatedBy: NSLayoutRelationEqual toItem: nil attribute: NSLayoutAttributeNotAnAttribute multiplier: 1.0 constant: 0]; _contentOverlayTopConstraint = [NSLayoutConstraint constraintWithItem: contentOverlayView attribute: NSLayoutAttributeTop relatedBy: NSLayoutRelationEqual toItem: nil attribute: NSLayoutAttributeNotAnAttribute multiplier: 1.0 constant: 0]; [contentOverlayView addConstraints: @[_contentOverlayLeftConstraint, _contentOverlayTopConstraint]];

layoutSubviews

崩溃

但是当我尝试添加新约束时,我得到一个错误的异常:

  

*由于未捕获的异常'NSInvalidArgumentException'而终止应用程序,原因:'* + [NSLayoutConstraint constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:]:无法建立约束设置一个等于常量的位置。位置属性必须成对指定

我根本不懂。我看不出如何纠正这个问题。

你能帮忙吗?

2 个答案:

答案 0 :(得分:7)

问题在于这些线条和类似:

[NSLayoutConstraint constraintWithItem: contentOverlayView
                             attribute: NSLayoutAttributeLeft
                             relatedBy: NSLayoutRelationEqual
                                toItem: nil
                             attribute: NSLayoutAttributeNotAnAttribute
                            multiplier: 1.0
                              constant: 0];

您不能将Left / Top设置为等于零项目。您必须将其设置为另一个视图的某个属性(通常是该项目的超级视图的左侧/顶部,或者您希望与其对齐的内容)。

只有宽度和高度可以是绝对的。即使他们不能为0(或至少不应该),就像你在这里一样;这毫无意义。

答案 1 :(得分:2)

Matt在这里提供了great answer。我想从我的实施中添加一些我觉得有用的点。

避免违反初始约束

正如Matt所指出的,我创建了约束,使得视图在视图的init期间具有0宽度和高度。我这样做是因为当我创建约束时,我对宽度或高度没有任何明智的想法。不幸的是,如果我将这些约束添加到视图中,它们可能会导致约束违规。

为避免这种情况,init不会将约束添加到视图中。我等到第一次调用updateConstraints。在这里,我知道宽度和高度的正确值,所以我设置了它们。我然后将约束添加到视图中,如果我还没有这样做的话。它看起来像这样:

-(void) updateConstraints
{
  const CGRect videoBounds = [self videoBounds];
  [_contentOverlayWidthConstraint setConstant: CGRectGetWidth(videoBounds)];
  [_contentOverlayHeightConstraint setConstant: CGRectGetHeight(videoBounds)];
  [_contentOverlayLeftConstraint setConstant: CGRectGetMinX(videoBounds)];
  [_contentOverlayTopConstraint setConstant: CGRectGetMinY(videoBounds)];

  if(!_constraintsAreAdded)
  {
    [_contentOverlayView addConstraints: @[_contentOverlayWidthConstraint, _contentOverlayHeightConstraint]];
    [self addConstraints: @[_contentOverlayLeftConstraint, _contentOverlayTopConstraint]];
    _constraintsAreAdded = YES;
  }

  [super updateConstraints];
}

约束创建shorcuts

我发现创建单个约束的NSLayoutConstraint方法相当长而且不清楚。我添加了一个类别,使其更容易,并提供了所创建的特定约束类型的符号描述。我会添加更多方法,因为它们似乎很有用,但这是我到目前为止所拥有的:

的UIView + Constraints.h

@interface UIView (Constraints)
-(NSLayoutConstraint*) widthConstraintWithConstant: (CGFloat) initialWidth;
-(NSLayoutConstraint*) heightConstraintWithConstant: (CGFloat) initialHeight;
-(NSLayoutConstraint*) leftOffsetFromView: (UIView*) view withConstant: (CGFloat) offset;
-(NSLayoutConstraint*) topOffsetFromView: (UIView*) view withConstant: (CGFloat) offset;
@end

的UIView + Constraints.m

@implementation UIView (Constraints)

-(NSLayoutConstraint*) widthConstraintWithConstant: (CGFloat) initial
{
  return [NSLayoutConstraint
          constraintWithItem: self
          attribute: NSLayoutAttributeWidth
          relatedBy: NSLayoutRelationEqual
          toItem: nil
          attribute: NSLayoutAttributeNotAnAttribute
          multiplier: 1.0
          constant: initial];
}

-(NSLayoutConstraint*) heightConstraintWithConstant: (CGFloat) initial
{
  return [NSLayoutConstraint
          constraintWithItem: self
          attribute: NSLayoutAttributeHeight
          relatedBy: NSLayoutRelationEqual
          toItem: nil
          attribute: NSLayoutAttributeNotAnAttribute
          multiplier: 1.0
          constant: initial];
}

-(NSLayoutConstraint*) leftOffsetFromView: (UIView*) view withConstant: (CGFloat) offset
{
  return [NSLayoutConstraint
          constraintWithItem: self
          attribute: NSLayoutAttributeLeft
          relatedBy: NSLayoutRelationEqual
          toItem: view
          attribute: NSLayoutAttributeLeft
          multiplier: 1.0
          constant: offset];
}

-(NSLayoutConstraint*) topOffsetFromView: (UIView*) view withConstant: (CGFloat) offset
{
  return [NSLayoutConstraint
          constraintWithItem: self
          attribute: NSLayoutAttributeTop
          relatedBy: NSLayoutRelationEqual
          toItem: view
          attribute: NSLayoutAttributeTop
          multiplier: 1.0
          constant: offset];
}

@end