我有一个容器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:]:无法建立约束设置一个等于常量的位置。位置属性必须成对指定
我根本不懂。我看不出如何纠正这个问题。
你能帮忙吗?
答案 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];
}
我发现创建单个约束的NSLayoutConstraint
方法相当长而且不清楚。我添加了一个类别,使其更容易,并提供了所创建的特定约束类型的符号描述。我会添加更多方法,因为它们似乎很有用,但这是我到目前为止所拥有的:
@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
@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